mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-24 01:41:42 +02:00
fix(claude): prepare compatible sandbox runtimes during onboarding (#17259)
## Thinking Path > - Paperclip connects agent accounts and prepares their execution environments. > - A managed sandbox image can outlive the model defaults shipped by the app. > - Claude setup skipped installation whenever a CLI was already on PATH. > - The selected model then failed its minimum-version check after sign-in succeeded. > - This pull request prepares a compatible private CLI before testing or running it. > - Cloud onboarding also stops asking customers to run inaccessible shell commands. ## Linked Issues or Issue Description **What happened?** A new managed Cloud stack completed Claude subscription sign-in. Its environment test then failed because the snapshot contained Claude Code 2.1.263, while the selected `claude-opus-5-5` model requires 2.1.280. The screen displayed Manual debug commands that a hosted customer cannot run. **Expected behavior** Paperclip prepares its managed runtime before testing the selected account. A stale image CLI does not require the customer to upgrade a server package. Failed preparation still blocks hiring. Cloud does not display manual shell debugging. **Steps to reproduce** 1. Start a managed sandbox with Claude Code 2.1.263 on PATH. 2. Connect a Claude subscription and select `claude-opus-5-5` on the CLI lane. 3. Connect the agent. Installation is skipped because the command exists, then the version gate blocks the hello test. **Paperclip version or commit** Source baseline: `6204239cfc8c3be79fc185cf3f468ebaa0ab1e3e`. **Deployment mode** Authenticated managed Cloud with a Daytona named snapshot. The correction also applies to the default CLI in other managed sandbox transports. Related work: Refs #14993 sets model/runtime requirements; Refs #15993 changes native defaults on qualified targets. Neither prepares an outdated default sandbox CLI on this path. Searches found no open duplicate. ## What Changed - Check the actual default CLI against the selected model's existing minimum version. - When a sandbox's default command is outdated, install the exact required npm package into a private versioned workspace prefix. Reuse a compatible private installation. Keep the image's global CLI intact. - Verify the private executable and use that same command for the account hello test and normal CLI runs, with matching command metadata. Probe effort support after preparing that executable so the chosen effort is retained. Keep the model, account, engine and permissions unchanged. - Return safe preparation checks to environment tests and normal run logs, distinguishing installation failure causes without returning raw installer output or credential-bearing URLs. - Exclude provider and Paperclip credentials from the installer environment. Preserve the existing failing version gate if installation or verification cannot succeed. Local, SSH and explicit custom commands remain operator-managed. - Hide Manual debug and legacy operator check hints on Cloud and while deployment metadata is unknown; preserve check messages and details. Retain self-hosted help and the detailed failed checks. Cloud copy asks for a retry instead of asking the customer to repair the environment. - Add subprocess-backed runtime controls, an account-probe regression and mounted Cloud/self-hosted wizard controls. Update development guidance and the affected capability maps. ## Verification - Passed all Claude source suites and the server Claude execution suite on head `c941ae44c22d8dafec0df033a129e39ce4ebd966`: **412 passed**, one existing opt-in test skipped. These include the new command/effort ordering, safe installer diagnostics, environment forwarding and run-log regressions. - On final head `0ecae0e9304f495a57524158b3700b8e9bc21cb8`, passed **208 tests** across all 174 mounted onboarding wizard tests and 34 Claude execution tests. The failure fixture includes the real command-bearing hint, and execution asserts both metadata command fields agree. - Passed `pnpm -r typecheck`, `pnpm build`, `pnpm check:token-gates`, `node scripts/check-maps.mjs` and `git diff --check`. Full typecheck and build were repeated successfully on final head `0ecae0e9304f495a57524158b3700b8e9bc21cb8`. [Latest-head CI](https://github.com/paperclipai/paperclip/actions/runs/39879208694) is green: 54 checks passed and two skipped, including browser E2E and serialized database suites. - The new runtime controls execute real subprocesses with a controlled installer. They cover an old CLI, an already compatible CLI, failed or ineffective installation, local/custom commands, and an unknown version. The probe regression asserts that the hello call uses the private executable. - A separate non-root local subprocess check installed the real npm `@anthropic-ai/claude-code@2.1.280` package. Its private command reported 2.1.280, the simulated image CLI remained 2.1.263, and a second preparation reused the private copy. This ran on macOS ARM64. It did not use a provider account, send a model prompt, or prove live Daytona behavior. - Attempted `pnpm test:run`. Embedded PostgreSQL initialization failed after five attempts on this Mac with `Postgres init script exited with code null`. The unchanged native-session database cases then failed. Stopped only that full-suite process tree after recording the repeated startup failure. No full local-suite pass is claimed; Linux CI must cover those tests. - The first new subprocess fixture had a quoting syntax error. It was corrected before the passing focused runs. The one-off real-package script first required an ESM extension correction, then passed. Logs retain both attempts. A new execution assertion initially inspected the final sandbox cleanup shell instead of the launched command; it now checks execution metadata. A broader directory selection also picked up generated `dist` tests whose Markdown fixtures are not copied by the build (two ENOENT failures). The explicit source-suite run above passes; no generated-test pass is claimed. - Apex review: **5/5**, reviewed final head `0ecae0e9304f495a57524158b3700b8e9bc21cb8`, all five earlier findings addressed and resolved, no new findings. - Merge, deployment and a fresh live Cloud subscription test are still required. No customer account or production runtime was changed by this branch. ## Risks - An outdated sandbox needs npm registry access and up to 120 seconds for one bounded installation attempt. A failed or unverified install continues to block execution. - The versioned package uses sandbox workspace storage. Compatible cached copies avoid repeat installation within that workspace. Existing runtime cleanup and sandbox lifecycle still apply. - The compatible version comes from the adapter's existing model-version contract. This does not add a model fallback or change the selected execution engine. - No schema, credential, permission, workflow or lockfile change. ## Model Used OpenAI Codex, based on GPT-6, assisted with implementation and verification. The exact serving model ID and context-window limit are not exposed in this session. Capabilities used: code editing, shell execution, API inspection and test execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
650581b2f0
commit
c5ca29dbef
13 files changed
+409
-36
No files matched your search
@@ -1049,6 +1049,59 @@ describe("claude execute", () => {
|
||||
}
|
||||
}, 10_000);
|
||||
|
||||
it.each(["compatible", "network-error"])("prepares the selected Claude command before probing effort and logs the %s outcome", async state => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-claude-prepared-effort-"));
|
||||
const { workspace, commandPath, capturePath, restore } = await setupExecuteEnv(root, {
|
||||
commandWriter: writeHelpWithoutEffortClaudeCommand,
|
||||
});
|
||||
const remoteWorkspace = path.join(root, "sandbox-workspace");
|
||||
const preparedCommand = path.join(remoteWorkspace, ".paperclip-runtime/claude-cli/2.1.280/bin/claude");
|
||||
const logs: string[] = [];
|
||||
const commands: string[] = [];
|
||||
const loggedCommands: string[] = [];
|
||||
try {
|
||||
await fs.writeFile(commandPath, (await fs.readFile(commandPath, "utf8")).replace("2.1.286", "2.1.263"));
|
||||
await fs.mkdir(remoteWorkspace, { recursive: true });
|
||||
if (state === "compatible") {
|
||||
await fs.mkdir(path.dirname(preparedCommand), { recursive: true });
|
||||
await writeHelpWithEffortClaudeCommand(preparedCommand);
|
||||
}
|
||||
const runner = createLocalSandboxRunner();
|
||||
const result = await execute({
|
||||
runId: `run-prepared-effort-${state}`,
|
||||
agent: { id: "agent-1", companyId: "company-1", name: "Claude Coder", adapterType: "claude_local", adapterConfig: { engine: "cli" } },
|
||||
runtime: { sessionId: null, sessionParams: null, sessionDisplayId: null, taskKey: null },
|
||||
config: { engine: "cli", command: "claude", cwd: workspace, model: "claude-opus-5-5", effort: "high",
|
||||
env: { PAPERCLIP_TEST_CAPTURE_PATH: capturePath }, promptTemplate: "Keep the selected reasoning effort." },
|
||||
context: {},
|
||||
executionTarget: { kind: "remote", transport: "sandbox", providerKey: "daytona", environmentId: "env-1", leaseId: "lease-1", remoteCwd: remoteWorkspace,
|
||||
runner: { execute: async input => {
|
||||
if (input.args?.some(arg => arg.includes("npm install"))) return { exitCode: 1, signal: null, timedOut: false,
|
||||
stdout: "", stderr: "npm error code ENOTFOUND https://registry-secret@example.test/private", pid: null, startedAt: new Date().toISOString() };
|
||||
return runner.execute(input);
|
||||
} } },
|
||||
authToken: "run-jwt-token", onLog: async (_stream, chunk) => { logs.push(chunk); },
|
||||
onMeta: async meta => { commands.push(meta.command); loggedCommands.push(meta.env.PAPERCLIP_RESOLVED_COMMAND); },
|
||||
});
|
||||
if (state === "compatible") {
|
||||
expect(result.exitCode).toBe(0);
|
||||
const capture = JSON.parse(await fs.readFile(capturePath, "utf8")) as CapturePayload;
|
||||
expect(capture.argv).toEqual(expect.arrayContaining(["--effort", "high"]));
|
||||
expect(commands).toContain(preparedCommand);
|
||||
expect(loggedCommands).toEqual(commands);
|
||||
expect(logs.join("")).not.toContain("omitting configured effort");
|
||||
} else {
|
||||
expect(result.errorCode).toBe("claude_cli_version_incompatible");
|
||||
expect(logs.join("")).toContain("The package installation could not reach the registry.");
|
||||
expect(logs.join("")).not.toContain("registry-secret");
|
||||
await expect(fs.readFile(capturePath)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
}
|
||||
} finally {
|
||||
restore();
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
}
|
||||
}, 10_000);
|
||||
|
||||
it("passes through --effort and reuses the sandbox capability probe across sandbox leases when the installed Claude CLI advertises it", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-claude-execute-sandbox-effort-supported-"));
|
||||
const { workspace, commandPath, capturePath, restore } = await setupExecuteEnv(root, {
|
||||
|
||||
Reference in new issue
Block a user