{"record":{"id":"7dafc1f70aab73bb","repo":"paperclipai/paperclip","slug":"timed-out-checking-command-command-on-sandbox","errorCode":null,"errorMessage":"Timed out checking command \"${command}\" on sandbox target.","messagePattern":"Timed out checking command \"(.+?)\" on sandbox target\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/execution-target.ts","lineNumber":522,"sourceCode":"}\n\nasync function ensureSandboxCommandResolvable(\n  command: string,\n  target: AdapterSandboxExecutionTarget,\n  installCommand: string | null,\n  timeoutSec?: number | null,\n): Promise<void> {\n  // Probe whether the binary is resolvable inside the sandbox. We previously\n  // short-circuited this for sandbox targets, which let the caller report a\n  // success message even when the CLI was missing from the image. Now we run\n  // a real `command -v` through the same runner the hello probe will use, so\n  // the first step honestly reflects whether the binary is on PATH. The\n  // sandbox provider is responsible for sourcing login profiles (e2b mirrors\n  // SSH's buildSshSpawnTarget) so this and the hello probe agree on PATH.\n  let probe = await probeSandboxCommandResolvable(command, target);\n  if (probe.resolved) return;\n  if (probe.timedOut) {\n    throw new Error(`Timed out checking command \"${command}\" on sandbox target.`);\n  }\n\n  // If the caller supplied an install command, attempt the install once via\n  // the sandbox runner (which the sandbox provider wraps in a login shell)\n  // and re-probe before reporting failure. This lets fresh sandbox leases\n  // bring up the CLI before the resolvability gate, mirroring the test path.\n  let installFailureDetail: string | null = null;\n  if (installCommand) {\n    const runner = requireSandboxRunner(target);\n    const installTimeoutMs =\n      typeof timeoutSec === \"number\" && Number.isFinite(timeoutSec) && timeoutSec > 0\n        ? Math.floor(timeoutSec * 1000)\n        : target.timeoutMs ?? 300_000;\n    try {\n      const installResult = await runner.execute({\n        command: \"sh\",\n        args: shellCommandArgs(installCommand),\n        cwd: target.remoteCwd,","sourceCodeStart":504,"sourceCodeEnd":540,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapter-utils/src/execution-target.ts#L504-L540","documentation":"Thrown by ensureSandboxCommandResolvable when the initial `command -v <cmd>` probe inside a sandbox execution target does not return within the configured timeout (target.timeoutMs or 15s default). The sandbox runner wraps the probe in `sh -c`, so a timeout means the sandbox lease is unresponsive, network-stalled, or the shell environment is broken. This fires BEFORE any install command is attempted.","triggerScenarios":"Calling ensureSandboxCommandResolvable(command, target, installCommand, timeoutSec) where target.kind === 'remote' && target.transport === 'sandbox'. The probeSandboxCommandResolvable helper runs `sh -c 'command -v <cmd>'` via the sandbox runner's execute() with timeoutMs = target.timeoutMs ?? 15_000; that call returns { timedOut: true }.","commonSituations":"Sandbox provider (e2b or similar) lease is still warming up or has died; sandbox network proxy is blocking the exec channel; the sandbox image has a broken login shell that hangs on startup; target.timeoutMs is set too low for a cold lease; the sandbox host is under heavy load.","solutions":["Increase target.timeoutMs (or the caller's timeoutSec) to give the cold sandbox lease more time to respond.","Verify the sandbox lease is healthy — check provider dashboard/logs for the lease ID and confirm the VM is running and reachable.","Ensure the sandbox provider sources login profiles correctly so the shell does not hang waiting on interactive profile prompts.","Retry the operation after recycling the sandbox lease if the provider reports the lease as stale."],"exampleFix":"// before\nconst target: AdapterSandboxExecutionTarget = {\n  kind: \"remote\",\n  transport: \"sandbox\",\n  remoteCwd: \"/workspace\",\n  timeoutMs: 5_000, // too short for cold start\n};\n// after\nconst target: AdapterSandboxExecutionTarget = {\n  kind: \"remote\",\n  transport: \"sandbox\",\n  remoteCwd: \"/workspace\",\n  timeoutMs: 30_000, // allow time for lease warm-up\n};","handlingStrategy":"retry","validationCode":"// Pre-check sandbox lease health before probing commands\nasync function isSandboxResponsive(target: AdapterSandboxExecutionTarget): Promise<boolean> {\n  const runner = requireSandboxRunner(target);\n  try {\n    const result = await runner.execute({\n      command: \"sh\",\n      args: [\"-c\", \"echo ok\"],\n      cwd: target.remoteCwd,\n      timeoutMs: Math.min(target.timeoutMs ?? 15_000, 10_000),\n    });\n    return !result.timedOut && (result.exitCode ?? 1) === 0;\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await ensureSandboxCommandResolvable(command, target, installCommand, timeoutSec);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"Timed out checking command\")) {\n    // Sandbox may still be warming up — recycle lease and retry once\n    await recycleSandboxLease(target);\n    await ensureSandboxCommandResolvable(command, target, installCommand, timeoutSec);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Set target.timeoutMs high enough for cold sandbox lease startup (at least 15-30s).","Warm up sandbox leases before assigning agent work by running a simple echo probe.","Monitor sandbox provider health metrics and recycle stale leases proactively."],"tags":["sandbox","timeout","execution-target","adapter-utils"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}