github/copilot-sdk · error · IOException
Failed to make Copilot CLI executable:
Error message
Failed to make Copilot CLI executable:
What it means
makeExecutable calls File.setExecutable(true, false) on a freshly written binary. If a SecurityException is raised, this IOException wrapping the path is thrown. It means the JVM was not permitted to set the executable bit — typically a Java SecurityManager policy restriction or an OS-level ACL denying chmod on the target.
Solutions
- Grant the running code FilePermission(read,write,execute) on the cache directory in the security policy.
- Point the cache directory at a user-writable location (e.g. -Dcopilot.cache.dir=$HOME/.cache/copilot).
- Remove or relax the SecurityManager/sandbox restrictions if policy allows.
- Check directory ACLs (icacls / chmod / ls -le) and fix ownership so the running user can chmod files.
Example fix
// before (policy file)
grant { permission java.io.FilePermission "/opt/copilot-cache/-", "read"; };
// after
grant { permission java.io.FilePermission "/opt/copilot-cache/-", "read,write,execute,delete"; }; Defensive patterns
Strategy: try-catch
Validate before calling
// Java: confirm the JVM may write+execute in the cache dir before loading
Path probe = cacheDir.resolve(".probe");
Files.writeString(probe, "x");
probe.toFile().setExecutable(true, false);
Files.deleteIfExists(probe); Try / catch
try { loader.loadRuntime(); } catch (IOException e) { if (e instanceof SecurityException || e.getMessage().startsWith("Failed to make Copilot CLI executable")) { // widen SecurityManager policy or switch cache dir } else throw e; } Prevention
- Grant FilePermission read/write/execute/delete on the cache path when using a SecurityManager
- Run under a user with write access to the cache directory
- Check OS ACLs on company-managed machines before deploying
When it happens
Trigger: A SecurityManager (or sandboxed embedding) denies FilePermission 'execute'/'write' for the cache path when resolving the runtime wrapper or extracting runtime assets/CLI.
Common situations: Application servers or containerized environments running with a SecurityManager and restrictive policy; read-only or ACL-locked cache directories; macOS/Windows ACLs on company-managed machines.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Filesystem does not support atomic moves; cannot safely…
- Published runtime wrapper is not a non-empty executable…
- Runtime asset escapes cache directory:
- Published Copilot CLI is not a non-empty executable file:
- failed to chmod binary
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/43b199e5d1811814.
Report an issue: GitHub.
Appendix: source
Thrown at java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java:574
if (!Files.isRegularFile(path)) {
return false;
}
return Files.size(path) > 0;
}
private static boolean isValidCachedCli(Path path) throws IOException {
return isValidCachedFile(path) && (isWindows() || Files.isExecutable(path));
}
private static void makeExecutable(Path path) throws IOException {
if (isWindows()) {
return;
}
final boolean executableSet;
try {
executableSet = path.toFile().setExecutable(true, false);
} catch (SecurityException ex) {
throw new IOException("Failed to make Copilot CLI executable: " + path, ex);
}
if (!executableSet || !Files.isExecutable(path)) {
throw new IOException("Failed to make Copilot CLI executable: " + path);
}
}
private static void copyResourceToTemp(URL resource, String resourcePath, Path temp) throws IOException {
try (InputStream in = resource.openStream()) {
long bytesWritten = Files.copy(in, temp, StandardCopyOption.REPLACE_EXISTING);
if (bytesWritten == 0) {
throw new IllegalStateException("Classpath resource is empty: " + resourcePath);
}
}
// Flush OS buffers to durable storage before the atomic rename.
try (FileChannel channel = FileChannel.open(temp, StandardOpenOption.WRITE)) {
channel.force(true);
}
}View on GitHub (pinned to cd8cf15dc3)