How the Runtime Resolver Decides Between Spawning Claude Binary or Pi Subprocess
The runtime resolver prefers the native Claude binary by checking a deterministic lookup order for the SDK executable, and only falls back to the legacy Pi subprocess when the binary cannot be located.
The craft-ai-agents/craft-agents-oss repository implements a deterministic resolution strategy in its runtime resolver to determine which backend executable to spawn. Located in packages/shared/src/agent/backend/internal/runtime-resolver.ts, this module orchestrates the discovery of the modern Claude native binary versus the legacy Pi Node.js subprocess. Understanding this resolution hierarchy is essential for debugging deployment environments and ensuring the correct agent runtime boots.
Claude Binary Discovery via resolveClaudeBinaryPath
The resolver begins by attempting to locate the native claude binary (named claude on Unix systems and claude.exe on Windows) through the resolveClaudeBinaryPath function. This process follows a strict priority order to ensure reproducible builds across different environments.
Stable Build Alias Priority
The resolver first checks for the stable build alias @anthropic-ai/claude-agent-sdk-binary. This package is generated by the build scripts and acts as a deterministic pointer to the correct binary for the current platform. According to the source code in runtime-resolver.ts lines 10-14, this alias takes precedence over all other discovery methods.
Platform-Specific Optional Dependencies
If the alias is not found, the resolver falls back to the per-platform optional-dependency package with the naming pattern claude-agent-sdk-<platform>-<arch>. These packages are created by bun install during development or CI environments, as referenced in lines 94-98 of the resolver implementation.
Development Mode Traversal
In development runs where the CRAFT_DEV_RUNTIME environment variable is set, the resolver performs an upward directory traversal from the app bundle. It searches for either the alias or the platform-specific package up to ten directory levels deep, as implemented in lines 41-45. This allows developers to run the application from various working directories without explicit configuration.
If any candidate location exists, resolveClaudeBinaryPath returns the absolute path; otherwise, it yields undefined and triggers the fallback mechanism.
Pi Subprocess Fallback via resolveServerPath
When the Claude binary cannot be located, the resolver falls back to the legacy Pi subprocess through resolveServerPath(hostRuntime, 'pi-agent-server'), as shown in lines 71-78. This function locates the Node-based Pi server bundled with the Electron application.
Packaged Build Paths
In packaged production builds, the resolver searches for resources/pi-agent-server/index.js (or the equivalent dist/resources/ path). This location contains the bundled Pi server code ready for distribution.
Development Source Paths
For non-packaged development or headless runs, the resolver walks upward from the application root to locate the source file at packages/pi-agent-server/dist/index.js. This allows developers to iterate on the Pi server without rebuilding the entire application bundle.
The Runtime Decision Point
The resolveBackendRuntimePaths function aggregates all discovered paths into a structured object containing claudeCliPath and piServerPath fields. The actual decision logic occurs later in the bootstrap phase, specifically within packages/shared/src/agent/backend/internal/drivers/anthropic.ts.
When the agent runtime initializes, it checks paths.claudeCliPath. If present, the system invokes applyAnthropicRuntimeBootstrap to call setPathToClaudeCodeExecutable, configuring the Anthropic SDK to use the native binary. If the field is missing, the session manager spawns the Pi subprocess using paths.piServerPath, ensuring backward compatibility for environments lacking the modern SDK.
Practical Implementation Example
The following example demonstrates how to resolve runtime paths and implement the decision logic in your own integration:
import {
resolveBackendRuntimePaths,
resolveBackendHostTooling,
} from '@craft-agent/shared/agent/backend/internal/runtime-resolver';
// Host runtime context supplied by Electron (appRootPath, resourcesPath, etc.)
const hostRuntime = {
appRootPath: __dirname,
resourcesPath: process.resourcesPath,
isPackaged: !process.env.CRAFT_DEV_RUNTIME,
// …other fields…
};
// Resolve all possible runtime executables
const runtimePaths = resolveBackendRuntimePaths(hostRuntime);
// Decision point – prefer Claude binary if available
if (runtimePaths.claudeCliPath) {
// Use Claude native binary
console.log('Launching Claude binary at', runtimePaths.claudeCliPath);
// The Anthropic driver will call `setPathToClaudeCodeExecutable` internally
} else if (runtimePaths.piServerPath) {
// Fallback to Pi subprocess
console.log('Claude binary not found – spawning Pi subprocess at', runtimePaths.piServerPath);
// Example: spawn the Pi server with Node
import { spawn } from 'child_process';
spawn('node', [runtimePaths.piServerPath], { stdio: 'inherit' });
} else {
throw new Error('No suitable backend executable could be resolved.');
}
Summary
- The runtime resolver in
packages/shared/src/agent/backend/internal/runtime-resolver.tsimplements a deterministic lookup order that prioritizes the modern Claude native binary. - Claude binary discovery follows a three-tier approach: stable build alias
@anthropic-ai/claude-agent-sdk-binary, platform-specific optional dependencies, and development-mode upward traversal. - Pi subprocess fallback occurs only when
resolveClaudeBinaryPathreturnsundefined, triggeringresolveServerPathto locate the Node-basedpi-agent-serveratresources/pi-agent-server/index.jsorpackages/pi-agent-server/dist/index.js. - The final decision occurs in the Anthropic driver via
applyAnthropicRuntimeBootstrap, which configures the SDK path if the Claude binary exists, otherwise launching the Pi subprocess for backward compatibility.
Frequently Asked Questions
What happens if neither the Claude binary nor the Pi subprocess is found?
If resolveBackendRuntimePaths returns empty values for both claudeCliPath and piServerPath, the bootstrap code throws an error indicating that no suitable backend executable could be resolved. This prevents the agent from entering an undefined state and alerts developers to missing dependencies immediately.
How does the resolver handle different operating systems?
The resolver automatically adjusts binary names and platform identifiers based on the host OS. On Windows, it searches for claude.exe rather than claude, and the platform-specific optional dependencies use the <platform>-<arch> naming convention (e.g., claude-agent-sdk-win32-x64) to ensure the correct native binary is selected for the architecture.
Can I force the runtime resolver to use the Pi subprocess instead of the Claude binary?
Yes, by ensuring the Claude binary is not discoverable through any of the three lookup paths. Remove the @anthropic-ai/claude-agent-sdk-binary alias from your node_modules, ensure no platform-specific packages are installed, and avoid setting CRAFT_DEV_RUNTIME when running outside the expected development tree. When resolveClaudeBinaryPath returns undefined, the system automatically falls back to the Pi server.
Where is the stable build alias @anthropic-ai/claude-agent-sdk-binary defined?
The stable build alias is generated by the repository's build scripts during the packaging process. It is not a public npm package but rather a local synthetic package created by the build system to provide a deterministic, version-locked pointer to the correct Claude binary for the specific build target, as referenced in lines 10-14 of runtime-resolver.ts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →