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

  1. Pass a canonical (symlink-free, java.io.File.getCanonicalFile()-normalized) cache directory to the loader.
  2. Check the inventory resource for path entries escaping the cache and replace the artifact.
  3. 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

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


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)