github/copilot-sdk · error · IOException
Runtime asset escapes cache directory:
Error message
Runtime asset escapes cache directory:
What it means
After resolving the inventory entry against the cache directory, the resolved path is normalized and checked with cached.startsWith(cacheDir). If the final absolute path does not remain inside the cache directory, the library throws this error. This is a defense-in-depth check (belt to the suspenders of error 180) preventing symlink or resolution tricks from letting an asset be written outside the cache.
Solutions
- Pass a canonical (symlink-free, java.io.File.getCanonicalFile()-normalized) cache directory to the loader.
- Check the inventory resource for path entries escaping the cache and replace the artifact.
- Verify cacheDir is an absolute normalized path without '..' or symlinked parents.
Example fix
// before Path cacheDir = Path.of(prop); // after Path cacheDir = Path.of(prop).toRealPath();
Defensive patterns
Strategy: validation
Validate before calling
// Java: give the loader a canonical, symlink-free cache dir up front
Path cacheDir = Path.of(System.getProperty("copilot.cache.dir",
System.getProperty("user.home") + "/.cache/copilot")).toRealPath(); Try / catch
try { loader.extractRuntimeToCache(...); } catch (IOException e) { if (e.getMessage().startsWith("Runtime asset escapes cache directory")) { // reconfigure cache dir with toRealPath(), clear cache, retry once } else throw e; } Prevention
- Avoid symlinked paths for the cache directory (notably /tmp and /var on macOS)
- Use absolute, normalized paths without '..' when configuring cache locations
- Keep the cache directory owned by the running user
When it happens
Trigger: A cacheDir.resolve(relative).normalize() result escapes cacheDir — e.g. via a '..'-heavy relative path that normalizes oddly, or cacheDir itself containing symbolic links / redundant segments (like '/cache/../cache2') making the prefix comparison fail.
Common situations: Cache directory configured via a path containing symlinks (e.g. /var → /private/var on macOS, or /tmp symlink); user-supplied cache dir with '..' segments; corrupted inventory entries.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Unsafe runtime asset inventory path:
- Filesystem does not support atomic moves; cannot safely…
- Published Copilot CLI is not a non-empty executable file:
- Failed to make Copilot CLI executable:
- Unsafe runtime package path
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/2230c3376af51652.
Report an issue: GitHub.
Appendix: source
Thrown at java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java:465
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(inventoryResource.openStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.isBlank()) {
continue;
}
String[] fields = line.split("\\t", 2);
if (fields.length != 2) {
throw new IOException("Invalid runtime asset inventory entry: " + line);
}
boolean executable = (Integer.parseInt(fields[0], 8) & 0111) != 0;
Path relative = Path.of(fields[1]).normalize();
if (relative.isAbsolute() || relative.startsWith("..")) {
throw new IOException("Unsafe runtime asset inventory path: " + fields[1]);
}
Path cached = cacheDir.resolve(relative).normalize();
if (!cached.startsWith(cacheDir)) {
throw new IOException("Runtime asset escapes cache directory: " + fields[1]);
}
if (isValidCachedFile(cached) && (!executable || isWindows() || Files.isExecutable(cached))) {
continue;
}
String resourcePath = "native/" + classifier + "/" + fields[1];
URL resource = loader.getResource(resourcePath);
if (resource == null) {
throw new FileNotFoundException("Runtime asset not found on classpath: " + resourcePath);
}
Files.createDirectories(cached.getParent());
Path temp = Files.createTempFile(cached.getParent(), "runtime-asset-tmp-", "");
try {
copyResourceToTemp(resource, resourcePath, temp);
if (executable) {
makeExecutable(temp);
}
publisher.publish(temp, cached);View on GitHub (pinned to cd8cf15dc3)