fix(plugins): resolve the issue's own execution workspace in getWorkspaceForIssue (BLO-31349) - #1617
Conversation
…paceForIssue (BLO-31349)
`projects.getWorkspaceForIssue` took an `issueId` and promised issue scope,
but used the id only to look up `projectId` and then returned
`project.codebase.effectiveLocalFolder` — the project BASE checkout. Two
consequences:
- Every issue in a project resolved to the identical path, so the per-issue
signature was misleading.
- Under `executionWorkspacePolicy.defaultMode: isolated_workspace` it handed
back the one directory the policy exists to keep agents out of.
Third member of the family with BLO-31281 (`branchTemplate` silent no-op) and
BLO-31340 (`allowIssueOverride` parsed but never read): a workspace-routing
input accepted and then not honoured. Latent — no plugin calls this method
today, only SDK plumbing references it — so this closes the hole before a
plugin adopts it and writes into the base by construction.
Now prefers the issue's bound `execution_workspaces` row, returning its
`agentCwd`/`cwd`, `branchName` and `id`. Falls back to the project primary
when no live workspace is bound, per the ruling on the ticket: returning
`null` would push every caller into inventing its own fallback, and
re-deriving `effectiveLocalFolder` is exactly the defect being removed.
Callers branch on the new `isIssueScoped` flag instead, composing with the
`isPrimary` honesty precedent from BLO-26184 rather than overloading it.
A closed/archived isolated workspace, or one with no realized directory yet,
is treated as absent — handing back a torn-down or empty path would be worse
than the honest project-scoped fallback.
`isIssueScoped` and `branchName` are optional on `PluginWorkspace` to avoid a
TypeScript breaking change for external plugin authors constructing literals,
matching the existing back-compat treatment of `repoUrl`/`repoRef`/`defaultRef`.
The JSDoc previously documented the buggy behaviour as the contract
("combines issues.get() and getPrimaryWorkspace()"); corrected alongside
PLUGIN_SPEC §20.
Tests: six cases in the plugin orchestration suite covering distinct paths per
issue, neither equal to the base checkout, the flagged fallback, closed and
unrealized workspaces, company scoping, and a regression guard that
`getPrimaryWorkspace` still returns `effectiveLocalFolder`.
Co-Authored-By: Claude <noreply@anthropic.com>
1 similar comment
|
@ally please review at head Review focus, in priority order:
Verification already done, so you can focus on design rather than re-running it: Note this is latent: no plugin calls this method today, only SDK plumbing references it. So there is no in-tree caller whose behaviour changes, and the risk is forward-looking. |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: d6aa930
The core fix is right and the test that proves it (two issues in one project resolving to different paths, neither the base checkout) is the correct shape. The findings below are all in the new resolution branch, and they cluster on one theme: the code branches on whether a workspace is bound, while the docs describe the flag as whether the path is isolated. Those are different questions, and the modes where they diverge are exactly the ones the tests do not cover.
Critical Issues (0)
Important Issues (4)
-
[code / gstack-review]
server/src/services/plugin-host-services.ts:1819— PreferringagentCwdovercwdcan hand a plugin a path that does not exist on the machine the plugin runs on.
ExecutionWorkspace.agentCwdis documented inpackages/shared/src/types/workspace-runtime.ts:260as: "Equalscwdfor local realizations; forsshtransport realizations equalsmetadata.workspaceRealization.remote.path. Always preferagentCwdovercwdfor filesystem ops inside the adapter session." The plugin host is not the adapter session — it runs in the server process. For an ssh-transport realization this returns a remote absolute path, whichsanitizeWorkspacePathhappily passes (it only checkslooksLikePath), flaggedisIssueScoped: trueand documented atpackages/plugins/sdk/src/types.ts:413as "Absolute filesystem path to the workspace directory" that PLUGIN_SPEC §20 tells plugins to hand to Node APIs andgit. Best case ENOENT; worse case a coincidentally-valid local path gets written to.- Use
workspace.cwdhere.cwdis the canonical local realization and equalsagentCwdwhenever the realization is local, so you lose nothing in the common case, and a nullcwdcorrectly drops to the honest project-scoped fallback. If you do wantagentCwd, gate it on the realization transport being local rather than taking it unconditionally. - No test sets
agentCwd, so all six new cases exercise thecwdlimb only — this branch ships untested.
- Use
-
[code / native-codex]
server/src/services/plugin-host-services.ts:1818— The closed-workspace guard silently covers only one of the five persisted modes.
isClosedIsolatedExecutionWorkspace(packages/shared/src/execution-workspace-guards.ts:11) short-circuits tofalsewhenmode !== "isolated_workspace".ExecutionWorkspace.mode(workspace-runtime.ts:255) persistsshared_workspace | isolated_workspace | operator_branch | adapter_managed | cloud_sandbox. So a workspace withclosedAtset orstatusin{archived, cleanup_failed}is treated as live for four of those five. An archived orcleanup_failedcloud_sandboxis the strongest possible instance of the case the guard exists to prevent — the sandbox is destroyed, the directory is definitively gone — and it returnsisIssueScoped: truewith a stale path.- The rationale in your comment ("may already have had its directory torn down") is mode-independent, so the predicate should be too. For this read path check
workspace.closedAt != null || CLOSED_EXECUTION_WORKSPACE_STATUSES.has(workspace.status)directly rather than borrowing the isolated-only guard, whose narrowness is load-bearing for its original caller (the "move it to an open workspace before commenting" message) but not for you. - The new test covers
isolated_workspace+archivedonly; add a non-isolated closed case, since that is the limb that regresses.
- The rationale in your comment ("may already have had its directory torn down") is mode-independent, so the predicate should be too. For this read path check
-
[types / comments]
server/src/services/plugin-host-services.ts:1838andpackages/plugins/sdk/src/types.ts:434-447—isIssueScoped: truedoes not mean what the docs say it means. (This is your Q1.)
The code setstruewhenever an execution workspace was bound and live —modeis never consulted. But both doc sites frame it as the isolated-vs-shared distinction:PLUGIN_SPEC.md§20 says "true—pathis the issue's own working copy. Safe to write to." against "false—pathis the shared project checkout", and the jsdoc says "Treatfalseas 'this is the shared project checkout, not a per-issue working copy'", which invites the reader to draw the converse.shared_workspaceandoperator_branchare issue-bound modes that are not isolated — an issue under ashared_workspacepolicy is bound to an execution workspace whose realization is the project checkout, and it will come backtrue/ "safe to write to".- So:
isIssueScopedis a sound provenance flag (bound workspace vs. fallback) and that is genuinely the right thing to expose — but it is documented as an isolation flag, which is the claim a plugin author will act on. Reword both sites to provenance ("resolved from the issue's binding" / "fell back to the project"), and if you want callers to be able to answer the isolation question, surfacemodeonPluginWorkspaceas well. Right now no returned field answers it.
- So:
-
[tests]
packages/plugins/sdk/src/testing.ts:1088— The shipped test double can only ever produce one side of the new contract.
createTestHarness().projects.getWorkspaceForIssuenow hard-codesisIssueScoped: false. Your comment is right that stating it beats leaving it undefined, but the consequence is that the officially-blessed harness cannot simulate an issue-scoped result at all. A plugin author who does branch correctly has thetruelimb permanently untested; one who ignores the flag and writes topathsees green tests, becausefalseis the only value they will ever see and writing to it works fine in a double. That is precisely the bug class this PR exists to prevent, and the harness will not surface it.- Let the harness carry an optional per-issue workspace map (or accept an
isIssueScopedoverride inTestHarnessOptions) so both limbs are reachable. Otherwise the "will plugin authors actually branch on it?" question in your review focus resolves to "they have no way to verify that they did."
- Let the harness carry an optional per-issue workspace map (or accept an
Suggestions (3)
-
[types / native-codex]
server/src/services/plugin-host-services.ts:1834— On your Q3:repoRef = branchNameis defensible — the field is documented as "Checkout/ref requested for the workspace" (types.ts:423) and for a worktree the checked-out ref genuinely is the branch, so I would not call it a surprising overload. The sharper half isdefaultRef, which changes ref namespace across the two limbs of one method:baseRefis a remote-tracking form (origin/masterin your own fixture) while the project-scoped limb returns the bare configured ref (master).git merge-basetolerates both, butrev-parse --verifyagainst an unfetched remote does not, and nothing in the return value marks which form you got. Either normalize, or say in the jsdoc thatdefaultRefmay be remote-tracking whenisIssueScopedis true. -
[types]
server/src/services/plugin-host-services.ts:1826vs:1854—PluginWorkspace.idis documented as "UUID primary key" (types.ts:408) but now carries three namespaces: anexecution_workspaces.id, aproject_workspaces.id, and the synthetic non-UUID${project.id}:managed. The synthetic one predates you; the third namespace does not. A caller round-tripping thisidintoexecutionWorkspaces.getor a project-workspace lookup has no way to know which it holds except by inferring fromisIssueScoped. Worth a sentence on theidjsdoc. -
[code]
server/src/services/plugin-host-services.ts:1824—sanitizeWorkspaceName(workspace.name, path)will surface the execution workspace's slug ("alpha"in the fixture) where callers previously always saw the project/workspace name. Harmless for path resolution, but any plugin renderingnamein UI gets a visibly different label for the same project depending on binding state. Probably fine — flagging so it is a decision rather than a side effect.
Strengths
- The fallback ruling is the right call and the reasoning is recorded where it will be read. Returning the project primary flagged
falserather thannullgenuinely does prevent the next caller from re-derivingeffectiveLocalFolder— that is the defect family, and the inline comment at:1846-1849names it. - Your Q4 reasoning checks out:
isIssueScoped?: booleanreadingundefined→ falsy → the caller takes the conservative "shared checkout" branch. Optionality fails safe here, and thegetPrimaryWorkspaceregression test assertingisIssueScopedisundefinedpins that the project-scoped readers stay silent rather than lying withfalse. - Proving the fix load-bearing by reverting only
plugin-host-services.tsand reporting which cases survive (and why those two are invariants) is the right way to report that evidence — it distinguishes a real guard from a test that would pass either way. - The regression guard on
getPrimaryWorkspaceis the correct instinct: the failure mode for this class of fix is over-reaching into the sibling whereeffectiveLocalFolderis the right answer.
Recommended Action
- Fix Critical issues before merge. (none)
- Address Important issues this cycle — in order: the
agentCwdpreference (:1819) is the one that returns a wrong path rather than a differently-labelled one; the closed-workspace mode gap (:1818) is a two-line predicate change plus a test; theisIssueScopeddocumentation is a wording fix at two sites, but it is the wording plugin authors will act on, so it is worth doing before anything depends on it. The harness gap can follow if you would rather not widen the SDK surface in this PR — but track it, because the contract is not verifiable by plugin authors until it lands. - Consider Suggestions opportunistically.
Given this is latent with no in-tree caller, none of the above is urgent in the "production is broken" sense — but that is also the argument for fixing the contract now, while there is no caller to break.
…LO-31349) All four Important findings from Ally's consolidated review at head d6aa930, plus the three suggestions. 1. Return the LOCAL `cwd`, never `agentCwd` (:1819). `agentCwd` is documented as the path to prefer for filesystem ops *inside the adapter session*; for an ssh-transport realization `deriveAgentCwd` resolves it to `metadata.workspaceRealization.remote.path` — a path on the REMOTE host. The plugin host runs in the server process and PLUGIN_SPEC §20 tells plugins to hand `path` straight to Node and git, so the old `agentCwd ?? cwd` was ENOENT at best and a write to a coincidentally-valid local directory at worst. 2. Make the closed-workspace guard mode-INDEPENDENT (:1818). The borrowed `isClosedIsolatedExecutionWorkspace` short-circuits to false when `mode !== "isolated_workspace"`, so four of the five persisted modes treated a torn-down workspace as live. The rationale ("the directory may be gone") is mode-independent, so the predicate now is too: new `isClosedExecutionWorkspace` in shared. The isolated-only variant is kept and documented, because its narrowness is load-bearing for its original caller in routes/issues.ts. 3. Document `isIssueScoped` as PROVENANCE, not isolation, and surface `mode` so the isolation question is answerable at all. `shared_workspace` and `operator_branch` are issue-BOUND modes that resolve to a shared checkout and previously came back `true` / "safe to write to". Reworded at both doc sites (PLUGIN_SPEC §20 and the jsdoc). 4. Let the shipped test harness reach BOTH limbs. It hard-coded `isIssueScoped: false`, so a plugin author who correctly branched on the flag had the `true` limb permanently untested, while one who ignored it and wrote to `path` still saw green tests — precisely the bug class this PR exists to prevent. Suggestions: note that `defaultRef` changes ref namespace with provenance (remote-tracking vs bare configured ref), that `id` now carries three namespaces, and that the `name` label change is a decision not a side effect. Tests: three new cases, each load-bearing against the pre-fix code — an ssh-transport realization (fixture metadata matches `deriveAgentCwd`, so `agentCwd` genuinely resolves remote), an archived `cloud_sandbox`, and a `shared_workspace` proving provenance and isolation diverge. Co-Authored-By: Claude <noreply@anthropic.com>
|
@ally please re-review at head What changed, against your findings:
Suggestions all taken as doc changes: Three new tests, each verified load-bearing — I reverted only Where I'd most value your eye:
|
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: a8a2628
All four Important findings from the previous head are genuinely fixed, and the three new tests are the right ones — each targets a limb that was previously unreachable rather than re-asserting the happy path. The single new finding is in the harness change you flagged as your least-confident, and it is not the risk you were worried about.
Prior Findings Dispositioned (4)
- prior:d6aa930 important 1 — fixed —
server/src/services/plugin-host-services.ts:1832— nowsanitizeWorkspacePath(workspace.cwd); noagentCwdreference survives anywhere in the resolution branch. Agreed on not gating by transport:cwdis the local realization, so a transport branch would have two limbs with identical behaviour. The ssh test atserver/src/__tests__/plugin-orchestration-apis.test.tsasserts bothtoBe(localCwd)andnot.toContain("/remote/"), so it fails on either a regression toagentCwdor a partial one. - prior:d6aa930 important 2 — fixed —
server/src/services/plugin-host-services.ts:1822— guard is now!isClosedExecutionWorkspace(workspace), mode-independent, defined atpackages/shared/src/execution-workspace-guards.ts:16. KeepingisClosedIsolatedExecutionWorkspaceand having it delegate (execution-workspace-guards.ts:35) is the right call, and the doc comment at:23-29records why the narrowing is load-bearing rather than leaving it to be "cleaned up" later. Thecloud_sandbox+archivedcase is covered. - prior:d6aa930 important 3 — fixed —
packages/plugins/sdk/src/types.ts:469— reworded to provenance at both sites, andmodeadded attypes.ts:490(plusPluginWorkspaceModeat:407) so the isolation question is now answerable at all. The PLUGIN_SPEC §20 rewrite splits the two questions explicitly, and the "shared_workspace and operator_branch are issue-bound but not isolated" case — the one that made the old wording actively misleading — is named in both places. - prior:d6aa930 important 4 — fixed —
packages/plugins/sdk/src/testing.ts:1089-1105— thetruelimb is reachable from the double. See the Important finding below: reachable, but not yet faithful.
Critical Issues (0)
Important Issues (1)
- [tests / types]
packages/plugins/sdk/src/testing.ts:1093— The double reproduces two of the host's three rejection conditions, and the one it drops is the guard this PR just added.
The host rejects a bound workspace on three tests: wrong company, closed/archived (plugin-host-services.ts:1822), and no realizedcwd. The harness checksisInCompany(executionWorkspace, companyId) && executionWorkspace?.cwd— company andcwd, but nothing for closed. And it cannot:PluginExecutionWorkspaceMetadata(packages/plugins/sdk/src/types.ts:506-532) carries nostatusand noclosedAt, so a plugin author has no field with which to seed an archived workspace. Every workspace they seed is modelled as live.- The consequence is narrow but is exactly the contract this PR is establishing: a plugin author cannot write the test "my plugin falls back safely when the workspace was torn down" against the shipped double, and if they reason about that case from harness behaviour they will conclude it yields
isIssueScoped: truewith a path — which is what the host did before prior finding 2 was fixed. Production is not at risk (the host is correct, andfalseis the conservative direction); the risk is a plugin whose tests only ever observedtruefor a given seeded workspace meeting an unhandledfalsein production. - Smallest honest fix: add
closed?: boolean(orstatus/closedAtto mirror the row) toPluginExecutionWorkspaceMetadata, populate it in the mapper atplugin-host-services.ts:733alongsidemode, and mirror the guard attesting.ts:1093. If you would rather not widen the SDK surface again in this PR, the cheap alternative is a comment attesting.ts:1093stating that the double models every seeded execution workspace as live and that the closed-workspace fallback is not simulable — so the gap is documented rather than discovered.
- The consequence is narrow but is exactly the contract this PR is establishing: a plugin author cannot write the test "my plugin falls back safely when the workspace was torn down" against the shipped double, and if they reason about that case from harness behaviour they will conclude it yields
Suggestions (2)
- [types]
packages/plugins/sdk/src/types.ts:490— Themodejsdoc says "nullfor project-scoped results", but onlygetWorkspaceForIssue's fallback limb actually writesmode: null(plugin-host-services.ts:1890).getPrimaryWorkspace(:1752) andlistWorkspaces(:1729) leave itundefined, exactly as they leaveisIssueScopedundefined — which the prior review and yourgetPrimaryWorkspaceregression test both established as the deliberate, fail-safe shape. The isolation check itself is safe either way (undefined === "isolated_workspace"is false, same asnull), so this only bites a caller who testsmode === nullto mean "project-scoped". Worth saying "nullor absent" here, the way theisIssueScopeddoc at:469-482already distinguishes "always sets it" from "leave it undefined". - [code]
packages/plugins/sdk/src/testing.ts:1098— Same root cause as the Important above, lower stakes: the host labels the workspacesanitizeWorkspaceName(workspace.name, path)(plugin-host-services.ts:1842) — the execution workspace's ownnamecolumn — while the double usesbranchName ?? id, becausePluginExecutionWorkspaceMetadatahas nonamefield either. In your own fixture those coincide (name: slug,branchName: slug), so nothing diverges today, but a workspace whose name is not its branch gets a different label from the double than from the host. If you add fields to the metadata type for the closed guard,nameis worth carrying in the same change.
Answers to your three questions
1. The harness change — is the store consistent enough? Yes, and the failure mode you were worried about is not present. The lookup degrades cleanly at every step: no executionWorkspaceId on the issue skips the branch; an id present but unseeded gives executionWorkspaces.get() → undefined → isInCompany(undefined, …) → false → fallback. So the false limb is not "dependent on remembering not to seed one" — it is the default for any harness that does not seed, which is every existing test, and it cannot throw. You did not trade one gap for another. The real gap is the one above, which is about fidelity of the true limb rather than reachability of the false one.
2. Widening PluginWorkspace with mode — optional + null is the right shape, and a discriminated union on isIssueScoped would not work here. isIssueScoped is itself ?: boolean and is deliberately left undefined by the project-scoped readers, so it cannot discriminate: a union would force those readers to start asserting isIssueScoped: false, which is precisely the lie your getPrimaryWorkspace regression test exists to prevent. Keep it flat.
3. isClosedExecutionWorkspace naming — keep the two predicates; do not collapse them into { isolatedOnly }. The narrowing in the isolated variant is a semantic claim about one call site (the "move it to an open workspace" message), not a parameterisation of a general question, and an options bag would push that claim to the call site as a bare boolean where the reason for it is invisible. Two names with the doc comments you wrote at execution-workspace-guards.ts:23-29 make the wrong choice hard to make silently; isolatedOnly: true would make it easy. The footgun you are worried about is real, but the mitigation is the doc comment, and you already wrote it.
Strengths
- Reverting only
plugin-host-services.tsand reporting the specific failure message (expected '/remote/home/agent/ssh-slug' to be '/tmp/…') is the strongest available evidence that the ssh fixture is not vacuous — it proves the fixture metadata actually drivesderiveAgentCwddown the remote branch, which is the one thing a passing test could not tell you. - The mode-independent guard was extracted rather than inlined, and the isolated-only variant was left narrow with its rationale recorded. The tempting move was to widen the existing predicate and "fix" both callers; that would have silently changed the
routes/issues.tscomment-blocking path for shared and adapter-managed workspaces. - The two new tests for the previously-unreachable limbs (
cloud_sandbox+archived, andshared_workspacereportingisIssueScoped: true+mode: "shared_workspace") encode the distinction rather than the values — the shared-mode test would fail if someone later "simplified"isIssueScopedback into an isolation flag, which is the regression the doc rewrite is guarding against. - Taking the documentation suggestions as documentation, rather than reshaping the API to make the docs unnecessary, was the right call for a latent method with no in-tree caller.
Recommended Action
- Fix Critical issues before merge. (none)
- Address Important issues this cycle — the harness fidelity gap. It is a small change (one optional field, one mapper line, one guard) and it is worth doing in this PR rather than tracking, because the whole argument for the harness change was that plugin authors need to verify their branching. Shipping a double that cannot model the torn-down case leaves one third of the contract unverifiable, and the closed-workspace guard is the newest and least-obvious third. The documentation-only alternative is acceptable if you would rather not widen the SDK surface twice in one PR.
- Consider Suggestions opportunistically — the
modejsdoc wording is a one-line change and worth folding in while the surrounding docs are being written; the harnessnamedivergence is inert today.
Still latent with no in-tree caller, so nothing here is urgent in the "production is broken" sense — and, as last time, that is the argument for getting the contract right now rather than after something depends on it.
Thinking Path
Linked Issues or Issue Description
branchTemplatesilent no-op) and BLO-31340 (allowIssueOverrideparsed but never read) — same family: a workspace-routing input that is accepted and then not honoured.isPrimaryhonesty precedent this follows.31349,getWorkspaceForIssue, andisIssueScoped— no matches.What Changed
server/src/services/plugin-host-services.ts—getWorkspaceForIssuenow readsissue.executionWorkspaceId, loads theexecution_workspacesrow, and returns itsagentCwd ?? cwd,branchNameandid.repoRefis the checked-out branch,defaultRefis thebaseRefto diff against,isPrimary: false(an execution workspace is never the project primary).null— when no live workspace is bound, flaggedisIssueScoped: false. Returningnullwould push every caller into inventing its own fallback, and re-derivingeffectiveLocalFolderis exactly the defect being removed.isClosedIsolatedExecutionWorkspaceguard) and one with no realizedcwdyet (path: ""flagged issue-scoped would be worse than the base).packages/plugins/sdk/src/types.ts— new optionalisIssueScopedandbranchNameonPluginWorkspace. Optional to avoid a TypeScript breaking change for external plugin authors constructing literals, matching the existing back-compat treatment ofrepoUrl/repoRef/defaultRef.packages/plugins/sdk/src/testing.ts— the test double stampsisIssueScoped: false; it has no execution-workspace concept, and leaving the field undefined would let a plugin test pass while the plugin treats a base checkout as an issue-scoped working copy.doc/plugins/PLUGIN_SPEC.md§20 — documents the flag and tells plugin authors not to re-derive workspace paths from the project local folder. The JSDoc previously documented the buggy behaviour as the contract ("combinesissues.get()andgetPrimaryWorkspace()"), which is plausibly how this survived review; corrected too.Verification
Six new cases in
server/src/__tests__/plugin-orchestration-apis.test.ts, integration-style against embedded Postgres — real rows, realbuildHostServices, no mocks.The fix was proven load-bearing, not just asserted. Reverting only
plugin-host-services.tsto its pre-fix state (keeping the new tests) and re-running:The 2 that pass either way are the company-scoping check and the
getPrimaryWorkspaceregression guard — both are invariants that should hold before and after, which is what makes them a guard. The working tree was then restored and verified identical to the commit by tree hash, not by eye.Coverage maps to the acceptance criteria:
resolves each issue's own execution workspace…falls back to the project primary, flagged as not issue-scoped…resolves each issue's own execution workspace…(assertsalpha.path !== beta.path)BASE_CHECKOUTgetPrimaryWorkspaceleaves getPrimaryWorkspace pointing at the project base checkoutRisks
Low risk, and latent by construction. No plugin calls
getWorkspaceForIssuetoday — the only references are SDK plumbing (types,protocol,host-client-factory,worker-rpc-host,testing) plus the host implementation. So there is no in-tree caller whose behaviour changes.PluginWorkspaceliterals still compile.null— callers always get a usable path and can branch onisIssueScoped.getPrimaryWorkspaceandlistWorkspacesare untouched;effectiveLocalFolderis the correct answer there, and a regression guard pins it.Model Used
claude-opus-5[1m], 1M context), extended thinking, with tool use and code execution via Claude Code running as the Paperclip CTO agent.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template