github/copilot-sdk · error · IllegalStateException

Concurrent extraction race: target already exists but is…

Error message

Concurrent extraction race: target already exists but is not a valid file: 

What it means

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.

Solutions

  1. Delete the stale cache entry (rm -rf ~/.copilot/runtime-cache) and restart the application so extraction starts from a clean state.
  2. Retry resolution once — a transient race usually resolves on the second call once the winner's file is valid.
  3. Serialize startup so only one process performs native extraction (e.g. startup lock or init singleton).
  4. Point COPILOT_CLI_PATH at a pre-installed runtime to bypass classpath extraction entirely.

Example fix

// before
Path runtime = NativeRuntimeLoader.resolve();

// after
Path runtime;
try {
    runtime = NativeRuntimeLoader.resolve();
} catch (IllegalStateException | IOException e) {
    Files.walk(Path.of(System.getProperty("user.home"), ".copilot", "runtime-cache"))
         .sorted(Comparator.reverseOrder())
         .forEach(p -> p.toFile().delete());
    runtime = NativeRuntimeLoader.resolve();
}
Defensive patterns

Strategy: retry

Validate before calling

Path cache = Path.of(System.getProperty("user.home"), ".copilot", "runtime-cache");
boolean cacheSane = Files.isDirectory(cache) && Files.list(cache).findAny().isPresent();
// if a previous run crashed, clear stale entries before first resolve()
if (!cacheSane) Files.walk(cache).sorted(Comparator.reverseOrder()).forEach(p -> p.toFile().delete());

Try / catch

try {
    Path runtime = NativeRuntimeLoader.resolve();
} catch (IllegalStateException e) {
    if (e.getMessage().startsWith("Concurrent extraction race")) {
        deleteRuntimeCache();          // clear bad cache entry
        runtime = NativeRuntimeLoader.resolve(); // retry once
    } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/697bf2c1dba07a80. Report an issue: GitHub.

Appendix: source

Thrown at java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java:97

     * {@link StandardCopyOption#ATOMIC_MOVE}.
     */
    static final AtomicPublisher DEFAULT_PUBLISHER = (temp, cached) -> {
        try {
            Files.move(temp, cached, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
        } catch (AtomicMoveNotSupportedException ex) {
            throw new IllegalStateException("Filesystem does not support atomic moves; cannot safely publish "
                    + RUNTIME_FILENAME + " to " + cached, ex);
        } catch (FileAlreadyExistsException | AccessDeniedException ex) {
            // Windows can report AccessDeniedException instead of
            // FileAlreadyExistsException when another publisher wins the race.
            try {
                if (isValidCachedFile(cached)) {
                    return;
                }
            } catch (IOException ignored) {
                // fall through to the error below
            }
            throw new IllegalStateException(
                    "Concurrent extraction race: target already exists but is not a valid file: " + cached, ex);
        }
    };

    private NativeRuntimeLoader() {
    }

    /**
     * Resolves the filesystem path to the {@code runtime.node} binary.
     *
     * <p>
     * Follows the three-step resolution order documented on this class. The
     * returned path is guaranteed to refer to a regular, non-empty file at the time
     * of return.
     *
     * @return absolute path to the {@code runtime.node} binary
     * @throws IOException
     *             if the binary cannot be located or extracted

View on GitHub (pinned to cd8cf15dc3)