{"record":{"id":"697bf2c1dba07a80","repo":"github/copilot-sdk","slug":"concurrent-extraction-race-target-already-exists","errorCode":null,"errorMessage":"Concurrent extraction race: target already exists but is not a valid file: ","messagePattern":"Concurrent extraction race: target already exists but is not a valid file: ","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java","lineNumber":97,"sourceCode":"     * {@link StandardCopyOption#ATOMIC_MOVE}.\n     */\n    static final AtomicPublisher DEFAULT_PUBLISHER = (temp, cached) -> {\n        try {\n            Files.move(temp, cached, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);\n        } catch (AtomicMoveNotSupportedException ex) {\n            throw new IllegalStateException(\"Filesystem does not support atomic moves; cannot safely publish \"\n                    + RUNTIME_FILENAME + \" to \" + cached, ex);\n        } catch (FileAlreadyExistsException | AccessDeniedException ex) {\n            // Windows can report AccessDeniedException instead of\n            // FileAlreadyExistsException when another publisher wins the race.\n            try {\n                if (isValidCachedFile(cached)) {\n                    return;\n                }\n            } catch (IOException ignored) {\n                // fall through to the error below\n            }\n            throw new IllegalStateException(\n                    \"Concurrent extraction race: target already exists but is not a valid file: \" + cached, ex);\n        }\n    };\n\n    private NativeRuntimeLoader() {\n    }\n\n    /**\n     * Resolves the filesystem path to the {@code runtime.node} binary.\n     *\n     * <p>\n     * Follows the three-step resolution order documented on this class. The\n     * returned path is guaranteed to refer to a regular, non-empty file at the time\n     * of return.\n     *\n     * @return absolute path to the {@code runtime.node} binary\n     * @throws IOException\n     *             if the binary cannot be located or extracted","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java#L79-L115","documentation":"Thrown by NativeRuntimeLoader's atomic publisher after an ATOMIC_MOVE fails with FileAlreadyExistsException or AccessDeniedException. It means another process/thread won the race to publish the cached native file, but the file already at the target path failed validation (missing, non-regular, or zero bytes). The loader cannot confirm a usable cached copy exists.","triggerScenarios":"Two JVMs (or threads) concurrently call NativeRuntimeLoader.resolve()/resolveRuntimeWrapper() on a cold cache; the winner of the atomic move leaves a corrupt, empty, or deleted file at the cache path, and the loser's post-race validity check (isValidCachedFile) fails.","commonSituations":"Parallel integration-test JVMs sharing ~/.copilot/runtime-cache; multiple app instances in the same container starting simultaneously; a previous crashed run left a zero-byte runtime.node; an antivirus or cleanup process truncated/removed the file between move and validation.","solutions":["Delete the stale cache entry (rm -rf ~/.copilot/runtime-cache) and restart the application so extraction starts from a clean state.","Retry resolution once — a transient race usually resolves on the second call once the winner's file is valid.","Serialize startup so only one process performs native extraction (e.g. startup lock or init singleton).","Point COPILOT_CLI_PATH at a pre-installed runtime to bypass classpath extraction entirely."],"exampleFix":"// before\nPath runtime = NativeRuntimeLoader.resolve();\n\n// after\nPath runtime;\ntry {\n    runtime = NativeRuntimeLoader.resolve();\n} catch (IllegalStateException | IOException e) {\n    Files.walk(Path.of(System.getProperty(\"user.home\"), \".copilot\", \"runtime-cache\"))\n         .sorted(Comparator.reverseOrder())\n         .forEach(p -> p.toFile().delete());\n    runtime = NativeRuntimeLoader.resolve();\n}","handlingStrategy":"retry","validationCode":"Path cache = Path.of(System.getProperty(\"user.home\"), \".copilot\", \"runtime-cache\");\nboolean cacheSane = Files.isDirectory(cache) && Files.list(cache).findAny().isPresent();\n// if a previous run crashed, clear stale entries before first resolve()\nif (!cacheSane) Files.walk(cache).sorted(Comparator.reverseOrder()).forEach(p -> p.toFile().delete());","typeGuard":null,"tryCatchPattern":"try {\n    Path runtime = NativeRuntimeLoader.resolve();\n} catch (IllegalStateException e) {\n    if (e.getMessage().startsWith(\"Concurrent extraction race\")) {\n        deleteRuntimeCache();          // clear bad cache entry\n        runtime = NativeRuntimeLoader.resolve(); // retry once\n    } else throw e;\n}","preventionTips":["Avoid multiple JVMs extracting to the shared ~/.copilot/runtime-cache simultaneously; serialize first startup.","Pre-set COPILOT_CLI_PATH in CI/containers to skip extraction races.","Clean ~/.copilot/runtime-cache after abnormal JVM termination.","Keep the cache on a local filesystem, not NFS."],"tags":["native-loader","race-condition","filesystem","cache"],"backgroundTag":"invalid-state-transition","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}