mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 16:02:36 +01:00
5a5bfe689faba82f48e4114dfe82a048922bbdaf
6700
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5a5bfe689f |
fix(heartbeat): finalize an accepted native result after a graceful shutdown (#19591)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Native runner runs go through a durable coordinator row (`native_run_finalizations`) that holds the controller's execution lease, the accepted result, and the finalization phase > - A graceful server shutdown (SIGTERM) drains running native runs: it detaches the executor and hands the run to the next server boot > - When SIGTERM lands after `run.result.accepted` / `run.terminal` but before finalization, the drain did not release the execution lease, because it only updated coordinator rows without a result > - Startup restart recovery skips accepted results, and both the finalization reconciler and the orphan reaper wait for an unexpired coordinator lease (20 minutes for a local run). The run stayed `running` and the task stayed `in_progress` with no Retry > - This pull request makes the drain release the execution lease of an accepted result too, so the next boot finalizes the durable result > - The benefit is that a completed task finishes after a restart instead of stalling silently ## Linked Issues or Issue Description Refs #19525, #19530, #19513 **What happened?** A restart-chaos wave sent SIGTERM to the server 0.75 s after a native OpenCode run emitted `run.terminal` (result accepted, `runTerminalState: succeeded`). The graceful drain listed the run in `restartSuspendedRunIds`. On the next boot, startup recovery claimed only another run. The accepted run stayed `running` for more than 10 minutes. Its task stayed `in_progress` with `interruption.state=working`, and no Retry appeared. **Expected behavior** After a restart, a run whose result was accepted and whose terminal state is known is finalized as succeeded, and its task is completed. It must never stay silently `running`. **Root cause** `drainRunningRunsForShutdown` (`server/src/services/heartbeat/recovery.ts`) updated the coordinator only `where result_id is null`. For an accepted result, the update did nothing, so the dead controller's execution lease (`NATIVE_SESSION_EXECUTION_LEASE_TTL_MS`, 20 min) stayed valid. The detached executor does not finalize (by design, it hands off to the next controller). Then: - `claimNativeRestartRecoveries` only claims rows with `result_id is null`. - `reconcileNativeFinalizations` only selects rows whose lease is null or expired. - The orphan reaper skips a native run with pending workspace finalization or an unexpired controller lease, and defers to the lease deadline. The 5-minute stale threshold is not involved. **Steps to reproduce** See the new regression test: accept a result through `PaperclipControlPlanePort.completeRun` under a held 20-minute execution lease, run the SIGTERM drain, then run startup recovery on a new heartbeat service. ## What Changed - `drainRunningRunsForShutdown` now releases the coordinator execution lease (`leaseExpiresAt = now`) for an accepted result as well. It keeps `recoveryState` unchanged for an accepted result, because the run needs finalization, not a runner reattach. Rows without a result keep the old behavior (`awaiting_runner_reattach`). - A `native-finalizer:` lease on an accepted result is kept. That lease belongs to a finalization that is in progress in the draining process, and the shutdown waits for it. - The drain event for an accepted result now says that the result was released for finalization (`runnerDisposition: awaiting_result_finalization`). - New regression tests in `server/src/__tests__/heartbeat-native-restart-suspension.test.ts`: - An accepted terminal result under a held execution lease, then SIGTERM drain, then next-boot recovery: the run becomes `succeeded`, the coordinator becomes `committed`, the task becomes `done`, there is one result row and one run, and no provider turn starts. - A `native-finalizer:` lease survives the drain unchanged. ## Verification - Red before the fix: `cd server && pnpm exec vitest run src/__tests__/heartbeat-native-restart-suspension.test.ts -t "accepted terminal"` failed with `{ status: 'running', phase: 'workspace_finalizing' }` instead of `{ status: 'succeeded', phase: 'committed' }`. - Green after the fix: the same file passes 5/5. - Related suites pass: `src/services/heartbeat/recovery.test.ts`, `src/services/legacy-controller-lease.test.ts`, `src/services/native-runtime/native-runner-restart-recovery.integration.test.ts` (80 passed, 14 skipped), and `src/__tests__/heartbeat-process-recovery.test.ts` except one case. - The one failing case in `heartbeat-process-recovery.test.ts` ("dispatches interrupted CLI input and cleans up early fixture exits (true)") also fails on unchanged `origin/master`. It is not related to this change. - `server` typecheck (`tsc --noEmit`) passes. - Restart-chaos replay of the original cell: in progress. Results follow in a PR comment. ## Risks - Low to medium. The change only affects the graceful SIGTERM drain of native runs owned by this boot. - A rolling deploy can overlap the old and new servers. The old process detaches the executor before it releases the lease, so it does not finalize the result. A finalizer that already started keeps its `native-finalizer:` lease, so the new server cannot finalize the same result at the same time. - If a `native-finalizer:` lease is in progress and the process exits before it finishes, that run still waits for the 5-minute finalizer lease. This was already the behavior and is out of scope. - No schema, API, or UI change. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edit, test runs). Extended reasoning was used. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1c9767d323 |
test(workspaces): remove archive fixture open-order assumptions (#19585)
Select held archive reads by source path and observe replacement before releasing sibling reads. Validation: red reordered-open reproduction, all 102 archive tests and all applicable CI pass. Greptile scored the exact head 5/5, and no review threads remain unresolved. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
50038fac70 |
fix(native-runtime): contain backup verification deadline aborts (#19556)
Reject an already-aborted backup verification before constructing its file stream so cancellation does not raise an uncaught AbortError. Validation: 60 focused tests and all applicable CI pass. Greptile scored the exact head 5/5, and no review threads remain unresolved. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
91b641757b |
fix(sentry): keep low-trust project denials on the task (#19531)
Keep exact pre-dispatch project access denials on their task instead of the unexpected Sentry failure stream. Validation: 301 focused tests and all applicable CI pass. Greptile scored the exact head 5/5, and no review threads remain unresolved. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
24508a8ba5 |
perf(dev): add agent preflight with a shared content-keyed build cache (#19555)
perf(dev): add agent:preflight with a shared content-keyed build cache Agent worktrees each built the Runner from nothing: a cold cargo debug and release build, the Runner TypeScript build and its checks, about 8 minutes per fresh worktree on a loaded machine. `pnpm agent:preflight` replaces the manual install + Runner build sequence. It skips steps whose inputs are unchanged and reuses other worktrees' results from ~/.cache/paperclip/agent-build: - Runner and eval-kernel dist, keyed by the Runner build-input digest and the toolchain - Runner debug binaries, keyed by every file the Rust build reads - a cargo seed with third-party crates only; the Runner crate is scrubbed because cargo records its out-of-package includes by absolute path Entries are written once with an atomic rename and restored as APFS clones. Also exclude provider-assets from the Runner build inputs. The build writes that directory itself, so the first build in every fresh checkout saw its inputs change and never recorded a build stamp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
17901d74ba |
Fix onboarding account changes after returning from Review (#19557)
Save changed onboarding connections on the existing agent after validation. Select the chosen personal provider account and serialize default changes so an abandoned save cannot override a later choice after Back. Preserve runner, environment, and unrelated agent settings. Reject unsupported managed-account to legacy-secret switches with an actionable Connect error. Verified with 327 component tests, three backend credential-selection tests, nine affected browser tests, typecheck, build, token gates, all CI jobs, and Greptile 5/5 with no unresolved comments. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
79c724b292 |
ci: run the Runner support suite as a required PR check (#19537)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The Runner support suite (`pnpm test:e2e:runner:unit`) checks the code behind paid Runner runs: workflow security policy, evidence oracles, cost receipts and controller recovery. > - Only a manual Docker Runner check ran this suite. PR CI did not run it. > - So changes on master broke 16 of its tests, and nobody saw it. #19527 and #19529 fix those tests. > - This pull request runs the suite in PR CI and makes the `verify` check require it. > - The benefit is that a change that breaks the suite fails on its own pull request, before merge. ## Linked Issues or Issue Description Refs #19527 and #19529 (both merged; they repair the suite). Also accounts for #19533, which added the sandbox provider job. **Merge order:** #19527 and #19529 have merged, so the suite now passes on master. This PR can merge. **What existing behavior does this improve?** PR CI for `pr-trusted.yml`. **Current behavior** `pnpm test:e2e:runner:unit` runs only in `docker-runner-check.yml`, by manual dispatch. Master changes broke 12–13 tests by Nov 3 and 16 by Nov 6, with no signal on any pull request. **Proposed behavior** A required "Runner support suite" job runs the suite on pull requests. **Reason and benefit** Drift fails on the pull request that causes it. Authors update deliberate pins (profile versions, suite hashes, workflow policy) in the same pull request. ## What Changed - `pr-trusted.yml`: new job `verify_runner_e2e_support` ("Runner support suite"). It uses the same steps as other trusted jobs: checkout without credentials, setup-node directly before pnpm, the npm overrides, the shared `setup-ci-dependencies` restore, and CI profiling. Then it runs `pnpm test:e2e:runner:unit`. It has no secrets. Timeout: 20 minutes. - `pr-trusted.yml`, job `verify`: the job now needs the new job and checks its result. A failed, cancelled or wrongly skipped run fails `verify`. - `turbo.json`: new routing task `ci:runner-e2e-support`. Its inputs are `server/**`, `cli/**`, `packages/**` and `ui/**`. Changes to `tests/**`, `scripts/**` and `.github/**` already force full CI, so they also run the job. - `.github/ci-task-matrix.json`: one lane for the new job. - Tests: `ci-plan.test.mjs`, `ci-profile.test.mjs`, `e2e-shard.test.mjs` and `workflow-security.test.ts` now include the new job. A UI component change now selects this one Runner task. The suite imports UI modules and checks that catalog-listed UI files stay tracked in git, so UI changes can break it. - CI profiling: when `PAPERCLIP_CI_PROFILE` is on, the suite's Vitest config adds `scripts/ci-profile/vitest-reporter.mjs`, and `run-unit.mjs` runs the Node tests through `scripts/ci-profile/node-test.mjs`. So the job uploads per-test timings, like the root and Runner package suites. The launcher loads the profiling helper only after its "Vitest is not installed" check. - `tests/runner-e2e/README.md`: says that PR CI runs the suite, and that deliberate pin changes go in the same pull request. ## Verification - `node --test .github/scripts/tests/*.test.mjs scripts/ci/*.test.mjs scripts/__tests__/e2e-shard.test.mjs scripts/__tests__/release-verify-workflow.test.mjs scripts/__tests__/runner-release-crate-cache.test.mjs`: 830 passed, 3 failed. The same 3 fail on the base branch without this change. They need local tools that are not on this Mac. - A local run with `PAPERCLIP_CI_PROFILE=true` wrote per-test and per-file timing records for Vitest and for Node. Without profiling, output and exit codes do not change. Both `unit-launcher.test.mjs` tests pass. - The aggregate test now covers the new job: `verify` rejects it when it fails, is cancelled, or is skipped while selected. - The pnpm bootstrap policy tests in `workflow-security.test.ts` pass with 10 setups in `pr-trusted.yml`. - On this PR's merged tree (`80b526d697`, master plus this PR): `pnpm test:e2e:runner:unit` passed, Vitest 320 files (6,830 tests) and node:test 373, in 58s. It also passes on master `4c1f629c31`. - Suite runtime on a 10-core Mac: 43–63s. With `--maxWorkers=4`: Vitest 118s, about 490s of CPU in total. On a 4-vCPU GitHub runner I expect about 3–4 minutes for the tests and 1–2 minutes for setup. The job runs in parallel with the longer test shards, so it should not lengthen CI. - `pr.yml` calls `pr-trusted.yml@master`. This PR's own CI does not run the new job. The first real CI timing comes from the first pull request after merge. ## Risks - More runner minutes: one extra job on most pull requests (all except documentation-only ones). - The suite has not run on Linux CI recently: old `docker-runner-check` logs have expired. A Linux-only failure would show up on the first pull requests after merge. The job can be removed from the `verify` list quickly if needed. - #19229 removes `version: 9.15.4` from the pnpm steps in this file. Whichever PR merges second needs a small rebase. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), by Anthropic, in Claude Code with tool use (shell, file edits, git, GitHub CLI). ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e8f07b2a39 |
test(restart-chaos): fire faults relative to user actions and run state, add K6 crash loop (#19513)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The control plane must keep tasks moving when the server restarts > - The restart chaos harness (#19477) kills the server only at a random time after a task starts > - Many restart bugs depend on what the user or the run was doing at that moment: a comment just sent, a Retry just clicked, a run still queued, a long tool call, or a second crash during startup recovery > - The harness cannot aim at these moments yet, so these cases occur only by chance > - This pull request adds fault triggers that fire relative to a user action or a run state, and the K6 crash loop > - The benefit is that chaos waves can target these boundaries on purpose and replay them ## Linked Issues or Issue Description Refs #19477 **What is the problem?** The restart chaos harness fires each fault at a seeded random offset from task launch. It cannot fire a fault right after the harness posts a comment to an active run, right after a Retry click, while a run is queued, during a long tool call, or a second time while the server is in the `recovering` startup phase. **Why is it a problem?** These are the boundaries where restart bugs hide (plan faults K6 and K8, boundaries B1, B6 and B8). Random timing seldom hits them, and a replay cannot repeat them. **What is the proposed solution?** Add a `--triggers` option. Each task samples a trigger with its cell. The observer arms the fault when the trigger condition holds, then fires it after a seeded delay. Add the K6 crash-loop fault to the supervisor. ## What Changed - `types.ts`, `config.ts`: add `TRIGGER_IDS` (`time`, `comment`, `retry`, `queued`, `tool`), the `--triggers` option, and the `K6` fault kind. - `scheduler.ts`: sample a trigger per task. `comment` pairs only with C1 and `tool` only with B5. Each trigger has a seeded delay range. `retry` adds an extra K1 after the planned fault. A replay re-arms the task's own triggered faults. - `observer.ts`: arm each triggered fault from the observed state: the follow-up reached a running run, an accepted Retry, the first run still queued, or the first `bench.sh` progress. A trigger that can no longer hold is recorded as `triggerMissed`. - `supervisor.ts`: K6 kills the replacement again as soon as `/api/ready` reports `recovering` (or `ready`, if recovery was too fast to see), then once more just after `ready`. The receipt records the phase that each kill hit. - `main.ts`, `ledger.ts`: pass the trigger to the fault receipt, the timeline and the ledger. The summary shows the trigger in each cell. - `triggers.test.ts`: unit tests for planning, firing, misses, replay and the crash-loop phase rule. - `README.md`: document the triggers and K6. ## Verification - `pnpm exec vitest run --config tests/restart-chaos/vitest.config.ts` (52 tests pass). - `node scripts/typecheck-test-projects.mjs tests/restart-chaos` passes. - Live: `node cli/node_modules/tsx/dist/cli.mjs tests/restart-chaos/main.ts --env local --adapters codex,claude,opencode --faults K1,K6 --triggers comment,retry,queued,tool --concurrency 3 --tasks 6` runs waves with the new triggers. ## Risks - Low risk. The change is in the test harness only. The default trigger is `time`, so existing waves and replays keep their behavior. - A trigger can miss its window (for example, a run starts before the first poll). The ledger records the miss, so the task is not counted as a crossing of that boundary. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with tool use (shell, file edits). ## 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 - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
124b2cd0ad |
fix(server): certify background deadlines for idle tenants (#19001)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Background services own future checks, retries, and recovery work. > - An idle tenant can stop only when a durable wake covers its remaining duties. > - The current alarm catalog covers several domains but leaves these owners blocked. > - A saved future timestamp alone cannot prove that reconstruction or admitted work has settled. > - This pull request binds durable domain snapshots to the actual registered worker revisions. > - The tenant can hand off supported future duties and retain all checks for uncertain or active work. ## Linked Issues or Issue Description No matching public issue was found. Related work: [domain deadline handoff #18656](https://github.com/paperclipai/paperclip/pull/18656), [owned blocker diagnostics #18632](https://github.com/paperclipai/paperclip/pull/18632), and [completed history proof #18646](https://github.com/paperclipai/paperclip/pull/18646). The separate [native idle-session change #18988](https://github.com/paperclipai/paperclip/pull/18988) covers runtime retirement. [Far-future retry re-probes #13534](https://github.com/paperclipai/paperclip/pull/13534) changes recovery responsiveness after an upstream success; this change preserves existing retry decisions. **What happened?** Future status-card, external-object, skill-sync, watchdog, retry, and quota duties lacked complete owner-bound coverage in the idle deadline proof. A completed quota task could also retain its monitor policy and timestamp and remain blocked after the scheduler had disarmed it. **Expected behavior** A settled registered owner can hand off its exact supported future deadline. Active work, dirty reconstruction, transaction uncertainty, changed revisions, claims, unknown state, and unsupported duties keep the tenant awake. Completed history retains its data and stops recurring work when the owner has no remaining duty. **Steps to reproduce** 1. Produce a supported future duty through its normal service. 2. Let the registered worker finish reconstruction and admitted work. 3. Request an owned idle report with the negotiated domain deadline catalog. 4. Check the saved earliest deadline and generation before durable alarm registration. 5. Stop and reconstruct the worker. Check that ordinary admission and claims resume useful work once due. 6. Complete a quota task. Keep its saved policy and timestamp. Check that linked unfinished work still blocks sleep and that settled history does not. **Paperclip version or commit** The owned patch was qualified at `699c8569fa34338dd75cdc5731381ec1e2efdb45`. Publication head `491fac368ee72eaac8ae105d53e6bf6b305d8a6a` normally merges actual master `039e0dad2c51c80251e1302a19c5566e039c357a`. All 43 owned files are byte-identical. All 29 new upstream paths are preserved. The later normal current-master integration is `8c912e1ba4fc66397a7341ade59373af8075068f`, described separately below. **Deployment mode** Source checkout with real PostgreSQL 17 and a controlled local Node process. No deployed alarm, tenant suspension, database suspension, or fleet result is claimed. ## What Changed - Add registered-worker reconciliation revisions and invalidate them on stop. - Keep status-card, external-object, and skill deadline interpretation with their owners. - Reuse the watchdog classifier against the supplied database snapshot. - Validate the exact saved retry run and paired queued wake. - Validate legacy quota source bindings and the current assignee or reviewer policy. - Preserve common issue scope, dependency, sibling, and state checks for covered waits. - Compose normalized owner snapshots within the existing bounded deadline proof and recheck revisions after inspection. - Bind actual service and worker instances from server startup. Missing bindings preserve existing blockers. - Retain completed quota policy and timestamps while independently rejecting linked unfinished runs or wakes. - Block unconsumed budget evaluation inputs through the owning queue predicate. Accounting policy and settlement stay unchanged. - Bind completed quota history through fixed-size UUID-array parameters, preserve exact company/task/source associations, and guard external references before the run-ID join. - Batch watchdog subtrees and shared classifier facts without changing the 64-query or two-second proof budget; preserve exact company pairing for first-run evidence. - Extend production-path regression controls and document supported coverage and limits. ## Verification Current integration head: `688abc6cac6016b9fb50e5832f81e2975ed5d49f`. Fresh exact-head checks completed with **58 successes, three intentional skips and one neutral review**, including aggregate `ci / verify`. The complete current Greptile review is **5/5**. The complete current Ponytail verdict is **Ship (4/5)**, with only optional cleanup of an unused MCP proof input; both prior correctness findings are resolved. All review threads and nested pages were inspected. The normal master merges preserve the registered retry deadline owner and current startup completion log after the same readiness await. The earlier focused evidence below remains tied to its tested source revisions. Current-master integration `8c912e1ba4fc66397a7341ade59373af8075068f`: - Normally merge actual master `46fb933b0155d183ce64a8bdc4950d1b58c926f6`. Every nonoverlapping tree blob retains its owning parent across 14,044 paths. All 42 owner-only files and 15 critical binding/proof/readiness files remain byte-identical. Three automatic overlaps preserve the existing watchdog binding, conservative unavailable-reader fallback and optional actual owner arguments. The two scoped-deadline conflicts retain the entire upstream resource-reservation/mid-pass admission/abort algorithm inside the existing reconciliation revision/running/failure fence, with both parent test sets. - Actual TypeScript 7.0.2 server noEmit passed in 9.434 seconds, normal source declaration layers in 12.102 seconds, and four selected test/helper/config layers extending the actual CI configuration in 3.167 seconds. No compiler option weakening, heap override, donor declarations or SDK replacement stub. - Focused PG0 controls passed: 102 scheduler/proof/owner controls in 16.451 seconds, including all 21 scoped-deadline cases; 121 startup controls; and all 107 unchanged instance-settings route cases in 6.456 seconds. The latter first hit a local prewire-guard listener-registration race. Only the ignored guard was corrected to register the same actual OS-bound server port immediately after native listen returns. Closed, unknown, foreign, offhost and PG destinations remain blocked. Original failure evidence is retained; product code and assertions were unchanged. - The exact current UI policy command now reports zero findings (0.362 seconds); its seven scanner controls pass. The earlier dependent PR used an old scanner/source baseline under a newly enforcing trusted workflow and reported 397 findings. Carrying the genuine current master resolves that mismatch without editing unrelated UI sites or relaxing the gate. - Every meaningful worker checked its executing PID/thread/preload hash and matching receipt. PG0 controls opened no PostgreSQL sockets; all owned processes exited. Original dependency-cache/DNS, ignored-harness syntax/type and guard failures remain separate from product results. - This is fresh semantic and focused runtime qualification, not a local whole-repository or current-head PostgreSQL acceptance rerun. The upstream workspace-busy parked-retry/terminal-holder notification path remains unchanged by this merge and needs its separate current-graph producer check. Historical six-case/scale results below stay tied to their measured sources. A matching controlled default-cadence follow-up is in #19191; no fresh current-head two-cycle or deployed suspension claim follows from this merge. - All 59 exact-head check contexts now pass (55 success, one neutral, three intentional skips), plus the legacy security status. The complete edited Greptile review is 5/5 for this exact commit, with all root/nested pages inspected and no unresolved threads. Ponytail stopped without completing its new turn; it supplied no new verdict or finding. Prior actual findings remain addressed. Earlier scores/checks below belong to their explicit earlier commits. On frozen clean merged source `699c8569fa`: - Real PostgreSQL acceptance: **6/6 passed**,83.483 seconds wall. Actual retry producer creates the exact saved run/wake; stopped and reconstructed owner resumes through current admission, one durable start, real local process output and normal task completion. Includes cancellation, drain arrival, retained quota history with queued-run/wake negatives, watchdog review settlement and budget-input empty/pending/consumed control using the actual owner. - Affected coordinator, deadline, scoped-deadline, recovery and composed-proof controls: **78/78 in five files passed**,32.114 seconds wall, worker 1/no file parallelism. - `pnpm exec tsc --noEmit -p server/tsconfig.json`: **passed**,17.467 seconds. Native TypeScript 7.0.2 and Node 24.22.2. - PostgreSQL tests use one dedicated test database, matching fixture/admin URLs, pool limit four, one worker, and no embedded fallback. Every run recorded unchanged source hashes and a clean checkout. - The eight-second real-clock retry is a domain execution check. It is below the Cloud minimum wake lead and is not a positive accepted hosted sleep cycle. - The completed quota-history product failure and original fixture/protocol failures were preserved; the owning-reader fix passed a real PostgreSQL red/green regression before the fresh merged six-case pass. - After runtime completion, the dedicated database had zero other clients and no owned runtime processes. All three qualification process PIDs exited. The coordinator independently audits and drops the database. - Comment-only cleanup has equal strict TypeScript parser ASTs across 15 files. The final lexical audit finds no added comments relative to actual master. `git diff --check` passed. - Earlier retry fixtures omitted responsible-user membership, advanced JavaScript time past the real PostgreSQL lease clock, or produced stdout without normal task completion. Corrected fixtures use real membership, an eight-second real deadline, and ordinary task comment/completion. The last case exposed legitimate disposition repair rather than duplicate retry promotion. - The quota and watchdog controls first ran before admitted work or the existing 30-second reconstruction duty settled. The stronger tests require an interim blocked report and then exact settled-owner proof. Production deadlines were not changed. The separate completed-quota-history defect was fixed in its reader and verified with linked unfinished-run and wake controls. - The status/object and skill owners reviewed the actual final startup, route and post-backup bindings. Their domain source evidence carries where token-equivalent. The master coordinator coalescing change was reviewed separately. Historical domain PostgreSQL results are not presented as fresh full-suite results on this head. The later master merge adds retry predecessor cleanup checks under the existing issue claim lock. The source audit confirms unchanged saved deadlines and run/wake identities. The three actual retry lifecycle/cancellation/drain cases passed on the batched candidate with this parent (31.817 seconds). Those local dependency controls preceded the current exact-head remote run. Native backup preservation and TestOps changes from master are retained. No pass is inferred for changed dependency behavior solely from unchanged owned files. Review repair published as `643f1e7400`: - The original reader returned `unknown` after seven actual watchdog reviews settled across seven companies. The unchanged composed-report regression passed after batching; the final reader passed in 61.841 seconds, including active queued-work and stopped-owner rejection. - A separate two-company red/green control rejects terminal-run context from another company as first-run grace evidence. Shared facts are serialized once, with per-root IDs and review summaries. - The affected recovery, proof and diagnostic unit controls passed 47/47 (14.575 seconds). Startup owner-contract fixtures passed 121/121 (10.589 seconds). - Diagnostic PostgreSQL controls passed7/7 (12.406 seconds), including the normal budget-input producer and its exact blocker reason. Initial empty-state setup uses the same cleanup as per-case teardown when a dedicated test database is reused. - Actual repository `pnpm exec tsc --noEmit -p server/tsconfig.json` passed 12.710 seconds with no heap override. Final source has no added code comments; whitespace checks pass. - The original query-budget failure, two-company discriminator, initial cross-suite fixture failure and an interrupted incorrect local-driver selector are retained separately. The interrupted run is not a pass. Independent cleanup found zero owned clients or runtime processes. Quota-scale repair published as `6696a37f8a`: - The exact 10,000-row completed-history case reproduced `MAX_PARAMETERS_EXCEEDED`:90,002 bound parameters exceeded the driver's 65,534 limit, and the composed report returned `unknown` despite settled owners. Original failure and actual driver diagnostics are retained. - Fixed pending-work queries use three typed UUID-array parameters plus the result limit, keeping exact company/issue/source tuple bindings. The primary run-ID column stays uncast; a guarded CASE validates the raw external reference before conversion. The 10,000-row cap and 64-query/two-second proof budget are unchanged. - The same 10,000-row composed-report case passed 18.860 seconds, with unfinished run/wake rejection, malformed-reference safety and conservative 10,001-row overflow. Its fixture derives retained history from the actual quota producer and normal task completion, expands valid persisted issue/source pairs, then starts the actual registered owners. - The existing actual quota producer case passed 14.187 seconds, including exact future deadline, retained completion history, unfinished work, real foreign-company source references and noncanonical/malformed references. - Affected owner/proof controls passed 22/22 (26.444 seconds). Production server noEmit passed 16.039 seconds. - CI on 643 identified TS7034/TS7005 in the settled-watchdog fixture's initially untyped collection. It is now explicitly typed. A separate affected-entry semantic check extending the repository's actual `server/src/__tests__/tsconfig.json` passed 15.093 seconds: six affected tests, two owner files, ambient declarations and current imported source, with unchanged strict/module/lib/path settings. The production server config excludes that test directory and is not presented as test-layer proof. No custom suppression or replacement SDK stub was added. - All runs were sequential with pool max 4, worker 1, source/config/command hashes and cleared inherited provider/LIVE/other-database variables. Cleanup found zero owned database clients or local runtime processes. At prior head `6696a37f8a5718cd0f79054969a0084462d41d47`, all required CI gates pass. The exact-head inventory has 55 successes, one neutral review and three intentional skips. Greptile is 5/5. The complete latest Ponytail verdict confirms the quota finding is fixed; its only remaining note is optional unused MCP proof state, not a behavior defect. All three review threads are resolved. The earlier 643 result remains historical. The repository-wide typecheck, tests, build and browser gates passed in the prior `6696a37f8a5718cd0f79054969a0084462d41d47` CI run. The new `8c912e1ba4fc66397a7341ade59373af8075068f` full CI also passes. They were not run as a full local workspace command in the focused PostgreSQL lease. Local evidence above uses bounded external databases and does not claim embedded-cluster, live-provider or deployed-alarm qualification. ## Risks - An unsupported or uncertain owner state must remain blocked. An optimistic exemption could lose work while compute is stopped. - Changes during reconstruction or inspection must invalidate the proof. A memory-only timer is not durable alarm coverage. - Current authorization, budgets, cancellation, accounting, and resource ownership still apply on dispatch. The proof does not grant execution authority. - The supported writer model uses in-process transaction signals and startup/resume reconstruction. Independent SQL writers and other replicas need an explicit wake contract. - Native, review, coalesced, and taskless retry variants remain conservative until their owner cases qualify. Reviewer quota and hosted alarm qualification remain open. - The shared catalog remains opt-in. Legacy callers and missing owner bindings retain their old blockers. ## Model Used OpenAI `gpt-6.1-sol` was the configured model for the delegated service implementations and reviews. OpenAI Codex, based on GPT-6, performed managing review and integration. Both used repository tools and code execution. The exact served backend snapshots and context-window limits were not exposed. ## 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 and contains no internal Paperclip ticket id - [x] I have run tests locally and they pass on the final merged head - [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 — exact current-head run passes - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups — complete current-head review and all paginated threads inspected - [ ] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
694076245f |
fix(status-cards): inline query guidance in the compile task instead of an unprovisioned skill (#19554)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Status cards let the board watch a slice of company work with a
prose prompt
> - A new card starts a compile task. The summarizer agent compiles the
prompt into search queries and writes the first summary.
> - The compile task told the agent to use the bundled
`status-card-query` skill. Nothing gives that skill to the built-in
Summarizer or to a per-card override agent.
> - The agent saw no assigned skills, got `runner_skill_not_assigned`,
and then searched the filesystem until the run deadline. It never wrote
the card query.
> - This pull request puts the query guidance directly in the compile
task, so the task names only things the run has.
> - The benefit is that any summarizer agent, on any model, can compile
and save the card query without a missing dependency.
## Linked Issues or Issue Description
**What happened?**
Creating a status card with a summarizer on a smaller model (for example
an OpenCode agent on a DeepSeek flash model) never finished. The compile
run reported no assigned skills, then spent its full budget looking for
`status-card-query`. No `PUT /api/status-cards/{id}/query` request was
sent, and the run hit its deadline. Stronger models sometimes worked
around the gap.
**Expected behavior**
The compile task contains everything the run needs. The agent writes the
query, reads the compiled scope, and writes the first summary.
**Steps to reproduce**
1. Enable status cards and configure a summarizer agent that does not
have the `status-card-query` skill (the default Summarizer only installs
`summarize-status`).
2. Create a status card.
3. Watch the compile run: it looks for the skill and does not write the
query.
## What Changed
- `server/src/services/status-cards.ts`: the compile task no longer says
"Use the bundled `status-card-query` skill". It now inlines the query
format (fields, allowed status and priority values from shared
constants, limits), the project and label lookup endpoints, an example
query body, and an explicit `GET /api/status-cards/{id}/dry-run` step
before the summary write.
- The example body uses a `text` fence, so the existing parser still
reads the `json` generation payload.
- New `server/src/__tests__/status-card-compile-prompt.test.ts`: asserts
that the compile task names no skill, contains the full query format and
write-back sequence, has an example that passes
`companySearchQuerySchema`, and keeps a parseable generation payload.
## Verification
- `cd server && npx vitest run
src/__tests__/status-card-compile-prompt.test.ts
src/__tests__/status-cards.test.ts
src/__tests__/status-card-evaluation-work.test.ts`: 3 files, 74 tests
pass.
- `npx tsc --noEmit -p server`: no errors in the changed files.
- `pnpm verify` selected 2224 test files. Most failed in this fresh
worktree because `@paperclipai/plugin-sdk` was not built yet (`Failed to
resolve entry for package "@paperclipai/plugin-sdk"`). After a build of
that package, the status-card files above pass. Required CI supplies the
broad coverage.
## Risks
- Low risk. The change is prompt text only. The write-back routes,
validators, and authorization do not change.
- The compile prompt is longer. The added text is a small, bounded
block.
- The `status-card-query` catalog skill still exists for agents that
author cards. The two texts can drift. The new test pins the prompt
contract.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), via Claude Code with tool use and
code 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
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
1391f64551 |
Add gated release/resume methods to plugin Runner owners (#19584)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Core owns Runner tasks through a lease-bound plugin owner. > - The SDK already defines release and resume in #19468. > - The owner still expects an accepted result for both operations, so it cannot use those SDK results. > - This pull request adds both methods behind the existing capability, warm-task and account checks. > - It uses the SDK parser to verify the result and task ID, and preserves the owner's cancellation checks. > - This prepares the owner boundary for later lifecycle integration. It does not start automatic release or resume. ## Linked Issues or Issue Description **Problem or motivation** The plugin Runner owner cannot forward the existing SDK release/resume pair. Its local result mapping rejects their valid results. Later lifecycle code needs both operations on the same checked task owner. **Proposed solution** Pass the existing description capability to the owner. Add explicit release and resume methods and use the SDK's operation-result parser. Require warm-task support and a selected account. Reject calls and late receipts after owner cancellation or detachment. **Alternatives considered** Adding automatic release here would also require connection quieting, settled-event drain, pending-ticket retry ownership and safe disposal. Keep those changes together in the next integration. Do not add a new flag, schema, timer or lifecycle mode here. Related: #19468. I checked ROADMAP.md and found no duplicate planned item. An open-PR title search for release/resume found no overlapping pull request. ## What Changed - Add the capability-gated owner facade for Codex and ACPX. Resume returns only `resumed` or `unavailable`. - Use the SDK parser for exact task-ID and operation-result validation. - Pass `releaseResumeVersion` from the checked description and test that it does not cause an automatic operation. - Cover account-preserving owner rebind, missing prerequisites, malformed results, ambiguous errors, cancellation and late replies. - Document the caller's remaining settlement, connection, ticket and attachment duties. ## Verification Passed locally on Node 26.12.0 with the pinned workspace dependencies: - In `server/`: `pnpm exec vitest run src/services/native-runtime/plugin-runner-task.test.ts` — 74 tests passed. - In `server/`: `pnpm exec vitest run src/services/native-runtime/native-session-executor.test.ts -t 'uses a task-only'` — 3 cases passed; 1,160 unrelated cases were not selected. - Built the real workspace SDK dependencies and Runner TypeScript. A focused strict TypeScript program for the owner and its tests passed. - `git diff --check` and the added-line secret/PII scan passed. The first root test command selected a nonexistent Vitest project name and ran no tests. Running the test from the server package fixed the command. The first focused TypeScript setup could not find Node types; pointing it to the package's installed type definitions fixed that setup. The full regression suite and release checks run in GitHub. ## Risks The facade has no automatic caller. Existing warm timeout, bootstrap, outbound connection and pool behavior stay in place. No plugin capability is advertised or task control channel enabled by this pull request. Future integration must serialize release with new work and disposal. It must quiet the owned connection, keep the exact pending ticket after an ambiguous reply, authenticate the source again and finish ordinary run attachment before new work. This change does not qualify provider parking, cold recovery, retention policy or deployment. ## Model Used OpenAI GPT-6.1 Sol, exact model ID `gpt-6.1-sol`, High reasoning. Used code execution and targeted tests. The context-window size was not exposed. ## 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> |
||
|
|
bc4cad3ac8 |
fix(environments): report an undescribable plugin Runner as a failed check (#19172)
Return a retryable environment check when a plugin Runner cannot be described. Preserve stable Runner revision mismatch checks and preconfigured Runner-home handling. Validation: 108 focused tests pass. All 53 applicable checks pass, with three intentional skips. Greptile scored the exact head 5/5 and no review threads remain unresolved. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
4c1f629c31 |
test(evals): repair the Runner support suite after master drift (#19529)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The Runner support suite (`pnpm test:e2e:runner:unit`) tests the code behind paid Runner qualification: workflow security policy, evidence oracles, cost receipts and controller recovery. > - PR CI does not run this suite. Only a manual `docker-runner-check.yml` run does. > - So deliberate changes on master broke its tests, and nobody saw it. There were 12–13 failures on Nov 3 and 16 on Nov 6. > - This pull request finds the master change behind each failure and fixes the side that is wrong. No security check becomes weaker. > - The benefit is that the suite passes on master again (with #19527), so it can become a required check. ## Linked Issues or Issue Description Refs #18516, #18625, #18983, #18325, #19265, #18994, #17009, #19195. Companion PR: #19527 (the one workflow fix). **What happened?** At master `543aa4d331`, `pnpm test:e2e:runner:unit` had 16 failures in 8 files. At `98ebeefbc9` I saw 15 in 7 files. The two embedded-Postgres files passed in every run I made, and the 256MiB packaging test timed out once. **Expected behavior** The suite passes on master. Each test still checks the rule it was written for. **Steps to reproduce** Run `pnpm install`, then `pnpm test:e2e:runner:unit` on master. ## What Changed Each failure, its cause, and the fix: - **`workflow-security.test.ts`: chat catalog workflows hold provider keys and use `runner-e2e-private-paid`.** This is a deliberate policy. #18516 admitted guarded, pinned-source chat TestOps cells, with special checks. #18625 removed the pilot workflows and their checks. It did not move the checks to the full-catalog workflows from #18542 and #18647, which use the same trust model. The fix adds `chat-catalog-testops.yml` and `chat-catalog-testops-case.yml` to the provider-key allowlist. It also brings back the #18516 checks, adapted to the caller plus reusable-workflow layout: - The cell is `workflow_call` only. Its single caller is the master-only dispatcher. - The trusted guard runs before the pinned candidate checkout. `verify-selection` runs before each provider key. - The paid job never sees `ALLURE_TOKEN`. The report job runs in `runner-e2e-private-history` and has no candidate checkout. The upload job has no private environment and no secret except `ALLURE_TOKEN`. - Provider keys appear only in the two caller jobs. Those jobs need `authorize, select, build` and pass the guard's `target_sha`. - I mutation-tested this: an `ALLURE_TOKEN` in the paid job fails, and a second caller workflow fails. - **`workflow-security.test.ts`: `ref: ${{ github.sha }}` 6 vs 4, `persist-credentials: false` 12 vs 10.** #18983 added two trusted TestOps jobs (`testops_selection`, `publish_testops`). Both check out reviewed code next to the TestOps token. This is the safe direction. The counts are updated. A new check makes sure both jobs check out only `github.sha`, do not install the candidate lockfile, and use no secret except `ALLURE_TOKEN`. - **`pi-file-evidence.test.ts` (4 tests): "one observed provider validation execution".** #19265 deliberately changed the ACPX tool normalizer to keep the latest concrete title. Pi's shell call now goes from `bash` to the exact command. The ACPX sidecar uses this normalizer, so the oracle would also reject real Pi runs. The oracle now accepts one change only, from `bash` to the exact validation command, with no change back. It also counts every completed `execute` call, whatever its name. This is stricter than before: a refined title cannot hide a second shell command. New mutations cover a regressed name, a different command, and an extra call that has its own command name. The retained 44-row real stream still passes. - **`pi-controls-evidence.test.ts`: Pi profile 37.** #18325 deliberately reset Pi to profile 37. The pin and the `pi-native` suite hash are updated. I checked that the old hash comes back exactly when the version is set to 36. So the version is the only change. - **`controller-outage.test.ts`: 1 continuity cell, not 2.** #18994 deliberately limited this lane to providers with managed warm sessions. The test now expects exactly `runner-codex`. - **`operating-receipts.test.ts` (3 tests): "Operating overhead allowance needs review".** This is a time bomb from #17009. The fixture allowance was valid from Oct 1 until Nov 1, 2026. `admitAttempt` reads the real clock. The allowance window is limited to 31 days, so a longer fixture is not possible. These tests now pin only `Date` to the fixture time. Timers and the filesystem stay real. I ran the full suite with the clock moved to Dec 15, 2026, and found no other date-bound failures. - **New file `private-trust-workflow-security.test.ts`.** The private archive trust test, the chat catalog checks and the full-stack TestOps job checks moved here from `workflow-security.test.ts`. That file would have crossed 1,000 lines. It is now 970 lines. The checks did not change. - **`remote-native-bootstrap.test.ts`: cannot load `@paperclipai/plugin-sdk`.** Since #19195, server code under test imports the built plugin SDK. `run-unit.mjs` now runs `scripts/ensure-plugin-build-deps.mjs` first, as `test:run:general` already does. This step is cached. - **`private-framework-report.test.ts`: 256MiB packaging test timed out.** It takes about 10s alone and more than 30s with the whole suite running in parallel. It now has a 120s budget. - **`controller-database.test.ts`, `controller-recovery-observer.integration.test.ts`: no change.** Embedded Postgres started and both files passed in all my runs. The earlier failures look like local resource problems. ## Verification - Combined check (this branch plus #19527, on master `5d25117ffa`): `pnpm test:e2e:runner:unit` — Vitest 317 files passed (6,815 tests, 3 skipped), node:test 373 passed, 0 failed. It took 63s on a 10-core Mac. - `pnpm test:e2e:runner:typecheck` — passed. - This branch alone fails only the two pnpm-setup tests that #19527 fixes. - Focused files: `pi-file-evidence` 75 passed, `pi-controls-evidence` 72 passed, `operating-receipts` 12 passed, `workflow-security` 14 of 16 passed (the other 2 pass with #19527), `private-trust-workflow-security` 3 passed, `remote-native-bootstrap` 71 passed. ## Risks - Low risk. Only test files, one evidence oracle under `tests/runner-e2e/`, and the suite launcher change. No product code changes. - The Pi oracle change affects how paid Pi file qualification is graded. It accepts the old and new title shapes, and it is stricter about extra shell calls. A live Pi qualification run would confirm it on real Rust-normalized events. I did not run one, because it is paid. - The chat catalog policy is the same as #18516. Under that design, the pinned candidate source runs in `runner-e2e-private-paid` with `id-token: write`. The guards above limit who can start it and which source runs. This PR does not change that trust model. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), by Anthropic, in Claude Code with tool use (shell, file edits, git, GitHub CLI). ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>canary/v2026.1106.0-canary.19 |
||
|
|
86e9c9354d |
test(opencode): pin Stop during a pending MCP tool call (#19550)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The native runner runs OpenCode agents and settles their cost after each run > - An operator can stop a run while the agent waits inside a Paperclip MCP tool call > - Before #18610, OpenCode then kept calling the model after the Stop. The turn never closed, so the run cost never settled > - #18610 fixed the cause, but no test covers a Stop during a pending MCP tool call > - This pull request adds that regression test at the driver boundary > - The benefit is that a change to empty-answer recovery or interruption cannot bring back runaway model work after a Stop ## Linked Issues or Issue Description Refs #18610 (the fix that this test pins) Refs #19497 (accounting that waits on a terminal run receipt) **What happened?** A board operator stopped a native OpenCode run. The agent was blocked inside a gated `paperclip_call_assigned_tool` call. The runner acknowledged the interrupt, but OpenCode made 8 more model requests in the next 60 seconds. No `turn.cancelled` event arrived. The run stayed at `costAccountingPending: true` and `accountingReceiptReady: false`, so cost accounting never settled. **Expected behavior** A Stop aborts the provider. The turn closes with `turn.cancelled`. The provider makes no new model requests. Cost accounting then settles from the final receipt. **Steps to reproduce** 1. Start an OpenCode turn with a dynamic Paperclip tool whose handler does not return. 2. Let the provider call that tool through the MCP bridge. 3. Fail the pending call (runnerd does this first on Stop), then interrupt the turn. 4. Let OpenCode report the abort as a bare `session.idle`. 5. Before #18610, the driver sent a corrective "give a final answer" prompt. The provider then continued work. **Root cause** The driver sent `/session/:id/abort` correctly. OpenCode 1.18.34 aborts correctly, and sometimes reports the abort only as `session.idle`. Empty-answer recovery treated that idle as an incomplete answer and sent a new prompt. #18610 added `#interruptedTurnId`, so recovery now stops for an interrupted turn. The failing eval ran on `3b66c3c144`, which is earlier than #18610. ## What Changed - Add `packages/paperclip-runner/src/drivers/opencode/interrupt-pending-tool-call.test.ts`. It calls the real MCP bridge over HTTP, holds the dynamic tool call open, and interrupts the turn. It checks three abort reports: - `idle-after-settle`: runnerd fails the call, then the interrupt, then a bare idle. - `idle-before-settle`: an idle while the call is still pending, then the late failure, then an idle. - `aborted-error`: `MessageAbortedError`. - Each case asserts one prompt only, exactly one terminal event (`turn.cancelled`), and no empty-response correction diagnostic. - No product code changes. Master already has the fix. ## Verification - `pnpm --filter @paperclipai/paperclip-runner exec vitest run src/drivers/opencode/interrupt-pending-tool-call.test.ts`: 3 passed on this branch. - The same test at `3b66c3c144` (before #18610): `idle-after-settle` and `idle-before-settle` fail with 2 prompts (the corrective prompt). `aborted-error` passes. This matches the eval symptom. - `vitest run src/drivers/opencode/`: 14 files, 185 tests passed. - `tsc -p tsconfig.test.json --noEmit` for the runner: no new errors. A pre-existing error in `src/eval/workflow-harness.ts` comes from an unbuilt local eval-kernel package. - `server` `native-stop-accounting.integration.test.ts`: 5 passed. This confirms that settlement completes after a Stop when the terminal event and final receipt arrive. - Manual, not committed: the same flow with the real OpenCode 1.18.34 binary, a mock OpenAI-compatible model, and a gated tool. I tested it through the driver alone and through runnerd, the proxy, and the driver. The interrupt produced `turn.cancelled`, and the model received no more requests. I also tried different gate-release and model-latency timings. - The new test waits for the terminal event with a bounded `expect.poll`, not a fixed delay (review feedback). - `pnpm verify` exited 2 (broader verification required) because the new test file is outside the resolved test graph. CI supplies the broad coverage. ## Risks Low risk. This change adds one test file only. The test uses the existing fake OpenCode fixture and an injected `fetch`. It uses real timers with short waits. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, used in Claude Code with tool use (shell, file edits, code execution) and extended reasoning. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c650f36007 |
fix(ui): refine agent profiles, identity editing, and list actions (#19535)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent pages expose identity files, reporting relationships, and
runtime settings.
> - Several controls had inconsistent spacing, weak navigation, or
misleading save feedback.
> - Empty task-row detail slots also reduced the title width before
truncation.
> - This pull request fixes those behaviors and aligns the reviewed UI
details.
> - The result is clearer agent editing and more readable task and agent
lists.
## Linked Issues or Issue Description
**What happened?**
Agent headers and file controls had inconsistent layout. The Identity
footer appeared without edits and did not provide clear save
confirmation. File names could wrap or omit the Markdown suffix. Task
titles truncated while empty detail slots occupied the row.
**Expected behavior**
Keep headers and actions aligned. Reveal the Identity footer only for
changes or save feedback. Use valid Markdown file names and actual file
dates. Let titles use available space.
**Steps to reproduce**
1. Open an agent overview, then its Identity tab.
2. Edit, undo, save, copy, and add an instruction file.
3. Resize the file panel and inspect file names and date labels.
4. Hover an agent row and inspect the Chat action and tooltip.
5. Open a task list with no recovery or external details and inspect
title width.
Related: #18716 is the prior UI bundle. #16934 and #14483 concern role
editing; this change does not add a role picker.
## What Changed
- Refine the character header, inline title and name editing, crown
position, runtime shortcut, and Chat/New task actions.
- Align profile metadata and manager selection; hide empty direct
reports.
- Refine Identity panels, Markdown filename validation, file-row hover,
truncation, Paper Mono typography, and copy feedback.
- Add a viewport-bound save footer with dirty, saving, and saved states.
Keep undo/discard separate from successful saves.
- Return recorded creation or modification dates for instruction files.
Reuse the active file transaction for metadata reads to avoid exhausting
the small connection pool.
- Reveal the agent-row Chat icon and label on hover, with a standard
delayed tooltip.
- Collapse empty task-row details so titles can use the available width.
## Verification
- Browser checks covered the local overview and Identity flows, narrow
file panels, successful file creation/update, undo/discard, header
navigation, row hover, tooltip delayed-open state, and full task titles.
- Focused instruction, save-footer, date, font-asset, and task-row tests
passed during development. After review, 55 file/instruction tests
passed, including cold restoration with recorded and unknown dates;
server typecheck and 18 immediate-save response tests also passed.
- Full `pnpm -r typecheck` and `pnpm build` passed after rebase onto
current master. The complete `pnpm test:run` attempt hit a 15-second
timeout in the existing chat Retry HTTP integration test; that case
passed in isolation (1 test passed). A second full attempt hit timeouts
in two other chat integration cases (GitHub webhook draining and
delivery retry budget); both passed in isolation without increasing
timeouts. All 55 remote checks passed on
|
||
|
|
705c3e955d |
fix(chat): show one onboarding question card when onboarding moves to agent chat (#19512)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - New users start with an onboarding first task. It has an opening question card and, after "Interview me", an interview question card. > - With the coordinator chat enabled, the agent's chat with the user mirrors pending questions from that agent's direct tasks. The onboarding task is a direct task. > - A tester moved onboarding from the task into the chat. The agent asked its interview in the chat. The chat then showed the chat card and the mirrored onboarding-task card. Both were answerable. > - This pull request expires the agent's pending onboarding-task questions when the agent asks a question in its chat with that user. > - The benefit is one interview card, answerable once, wherever onboarding runs. ## Linked Issues or Issue Description **What happened?** Onboarding testing on Paperclip Cloud staging (Codex API path). The steps were: enable the experimental chat and coordinator features, start the "Interview me" onboarding task, switch to the agent chat, and ask the chat to run the onboarding there. The chat agent started the interview, and a duplicate question card appeared. **Root cause** `conversationDecisionReceiptsService.listPendingQuestions` projects pending questions from direct tasks (created by the chat user, assigned to the chat agent) into the chat. The onboarding first task matches. The PAP-437 sibling-supersede rule in `issueThreadInteractionService.create` only works on the same issue, and it is off for chat questions. So the task's pending interview card and the new chat card both stayed pending and visible in the chat. **Expected behavior** One interview question card in the chat, answerable once. **Steps to reproduce** 1. Enable the experimental Agent Chat and coordinator features. 2. Create the onboarding first task and choose "Interview me" on its opening card. 3. Open the chat with the same agent and ask it to run the onboarding there instead of in the task. 4. The agent asks its interview in the chat. The chat shows two pending question cards: its own card and the mirrored onboarding-task card. **Paperclip version or commit** `master` at |
||
|
|
fca1f1be4f |
fix(attention): keep replaced confirmations out of Needs you after the newest is settled (#19520)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Needs you is the board inbox. It lists the decisions that wait for a person. > - Agents often ask for the same confirmation again on one task. Needs you shows only the newest pending confirmation for each task. > - When a person approved that newest card (for example in chat), or the card expired, the older card that it replaced came back into Needs you. > - Manual onboarding tests reported this: decision cards that were already approved in chat still showed in Needs you. > - This pull request makes the newest answered or expired confirmation also hide the older pending confirmations on the same task. > - The benefit is that Needs you does not show stale cards after a person answers the current one. ## Linked Issues or Issue Description **What happened?** A person approved a decision card in chat. Needs you still showed a decision card for the same task. Expired cards also caused an older card to show again. **Steps to reproduce** 1. An agent creates a confirmation on a task. Then it creates a second confirmation on the same task. 2. Needs you shows only the second card. 3. Approve the second card in chat (or let it expire). 4. The first card now shows in Needs you. **Expected behavior** Needs you shows no card for the task, because the newest ask is settled. **Root cause** `collapsePendingConfirmationsToNewest` compared only pending rows. Chat cards use the same `issue_thread_interactions` row and the same accept service, so there is no duplicate record. The server already drops answered rows. The problem was the older pending row that the collapse hid before. ## What Changed - `server/src/services/attention-source-eligibility.ts`: the collapse takes the newest settled (accepted, rejected, answered, expired) ordinary confirmation for each issue. Older pending ordinary confirmations stay hidden. A new `loadNewestSettledConfirmationAt` helper reads that value with one grouped query. - Cancelled and failed replacements do not settle the ask, so the older card returns (the existing retention test keeps this behavior). - Tool-action confirmations and quality-review confirmations are excluded from the settled check. A settled tool action never hides a different pending tool action. - `server/src/services/attention.ts` and `server/src/services/attention-retention.ts` use the new input, so the feed and the retention worker agree. - Review follow-up: the summary and task-index payload keeps `toolAction` and `secretProposal`. A settled card only hides older asks addressed to the same recipient. Tool actions, secret proposals and app connections (`connectionAuthorization`) form one governed set. They match the set the other confirmation readers use. A settled governed card never settles an ordinary ask, and a pending governed card is never hidden. - `server/src/__tests__/attention-service.test.ts`: new tests for "approved from chat", "rejected from chat", "expired", a re-ask after a settled card, another recipient, a settled secret proposal, tool actions in `list()`, and tool actions in `summary()`/`taskIndex()`. The tests use two agents, because the service already expires a same-agent re-ask at creation. ## Verification Red on `master` ( |
||
|
|
b5c06d660a |
chore(lockfile): refresh pnpm lockfiles (#19560)
## Thinking Path > - Paperclip uses a dependency lock for reproducible builds. > - Dependency changes on master need matching lock entries. > - Paid eval reports also pin a reviewed offline correction. > - Reports must install before paid model work starts. > - This automated PR keeps the lock and report proof together. ## Linked Issues or Issue Description **What happened?** Dependency manifests on master changed after the last committed lock refresh. **Expected behavior** Frozen installs must match those manifests. Eval reports must retain matching offline lock proof. **Steps to reproduce** Run a frozen install before and after this lock refresh. ## What Changed - Refresh the dependency lock from the manifests on master. - Refresh the sandbox provider lockfiles from their manifests on master. - Include the matching offline report correction and hashes. ## Verification - Pinned pnpm resolution completed with lifecycle scripts disabled. - The report proof refresh accepted the reviewed committed baseline. - The workflow rejected changes outside the lock and report proof files. ## Risks The lockfile bot merges this PR without CI or review when every added package version is at least 3 days old on npm. Otherwise the PR waits for a maintainer, and the daily refresh tries again. The bot merges only the exact commit this workflow generated from manifests already reviewed on master. Unknown report lock baselines remain blocked. This update changes no permissions or paid enable gates. ## Model Used None — generated by pnpm and the deterministic report-lock updater. ## Checklist - [x] Thinking path and problem description included - [x] Generated lock and matching report proof included - [x] Release-age gate decides between bot merge and maintainer review Co-authored-by: lockfile-bot <lockfile-bot@users.noreply.github.com> |
||
|
|
7dfdc97b9f |
fix(deps): let Dependabot update the sandbox providers (#19489)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Sandbox providers (`packages/plugins/sandbox-providers/*`) are standalone published packages. `pnpm-workspace.yaml` keeps them out of the root workspace on purpose. > - `.github/dependabot.yml` watches only `/`, so Dependabot never saw the providers. > - Because of this, the Kubernetes provider stayed on zod 3 (GHSA-mrhx-c7xc-m3w5) after the rest of the repo moved to zod `^4.6.6`. #18839 fixed it by hand. > - We do not want to keep these versions current by hand in many places. > - This pull request lets Dependabot update the providers, and keeps each provider standalone. > - The benefit is that a provider dependency that falls behind gets a pull request that can pass CI. ## Linked Issues or Issue Description Refs #18839 (adds `scripts/typecheck-sandbox-providers` and fixes Kubernetes zod by hand). Refs #18998 (a Dependabot pull request for the Pi distribution that fails CI by design). **What existing behavior does this improve?** Dependabot version updates. They skip every package that the root pnpm workspace excludes. **Subsystem affected** CI and dependency automation: `.github/dependabot.yml`, `scripts/typecheck-sandbox-providers`. **Current behavior** Dependabot does not update the sandbox providers. A provider can stay on an old or unsafe version, and nobody gets a pull request. **Proposed behavior** Dependabot updates the providers each week. Each pull request changes the provider manifests and the lockfiles that CI installs frozen, so the pull request can pass CI. **Reason and benefit** We stop having to find and fix old provider dependencies by hand. ## What Changed - `.github/dependabot.yml`: add an npm entry for `/scripts/typecheck-sandbox-providers`. Dependabot treats this workspace as one unit. One pull request per dependency changes every provider that uses the dependency and the shared `pnpm-lock.yaml` together. So the entry needs no `groups`. One entry per provider would open a separate pull request for each provider, and each one would fail the frozen install in CI. - `.github/dependabot.yml`: add an npm entry for `/packages/plugins/sandbox-providers/daytona`. Daytona is the only provider with its own lockfile, and the release build and the Daytona end-to-end job install it frozen. Each lockfile has exactly one Dependabot entry, so every pull request changes a manifest and its lockfile together. - Take Daytona out of the shared typecheck workspace. This pull request does not commit `scripts/typecheck-sandbox-providers/pnpm-lock.yaml`: since #19522, only the lockfile refresh workflow writes it. After merge, the bot removes Daytona's packages from it (no other version changes; checked with pnpm 9.15.4). Until then CI resolves it in place. `scripts/typecheck-test-programs.mjs` now installs Daytona from its own lockfile (`LOCKED_PROVIDERS`, with `--ignore-workspace`). A stale lockfile is resolved in place for the run, for both installs. - `.github/dependabot.yml`: add a comment that tells why the Pi and Kimi runtime distributions have no entry (see Risks). - `scripts/typecheck-sandbox-providers/pnpm-workspace.yaml`: list each provider by its full path. Dependabot does not expand a workspace glob that starts with `../`, so with the glob it saw no providers. - `scripts/typecheck-test-programs.test.mjs`: add tests. They check that the workspace list has no glob and matches `SANDBOX_PROVIDERS` without `LOCKED_PROVIDERS`, that each locked provider has its lockfile, that a locked provider installs frozen with `--ignore-workspace`, and that a stale locked-provider lockfile is resolved in place. - Add `.npmrc` (`auto-install-peers=true`) in `scripts/typecheck-sandbox-providers` and in the Daytona provider. Without it, Dependabot reads the root `.npmrc` (`false`). That rewrites the lockfile settings, and the frozen install then fails. npm and pnpm never publish `.npmrc`. - `scripts/typecheck-sandbox-providers/package.json`: add `"packageManager": "pnpm@9.15.4"`, the version CI uses. Without it, Dependabot guesses pnpm 10. - `doc/DEVELOPING.md`: say that contributors do not refresh or commit Daytona's lockfile either, and that a new provider goes in both lists. No provider moves into the root workspace. There is no pnpm catalog. The root `pnpm-lock.yaml` does not change. ## Verification I ran the real Dependabot updater on this branch with the Dependabot CLI (`dependabot update ... --local`, updater image `dependabot-updater-npm@sha256:c5a9451c…`): - Before the change, the workspace entry loaded only `cloudflare/bridge-template` (the one path without a glob). It skipped all the other providers. - After the change, `--dep typescript` gave one pull request. It changed the 9 non-Daytona provider `package.json` files and the shared `pnpm-lock.yaml`, and did not touch Daytona. - `--dep wrangler` changed only the wrangler closure in the shared lockfile. Before the `.npmrc` fix, the same run changed `autoInstallPeers` to `false` and 757 lockfile lines. - The Daytona entry (`--dep @daytonaio/sdk`) changed Daytona's `package.json` and its own lockfile in one pull request, and kept `autoInstallPeers: true`. It keeps the exact pin exact: `0.203.0` becomes `0.227.3`. - Both frozen installs (shared workspace, and Daytona with `--ignore-workspace`) pass with pnpm 9.15.4. - Dependabot installed `pnpm@9.15.4` from the new `packageManager` field. Other checks: - `.github/dependabot.yml` passes the SchemaStore `dependabot-2.0` schema (Ajv). A wrong key fails the same check. - With pnpm 9.15.4, `install --resolution-only --ignore-scripts --no-frozen-lockfile` in `scripts/typecheck-sandbox-providers` (the lockfile bot's command) gives exactly the lockfile that passed the frozen installs earlier in this pull request. - The lockfile rule from #19522 allows this pull request's diff: it changes no lock content. - `node --test scripts/typecheck-test-programs.test.mjs`: the new test and the provider tests pass. Two other tests need a root `node_modules` (`tsc`), which my worktree did not have. CI runs them. These checks cannot run before merge. GitHub reads `dependabot.yml` only from the default branch. After merge, open Insights → Dependency graph → Dependabot and make sure that the two new entries show no errors and run on Monday. ## Risks - A dependency in both a non-Daytona provider and Daytona (for example `typescript`) gets two pull requests: one from each entry. Each one passes alone, and they can merge in any order. - The typecheck job now typechecks Daytona's tests against Daytona's own lockfile instead of the shared one. Its versions are a little older (for example vitest 4.1.11), so a type difference can show up there. - Majors of shared dev tools (for example TypeScript 7, Vitest 5) come as one pull request across all providers. It can need code changes before it passes. That is the same as the root entry. - Pi and Kimi get no version updates. Every direct dependency there is a pinned provider release, and the pins feed the provider's ACPX `commandDigest`. The Runner checks compare the Pi lock with `PI_DISTRIBUTION_PINS`, and the Kimi build compares the lock SHA-256 with a pin. So any Dependabot change there fails CI (see #18998), and a real upgrade starts saved sessions fresh. GitHub security alerts still cover both lockfiles. - The Pi zod `3.25.76` pin is still affected by GHSA-mrhx-c7xc-m3w5 (open alert). It is deliberate: `pi-acp@0.0.33` has `zod ^3.25.0` as a direct dependency, not a peer. A fix needs a `pi-acp` release that allows zod 4. This pull request does not change it. - `packages/plugins/examples/plugin-orchestration-smoke-example` is also outside the root workspace. It uses `workspace:*` dependencies, so it cannot install alone, and this pull request does not cover it. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edits, local Docker runs of the Dependabot CLI). ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
65aee998a3 |
fix(server): settle empty environment cleanup notifications (#19316)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Environment workers retain cleanup duties and retry them safely. > - A metadata change can notify the worker without adding cleanup debt. > - The pending-cleanup worker kept a work hold during its thirty-second spacing window even when its owner reported no debt. > - Historical cleanup already checks that condition without advancing a provider sweep. > - This PR applies the same owner check to pending cleanup and preserves later notifications. > - An empty notification can settle while real cleanup and uncertain work still block sleep. ## Linked Issues or Issue Description Refs #18646. That change covers empty historical-cleanup notifications. This follow-up covers the pending-cleanup queue. Related runtime work: #18988. A search of open and closed PRs and issues found no duplicate pending-queue fix. This repairs an existing worker under the roadmap's Cloud deployment work. **What happened?** A normal metadata update on a released lease left one tracked work intent and a scheduled pass. The actual cleanup deadline was null before and after the update. The worker kept the hold until its spacing window ended. **Expected behavior** The worker can clear an empty notification through its existing owner predicate after a normal sweep and deadline read complete. It must retain new debt, unresolved transactions, failed sweeps, failed reads and later notifications. Provider sweep spacing and cleanup retry deadlines must stay unchanged. **Steps to reproduce** 1. Create and release a fixture cleanup lease through the normal services. 2. Start the registered worker and confirm that its cleanup deadline is null. 3. Change the released lease's metadata before the thirty-second spacing window ends. 4. Run the new test. The original worker keeps an active intent even though eligibility remains empty. **Paperclip version or commit** Candidate: `69aaee95cd73322a37e0270354d4a06bf5512ba7`. It normally merges actual master `cd3832d2357eab481a861331ba1bc944a0b951f4`. The focused retry repair follows the original two-file fix. Every other tree blob still matches that master. The diff has two files. **Deployment mode** Source checkout with external PostgreSQL 17 and controlled coordinator time. No deployed host or database suspension result is claimed. ## What Changed - Apply the existing cheap deadline check to both registered cleanup queues during provider spacing. - Clear only the captured notification generation when the owner returns no deadline and the latest normal sweep completed successfully. - Invalidate that coverage before every full sweep. Startup and later sweep failures keep their existing retry even if a metadata notification has no database deadline. - Add real PostgreSQL controls for empty metadata changes, concurrent debt, unresolved transactions, failed reads through drain/resume, and unread-spool recovery after startup and later sweep failures. - Retain the original provider sweep, retry, deferred-buffer, creation, startup, historical and shutdown controls. ## Verification **Real PostgreSQL red/green qualification passed.** The original empty-notification counterfactual failed at active work one, expected zero, after actual null eligibility reads (16.706s). Review then found a missing failed-sweep combination. Two additional cases using the real malformed spool and ordinary metadata notifications reproduced lost retries on the earlier published head, both at the intended pending predicate (42.144s). One case fails at startup; the other first completes a successful sweep, then fails a later sweep. Failed fixtures and original logs remain retained. At measured source `1ccd227c51e03ab4306d7cb85cf81efb0e3d3678`, all **29 owner cases passed in 113.061s**, without filters or skips. The tests repair the actual spool, recover the ordinary cleanup lease, perform provider cleanup once, preserve the later-sweep backoff, and return to quiet. Normal migrations used a fresh owned external database; each pool was capped at four with one worker. The first test-fixture attempt accidentally replaced the real spool driver; that setup failure is preserved separately and was corrected before the counterfactual. No final cleanup result or receipt was seeded. Run the full owner suite against a fresh, owned external test database with normal migrations applied: ```sh pnpm --filter @paperclipai/server exec vitest run src/__tests__/environment-cleanup-work.test.ts --maxWorkers=1 --no-file-parallelism ``` Set `PAPERCLIP_ENVIRONMENT_CLEANUP_TEST_DATABASE_URL` to that fixture. Local qualification uses one worker and a pool of four. Current native TypeScript checks passed: normal production declaration layers (44.127s) and the affected test entry using the repository's actual CI options and references (3.922s). Own dependency builds passed. A frozen install first found stale upstream ACPX patch metadata; normally carrying #19294 corrected the hash references for the same package version. No lock policy was relaxed. Full local workspace typecheck, tests and build were not run in this bounded external-PG lease. Fresh checks on the current head `69aaee95cd73322a37e0270354d4a06bf5512ba7` completed with **53 successes, one neutral Pony review and two skips**, with no pending or failed gates. Its [PR workflow](https://github.com/paperclipai/paperclip/actions/runs/42104787089), aggregate verification and commitperclip review succeeded. The complete latest edited Greptile and Pony summaries both gave **5/5**. Both review threads are resolved, and all root and nested thread pages were inspected. The current head only removes four changed code-comment lines to follow CONTRIBUTING.md. Its emitted JavaScript is byte-identical to the measured parent `1ccd227c51e03ab4306d7cb85cf81efb0e3d3678`; tests, configuration and dependencies are unchanged. The local 29-case evidence therefore carries to this head without a duplicate local run. The remote results above are fresh for the current head. On the earlier head, Runner CI failed an omitted-context checkpoint-settlement case. The fresh Runner job on this head passed without a rerun. The original failure logs are preserved and its cause remains unproven; current success is recovery evidence, not a causal explanation. The recorded test and migration command PIDs were reaped; an independent audit verifies zero owned database clients before normal cleanup. Failed fixtures remain retained. The process audit covers recorded command PIDs, not an exhaustive history of child processes. The long quiet assertion uses controlled coordinator time and actual SQL counters. It does not prove a real twenty-minute quiet window, provider behavior or deployed suspension. ## Risks The empty read must not erase a notification that arrives while it runs. The captured generation and transaction controls preserve that boundary. An unknown or failed read keeps work pending. Empty deadline rows cannot cover a failed sweep or an unread local spool; only a successful normal sweep and deadline read establish that coverage. No cleanup row, resource claim, provider action, authorization rule, sweep spacing or retry deadline is removed. ## Model Used OpenAI `gpt-6.1-sol` produced the delegated regression tests and source review. OpenAI Codex, based on GPT-6, implemented the small worker change and managed integration and verification. Both used repository tools and code execution. Exact served backend revisions and context limits were not exposed. ## 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 linked an existing public issue or described the issue using the issue template - [x] I have not referenced internal or instance-local Paperclip issues or links - [x] My branch name describes the change and contains no internal issue ID - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have checked that no documentation change is needed for this internal worker fix - [x] I have considered and documented risks - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open findings - [x] I will address all reviewer comments before requesting merge |
||
|
|
f513f0efd9 |
fix(recovery): admit certified process-loss preparation retries (#19552)
## Thinking Path > - Paperclip keeps task work durable across server restarts. > - Restart recovery records a certificate when a Runner preparation ended before provider work started. > - Automatic recovery already verifies that certificate together with saved files and stopped leases. > - Explicit Retry called a preparation helper that only reached this proof for status `interrupted`. > - A certified `failed/process_lost` source therefore stayed blocked after its files were recovered. > - This pull request makes the helper reuse the same existing proof. > - A queued user Retry can then admit one new turn while all current safety and policy gates remain effective. ## Linked Issues or Issue Description **What happened?** A task had a certified process-lost preparation, verified saved-workspace receipts, a destroyed source sandbox, and one or two later attempts refused before dispatch. Its accepted Retry request stayed deferred with `execution_recovery` and created no successor. The explicit admission helper excluded this source before it could use the preparation proof added in #19464. This also prevented the refused-attempt continuation path from #19466 from advancing. **Expected behavior** Explicit Retry must use the same positive pre-provider proof as restart recovery. It must preserve source history, refuse contradictory execution evidence, honor current policy gates, and admit at most one successor for the existing request. **Steps to reproduce** Persist a legacy preparing run as failed with `process_lost` and the host bootstrap certificate that provider work did not start. Record its canonical committed snapshot, recovery/finalization operations, clean workspace authority, and exact destroyed-lease receipt. Add one or two unstarted cancelled descendants with the dispatcher's execution-wait receipt. Accept one Retry request and resume it concurrently twice. Before this fix, no successor exists and the same request remains deferred. **Paperclip version or commit** Reproduced on master `a6c22806308e8a3c7b21145f80fb471756bb443d`. Related repairs: #19464 and #19466. ## What Changed - Reuse `isInterruptedNativePreparation` before the existing failed-preparation checks. The existing no-coordinator and historical-ancestor restrictions remain. - Add real-database regressions for one and two refused descendants, duplicate clicks, concurrent resume, continuation dispatch, preserved history, and unchanged recovery attempt counts. - Add late-gate checks for provider work, provider events, a newer native checkpoint, missing bootstrap proof, missing stop proof, a changed snapshot, formal approval, user Stop, agent/company pause, and a spent budget. - Clarify that explicit admission uses the existing certified preparation proof. ## Verification - Red: both real-database cases passed the saved-file, interrupted-preparation, and refused-dispatch proof checks, but the original successor-count assertion found zero queued runs instead of one. The accepted requests retained `execution_recovery`. - Green: the same cases admit one successor after two concurrent resume calls. Continuation dispatch resolves to the latest refused attempt. Source and cancelled-attempt history stay intact. - Complete relevant local suites: **456/456 passed** before rebase. Command from `server`: `../node_modules/.bin/vitest run --config vitest.config.ts --fsModuleCache src/services/task-retry.test.ts src/services/explicit-native-continuation.test.ts`. - Final head `9788382c019df57899af06e81211e7c5165e7f0d`: the thirteen new cases passed **13/13** again after rebase. Required CI and review remain pending. - Environment: macOS ARM64, Node 26, Vitest 5.0.3, disposable real PostgreSQL. Tests exercise production request, admission, and continuation services. No provider executes and no hosted task is changed. - Hosted recovery still requires rollout and verification through the original queued requests. Final reviewed head: `9788382c019df57899af06e81211e7c5165e7f0d`. [Required CI](https://github.com/paperclipai/paperclip/actions/runs/42273663624) passed (56 successful checks, four expected skips, none pending or failed). Greptile and Paperclip Review both rated this head 5/5 with no findings; all review threads are resolved. ## Risks The change accepts only the existing verified preparation contract. An error label alone is insufficient. Provider-started or contradictory state remains blocked. Saved-file, lease, approval, Stop, pause, and budget checks remain effective. No schema, retry limit, provider setting, account selection, or production scheduler changes. ## Model Used OpenAI GPT-6 family via Codex, with coding and tool use. The exact model ID and context window are unavailable in this session's metadata. ## 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> |
||
|
|
71313207eb |
fix(runner): continue after a restore-lost turn the checkpoint never bound (#19539)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Native Codex and ACPX runs survive control-plane restarts: the runner restores the provider conversation, and the control plane continues the task from its checkpoint > - When the runner restores a conversation after process loss, it reports the turn that was active as lost on restore (`provider_turn_lost_on_restore`) > - A controller can die after the provider accepted a turn but before that turn reached the checkpoint. Then the next recovery does not know the turn that the runner reports as lost > - In a restart chaos wave with two hard kills about 20 s apart, this made the second recovery fail with "Provider turn terminal did not name the active turn". The session close then became unrecoverable > - This pull request lets recovery continue: the transport no longer stalls a new turn start behind that report, and the Codex session records the report as restored history > - The benefit is that a task continues automatically after back-to-back restarts, instead of failing and needing an operator ## Linked Issues or Issue Description **What happened?** A Codex task was resumed after a hard restart. A second hard restart came about 20 s later. The second recovery failed at once with `NativeProviderTerminalFailure: Provider turn terminal did not name the active turn.`, and automatic recovery stopped. The session close could not settle, which quarantined later work (that blast radius is fixed separately in #19532). **Steps to reproduce** 1. Start a native Codex task. Kill the control plane (SIGKILL) shortly after the provider accepts a turn and before the next checkpoint is saved. 2. Let restart recovery resume the run. Kill the control plane again while the resumed run is active. 3. Observe the second recovery. Durable evidence from the wave (runner journal of the run): - Restart 1 (17:32:34.568): the provider accepted continuation turn T2. The controller was killed 170 ms later, before T2 reached the checkpoint. - Recovery 1 started a new turn T3. The runner command result named T3 (seq 15). The restored runner then reported T2 as lost on restore (seq 20) before `turn.started` for T3 (seq 23). The transport held the T2 terminal while the T3 start was pending, and stopped pumping at it. So T3 ran in the provider (items, a tool call) but the control plane never accepted or checkpointed it. - Restart 2: recovery found no active turn. The restored runner reported T3 as lost on restore. The Codex session had never bound T3, so it failed the protocol (`turn_binding_mismatch`). **Expected behavior** Recovery continues the task after each restart (R1). **Actual behavior** The run failed with a protocol error. Its close could not prove settlement, so cleanup needed an operator. ## What Changed - `runnerd-codex-transport.ts`: while a turn start is pending, the event pump holds a terminal only when the terminal can belong to that turn. When the durable command result names the new provider turn and the terminal names a different turn, the pump delivers it and continues to the new `turn.started`. A terminal that names no turn, or any terminal before the command result, is still held. - `codex-session-notifications.ts`: a `turn/completed` with status `failed` and a recoverable `provider_turn_lost_on_restore` error, for a turn that this session never bound (not active and not terminal), is recorded as restored history with a `harness.diagnostic` (`unbound_restored_turn_lost`). The session keeps its own turn. The runner reports this cause only after it proved that the old provider process ended and restored the conversation, so that turn can produce no further work or result. - Other terminals for an unbound turn (a completion, an ordinary failure) still fail with `turn_binding_mismatch`. ## Verification - Red before the fix, real `paperclip-runnerd` and fake Codex app server: `pnpm --filter @paperclipai/paperclip-runner exec vitest run src/live/runnerd-restart-continuation.test.ts -t "never bound"` failed with `NativeProviderTerminalFailure: Provider turn terminal did not name the active turn.` at `codex-harness-session.ts:207` via `native-session-runtime.ts` (the same stack as the wave). - Green after the fix: that test completes the run on `provider-turn-2` with no `session.failed` and no `turn.failed`. - New driver tests in `codex-app-server-driver.terminal-replay.test.ts`: the restore-lost unbound turn is recorded (also on replay) and the session's own turn completes. A completion or ordinary failure for an unbound turn still fails the protocol. - New transport unit test for the hold rule (`does not hold a pending turn start behind another turn's restore-lost terminal`). - Existing suites pass: `src/drivers/codex/`, `src/native-session-runtime.test.ts`, `src/live/runnerd-restart-continuation.test.ts`, `src/live/runnerd-controller-outage.test.ts`, `src/live/runnerd-codex-transport-warm-attachment.test.ts`, `src/live/runnerd-codex-transport.test.ts` (turn-start cases). Three tests timed out once at host load 51 and passed when rerun alone. - `pnpm --filter @paperclipai/paperclip-runner typecheck:typescript` passes. - Live replay of the wave task: results are added in a comment below. ## Risks - The transport change applies only when the command result already named the new turn and the terminal names a different turn. A terminal for the new turn keeps the existing fence that puts `turn.accepted` first. - The session change accepts only the runner's restart-interruption cause. A lost turn's work is already in the restored conversation. The continuation turn tells the agent to reconcile unfinished work. - No profile, schema or pack contract changes. ACPX sessions that use the same Codex harness session get the same behavior. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code execution in Claude Code. ## 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 - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c6eecac1e0 |
fix(heartbeat): keep a shutdown-suspended native recovery for the next boot (#19530)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The Runner Full-Stack E2E workflow tests the Runner in Daytona sandboxes from a signed, content-addressed image > - Every change to an audited Runner input needs a new image before a Daytona cell can test it > - The image job took 10 to 16.5 minutes today, and a restart chaos campaign waits on it after each runner-side fix > - Measurement shows two serial waits: a full Rust dependency compile (about 5 of 6.6 minutes) and a 3.5 to 4 minute download of cached base layers that starts only after the builder stages finish > - There is also no way to build only the image: every dispatch reaches a paid gate that the operator must reject > - This pull request removes both serial waits and adds an `image_only` dispatch input > - The benefit is a shorter fix-to-Daytona-test loop, with every image security check unchanged ## Linked Issues or Issue Description **What happened?** The `Publish verified Daytona image` job is on the critical path of the control-plane restart chaos campaign. Builds today took 16.5 min (run 42240434011), 15 min (42253920727), 10.5 min (42259118276) and 16 min (42265637650). Run 42264611573 failed: its RunsOn runner lost communication with GitHub 7.6 minutes into the build step, during the Rust compile. GitHub has no log for that job. **Expected behavior** A Runner source change should not recompile the locked third-party Rust graph. Cached image layers should download while the builder stages compile. An operator should be able to publish only the image, without a paid gate. **Steps to reproduce** Read the BuildKit timeline in the logs of job 127970125495 (run 42240434011). See the stage table below. ## What Changed - `docker/daytona-runner/Dockerfile`: compile the locked Rust dependency graph in its own layer against placeholder targets. The layer depends only on `Cargo.toml`, `Cargo.lock` and the crate manifest. The step deletes the placeholder outputs and fingerprints, so the next step always compiles the real Runner sources. The final stage still receives only the stripped executable. - `docker/daytona-runner/Dockerfile`: move the content-ID / source-revision check above the two `COPY --from` steps. It does not depend on either builder stage, so BuildKit now downloads the cached base and CLI layers in parallel with the compile. Both copies use `COPY --link`. - `.github/workflows/runner-full-stack-e2e.yml`: add the boolean `image_only` dispatch input. `authorize`, `target_lock`, `catalog` and `daytona_image` run unchanged. Every other campaign job gets `&& inputs.image_only != true`. `authorize` rejects `image_only` together with TestOps inputs. - `tests/runner-e2e/workflow-image-only.test.ts` (new): pin the job classification, the top-level guard on each campaign job, the guard on every protected environment, and the TestOps rejection. - `tests/runner-e2e/README.md`: document the layer layout and the `image_only` command. ## Verification Measured on the same RunsOn `m7i-flex.xlarge` (4 vCPU, 16 GiB, native x64) runner class. "Before" is master job 127970125495 (run 42240434011). "After (cold)" is this branch, job 128077687214 (run 42271236140), with no dependency layer in the registry cache yet. Its paid gate was rejected. | Stage | Before | After (cold dependency cache) | |---|---|---| | Whole image job | 16.5 min | **11.6 min** | | Rust dependency graph | 311 s (inside one 399 s step) | 395 s (own layer; reused from cache after a master build) | | `paperclip-runner-core` compile | ~87 s | 65 s | | Cached base and CLI layer download | 236 s, **after** cargo finished | 257 s, **in parallel** with cargo | | Copy runnerd + provider pack | 20 s + 50 s | 9 s (`--link`) | | Provider pack stage | 143 s (parallel) | 152 s (parallel) | | Export + push | 82 s + 10 s | 81 s + 11 s | | Dependency layer size | n/a | `target` 852 MB + registry 333 MB (builder stage only) | With a warm dependency cache, a Runner source change compiles only the 65 s crate step. The critical path then becomes the ~4 min cached-layer download or the provider pack. The expected job time is about 7 to 8 min. I measure this after merge with an `image_only` dispatch (master first, to write the cache, then a Rust-only branch change). Disk: the instance starts with 38.76 GiB free. The dependency layer adds about 1.2 GB uncompressed to builder storage. The final image does not change. - `vitest run tests/runner-e2e/workflow-image-only.test.ts tests/runner-e2e/workflow-security.test.ts tests/runner-e2e/daytona-image.test.ts`: the new test and all image tests pass. Three tests in `workflow-security.test.ts` (`gates every provider-secret job with stable actor IDs`, `keeps provider credentials inside explicitly gated paid workflows`, `isolates private archive trust from workflows that execute target-branch code`) fail on `origin/master` without this change, because of `chat-catalog-testops-case.yml`. This pull request does not change them. - `node --test tests/runner-e2e/product-regression-testops.test.mjs`: 15/15 pass. - No local Docker build. The Dockerfile is measured with a branch-targeted CI dispatch whose paid gate was rejected. ## Risks - The dependency layer keeps the Rust `target` and registry trees in a builder-stage layer, and default-branch builds export it to the registry cache. This adds builder disk and cache upload when `Cargo.lock` changes. The build prints the size (`du -sk target /usr/local/cargo/registry`). - The dependency cache helps only after a default-branch build writes it. Branch builds read it but never write it. This is the existing cache policy. - `image_only` changes the workflow, so it works only after merge to master. - No check is removed. Lockfile verification, cosign sign and verify, the anonymous pull check, the provider-pack manifest match, the default-branch-only cache write and the actor allowlist are unchanged. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, through Claude Code with tool use (shell, GitHub CLI). Extended reasoning on. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7afb57c022 |
test(runner-e2e): repair restart-cell harness drift (#19538)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The Runner Product E2E suite proves that agent work survives a control-plane restart > - A restart chaos campaign found three restart cells that fail in the test harness, not in the product > - A harness failure hides the real restart result, so the campaign cannot classify these cells > - This pull request repairs the harness drift in those cells and names the one failure the harness cannot prevent > - The benefit is that these cells now test restart behavior, or fail with a clear cause ## Linked Issues or Issue Description Refs #14811 (connector setup became one screen). Refs #19475 (completion-update restart fixture). **What happened?** 1. `lifecycle-baseline.runner-codex.local.tool-review-restart` clicked a "Save and continue" button. #14811 removed that button. The cell failed during setup. After that fix, the cell also failed after the restart: the expanded review card does not survive the post-restart reload, so "Approve & run" was not visible. 2. `local-session-integrity.runner-acpx-claude.local.structured-question-restart-resume` failed with `page.goto: net::ERR_ABORTED` after the controller restart. The old document's Vite client reloads when the replacement server starts, and this aborts the test navigation. The code tolerated only `TimeoutError`. 3. `completion-updates.runner-acpx-claude.local.handoff-completion-restart` waited for the source agent to run an instructed wait command. Claude replied without the command (2/2 attempts), so the restart never occurred. The cell then failed with a generic timeout. **Expected behavior** Each cell reaches its restart boundary and tests restart behavior. If the boundary cannot be reached, the cell names the cause. **Steps to reproduce** Run `pnpm test:e2e:runner -- --max-parallel 1 --max-automatic-retries 0 --id <cell>` for each cell above on `master`. ## What Changed - `tests/runner-e2e/connection-reviews.ts`: accept the one-screen "Check link" step and the older "Save and continue" step. This uses the same regex approach as `connection-flow.ts`. - `tests/runner-e2e/runner.spec.ts` (tool review): click "Review request" after the restart reload, on the document that acts on the card. - `tests/runner-e2e/chat-restart.ts`: add `gotoAfterServerRestart`. It retries only `net::ERR_ABORTED`, within the caller's deadline and at most 5 attempts. It still accepts `TimeoutError`. It rethrows all other errors. The structured-question restart uses it. The existing sentinel and durable-state assertions still prove the new document. - `tests/runner-e2e/completion-updates.ts` and `completion-update-flow.ts`: add `completionRestartGateSkipped` and `completionRestartGateTimeoutDetail`. The gate poll stops at once with `restart boundary not reached (agent skipped gate)` when no completion turn is active and the turn that carries this task's Done facts (`chatCompletionUpdates`, as in `completionDelivery`) succeeded or published its reply before the gate reports `waiting`. A failed or cancelled turn is not blamed on the agent, because delivery can retry it; the timeout detail names it instead. The post-restart chat navigation now uses `gotoAfterServerRestart`. - A fixture-level gate is not possible without a product hook. The native run finalizer publishes the completion reply inside its own transaction, through the issue service closure. The isolated-server entrypoint can patch only class prototypes and HTTP responses, and the reply uses no tool call or HTTP request. - `tests/runner-e2e/README.md`: document the skipped-gate failure. - Docs: use `pnpm --filter @paperclipai/paperclip-runner run build:runner-binaries` in `tests/runner-e2e/README.md`, `tests/restart-chaos/README.md`, `tests/restart-chaos/main.ts` and `doc/DEVELOPING.md`. - No product code changes. ## Verification - Unit: `pnpm exec vitest run --config tests/runner-e2e/vitest.config.ts tests/runner-e2e/chat-restart.test.ts tests/runner-e2e/completion-updates.test.ts tests/runner-e2e/connection-reviews.test.ts` (36 passed). - Typecheck: `node scripts/typecheck-test-projects.mjs tests/runner-e2e` passed. - Live cells, local, `--max-parallel 1 --max-automatic-retries 0`, macOS, load average 35–65: - `lifecycle-baseline.runner-codex.local.tool-review-restart`: PASS (first run found the review-card reload drift, fixed in the second commit). - `local-session-integrity.runner-acpx-claude.local.structured-question-restart-resume`: PASS. A first attempt failed before the restart with `PRP command session.open timed out` at load ~36 (environment). - `completion-updates.runner-acpx-claude.local.handoff-completion-restart`: FAIL as designed, twice (before and after the review fix), with `restart boundary not reached (agent skipped gate): completion turn … ended succeeded without the reference gate reporting waiting`. Claude skipped the wait both times, so this cell still does not exercise a restart. ## Risks - Low risk. Test harness and docs only. - The navigation retry could hide a real abort. It is limited to `net::ERR_ABORTED`, 5 attempts and the deadline, and the test still asserts that a new document loaded. - The ACPX Claude completion restart cell still does not exercise a restart when the agent skips the gate. It now reports this cause by name. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with tool use (shell, file edit, browser test runs). ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fd4b197f83 |
fix(ui): hide runner selection during agent setup (#19517)
## Thinking Path > - Paperclip lets people manage AI agents for work. > - Agent setup connects a harness, model, account, and computer. > - The system already selects a qualified runner for a new agent. > - Setup still asks users to select that internal runner. > - The runner dropdown can be the only field in Advanced. > - This PR removes the dropdown and hides an empty Advanced section. > - Users keep their saved execution settings and see fewer setup controls. ## Linked Issues or Issue Description **What existing behavior does this improve?** Agent creation, onboarding, built-in agent setup, company import, and agent configuration. **Subsystem affected** The UI, setup error messages, tests, and Storybook fixtures. **Current behavior** Users can select a runner from a dropdown. Some setup screens place only that dropdown in Advanced. Errors can direct users to that control. **Proposed behavior** Remove runner dropdowns from setup and configuration. Use the existing automatic selection for new agents. Retain saved runner settings. Show Advanced only when other fields remain. **Reason and benefit** Users can configure agents without an extra internal execution choice. Setup also avoids an empty Advanced section. **Breaking changes** Users can no longer select a runner through these UI dropdowns. The API and CLI retain their runner options. Existing agents keep their saved execution settings. **Additional context** Related: [Default new agents to the qualified runner for their harness](https://github.com/paperclipai/paperclip/pull/15422). This PR changes the existing setup UI. It adds no harness or runtime default. The master merge retains the custom gateway placement from [#19510](https://github.com/paperclipai/paperclip/pull/19510). ## What Changed - Remove the shared runner dropdown and its setup/configuration callers. - Hide Advanced when it has no remaining fields. Keep command and argument settings where supported. Preserve custom gateway setup under Advanced on the Claude and OpenAI API key paths. - Keep setup failures visible and keep the retry action outside the removed control. - Update errors and documentation that referred to the removed dropdown. - Update component tests and Storybook fixtures for the new UI states. - Make Legacy first-task evals resume an API-configured setup record. Record that path separately from fresh automatic onboarding. ## Verification - Latest master integration (`187c0f6d6e5`): 276 onboarding/provider-connection tests passed across six files. UI typecheck and build passed. Token gates, query-error rendering checks, and the Storybook catalog check passed. The conflict resolution retains master's bounded connection tests and cancellation while removing the obsolete runner instruction. The first test command used the wrong working directory and failed before collecting tests; the corrected invocation from `ui/` passed. - Earlier branch verification passed: full workspace build and typecheck, 1,037 focused UI/service tests, 49 heartbeat provider contract tests, eval typecheck, and 30 map/catalog tests. - The full local `pnpm test:run` run passed all six chat integration shards (1,931 tests). It was stopped for the master merge before the complete suite finished. The [complete CI run](https://github.com/paperclipai/paperclip/actions/runs/42264662642) passed on the previous head `72e1699`. - Current head `a097c8369d079c839e78661a2206cfa62c5210e0`: [full CI](https://github.com/paperclipai/paperclip/actions/runs/42273168268) passed. PR checks total 55 passed and 3 skipped, with none failed or pending. Greptile reviewed this head at 5/5 with no actionable findings or unresolved review threads. - Live browser/provider evals have not run. The Legacy eval change has no live qualification claim. ## Risks - Removing the dropdown changes the setup UI. Existing API and CLI runner choices remain available. - Conditional Advanced sections must retain valid command and argument fields. - Legacy first-task evals now prove setup resumption. They do not prove the fresh-agent automatic default. - No schema change or data migration is included. ## Model Used OpenAI `gpt-6.1-sol`, through Codex in T3 Code. Reasoning effort: `xhigh`. The model used file editing, shell commands, and test tools. The harness does not expose the context window size. ## 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> |
||
|
|
f0006c7783 |
fix(workspaces): contain inventory deadline aborts (#19543)
A capture deadline can expire while file identity validation awaits the filesystem. Recheck cancellation before creating the inventory read stream so the expired capture rejects without terminating the server. All 224 affected capture and archive tests pass, including real Node subprocess cancellation regressions. Final-head CI passes 54 checks, and Greptile scores 5/5 with no unresolved findings. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
187c0f6d6e |
fix(runner-e2e): prepare the artifact oracle for every suite that runs it (#19518)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The Runner Full-Stack E2E workflow runs paid Daytona restart cells for the control-plane restart chaos campaign > - Manual run 42243751291 failed `daytona-restart-continuity.runner-codex.daytona.recover-controller` before task creation: `docker version` timed out after 10 seconds in the artifact oracle preflight > - The everyday flow requires the Python artifact oracle by case alone (`requiresEverydayArtifactOracle(caseId)`). The workflow step that starts Docker and pulls the oracle image also filters by suite, so suites that reuse `recover-controller` skip it > - On a cold RunsOn host the first `docker version` takes about 20 seconds (the everyday cell in the same run shows this), so the 10-second preflight fails when the workflow step did not run first > - This pull request gates the oracle step by case only, which matches the runtime requirement > - The benefit is that the Daytona restart cell reaches task creation instead of failing in setup ## Linked Issues or Issue Description **What happened?** Run https://github.com/paperclipai/paperclip/actions/runs/42243751291 job `daytona-restart-continuity.runner-codex.daytona.recover-controller` failed with `Artifact sandbox qualification failed before task creation: ... docker version ... timed out after 10 seconds`. The job log shows no "Prepare pinned Python artifact oracle image" step. **Expected behavior** Every cell whose flow runs the artifact oracle prepares Docker and the pinned oracle image before paid execution. **Steps to reproduce** Enumerate `runnerMatrix` cells where `requiresEverydayArtifactOracle(task.id)` is true. The cells in `daytona-restart-continuity`, `rolling-web-deployment`, `rolling-web-deployment-warm` and `remote-workspace-storage` do not match the old workflow condition. ## What Changed - `.github/workflows/runner-full-stack-e2e.yml`: the "Prepare pinned Python artifact oracle image" step is now gated by `matrix.caseId` only. The suite filter is removed. - `tests/runner-e2e/codex-ci-sandbox.test.ts`: the oracle gate test now checks every catalog cell, not only `everyday-workflows` cells, and rejects a suite filter. - `tests/runner-e2e/workflow-security.test.ts`: the pinned condition string follows the new gate. ## Verification - Red: with only the test change, `npx vitest run --config tests/runner-e2e/vitest.config.ts tests/runner-e2e/codex-ci-sandbox.test.ts` fails on `prepares the artifact oracle for exactly the stories that execute downloads`. - Green: with the workflow change, the same command passes (13/13). - `tests/runner-e2e/workflow-security.test.ts` locally shows the same 5 failures before and after this change (`pr-trusted.yml` step counts and `chat-catalog-testops-case.yml` credential checks). They do not involve this step. - Live proof: a narrow manual dispatch after merge. ## Risks - Low risk. Cells in four more suites now run a Docker version check, a pinned image pull and an inspect before paid execution. This adds about 20 to 30 seconds per cell. The image is pinned by digest. The step has no secrets. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, GitHub CLI, file edit). ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d4d3383c97 |
fix(runner): scope cleanup quarantine to the session's task and conversation (#19532)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Native runs (Codex, ACPX) execute through the runner's
`executeNativeSession`, which gates each new session behind cleanup of
earlier sessions
> - When a session close fails in a way that needs operator recovery
(`NativeSessionCloseUnrecoverableError`), the runner keeps a quarantine
entry for it
> - The admission gate keyed that quarantine by `[companyId, backend
kind, backend name]`, so one bad close blocked every later run of the
same backend in the whole company
> - In a restart chaos wave (two hard kills about 20 s apart), one Codex
session's close became unrecoverable. After that, every Codex run and
every Retry in the company failed in about 1 s with
`native_session_cleanup_quarantined`
> - This pull request scopes the gate to the resources that a retained
owner can still hold: its task (issue) and its provider conversation
(normalized session)
> - The benefit is that one failed close no longer stops unrelated
tasks, while the task and conversation that are really at risk stay
protected
## Linked Issues or Issue Description
**What happened?**
After two hard control-plane restarts about 20 s apart, one Codex
session close became unrecoverable. From then on, every Codex execution
in the company failed at admission with
`native_session_cleanup_quarantined: prior session cleanup requires
operator recovery…` (`retryQuarantinedSessionCleanups`). Board Retries
for four unrelated tasks were accepted and then failed at once.
**Steps to reproduce**
1. Run two native Codex tasks in one company (local runner).
2. Make one session's close throw `NativeSessionCloseUnrecoverableError`
(in the wave: a protocol failure after a second hard kill, then an
unsettled runner suspension).
3. Start or retry a different Codex task in the same company.
**Expected behavior**
Only work that shares a resource with the retained owner waits. Other
tasks run normally.
**Actual behavior**
Every later Codex run in the company fails within about 1 s, until the
server process restarts.
## What Changed
- `packages/paperclip-runner/src/native-session-runtime.ts`: the cleanup
domain is now `{ key, resources }`. `key` is the old backend key
(company, backend kind and name, optional remote cleanup scope).
`resources` are the session's issue and its normalized session (or its
run when it has no normalized session).
- The admission gate (`retryQuarantinedSessionCleanups`) and its
retained close owners block a new session only when the backend key
matches and at least one resource matches.
- The exact retirement paths (`completeRetainedNativeSessionCleanup`,
`completeTerminatedRemoteNativeSessionCleanup`,
`completeTerminatedLocalNativeSessionCleanup`) read the unchanged
backend key, so their behavior does not change.
- New regression test: an unrecoverable close on session A does not
block session B of another task. A session with the same issue, or with
the same normalized session, stays blocked.
Why this scope is safe: the resources that a retained owner can still
hold are its runner state root and its workspace. The server derives the
runner state root from the session scope (company, agent, workspace,
provider, normalized session). The workspace belongs to the task.
Different tasks already run concurrently on the same backend, so a
different task shares no resource with the retained owner that normal
concurrency does not already share.
## Verification
- Red before the fix: `pnpm --filter @paperclipai/paperclip-runner exec
vitest run src/native-session-runtime.test.ts -t "unrelated tasks of the
same backend"` failed with `native_session_cleanup_quarantined` for the
unrelated task (the RC-20 error).
- Green after the fix: the same test passes. The full file passes
(211/211).
- `pnpm --filter @paperclipai/paperclip-runner typecheck` passes.
## Risks
- A retained owner can no longer block a different task that shares a
project workspace. Concurrent tasks in a shared workspace are already
allowed, so this adds no new kind of overlap.
- The same task and the same conversation stay blocked. A Retry of the
task whose close failed can still be accepted and then fail at
admission. The trigger of that close is handled separately.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code
execution in Claude Code.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
07ddf9a5c2 |
fix(native): keep accepted results when runner close cannot prove suspension (#19525)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Native runs persist an accepted result first. The Runner then closes and suspends its session. > - Under heavy host load, the Runner close can fail to prove a durable suspension after the result is accepted. > - The executor then kept its finalization lease. The heartbeat finalizer got `native_finalization_lease_busy`, and the run ended `failed/adapter_failed`. > - The provider work was complete. Only Runner bookkeeping failed, so the run must not fail. > - This pull request hands the exact lease to finalization and completes the run from its accepted result. > - The benefit is that an accepted result stays accepted, independent of host load. ## Linked Issues or Issue Description **What happened?** A native ACPX Claude run asked a structured question. The control plane accepted its result (`Waiting for…`) and recorded `run.terminal` with `runTerminalState=succeeded`. The Runner close then took about 8.6 s. Its `runner.suspend` command ended `failed`, but the durable Runner state was `suspended` with a matching identity. The close threw `NativeSessionCloseUnrecoverableError`. The executor emitted `native.post_result_failure` (`category: runner_settlement_incomplete`, `resultAlreadyAccepted: true`) and rethrew `native_session_lease_lost`. Its finalization lease stayed held (its TTL is 20 minutes). `finalizeNativeRun` got `native_finalization_lease_busy`, and the run ended `failed/adapter_failed`. No restart was in progress. Host load average was about 40. **Expected behavior** After the control plane accepts a result, finalization bookkeeping must not fail the run. The run settles from its accepted result. Repair of the Runner checkpoint stays with the existing next-run admission rules. **Steps to reproduce** Run `pnpm exec vitest run src/services/heartbeat/runtime-execution.test.ts -t "accepted local result"` in `server/`. The test uses the real executor, the real control-plane result commit, and embedded PostgreSQL. The provider boundary accepts a result and then throws `NativeSessionCloseUnrecoverableError`. On the base revision, the finalization row keeps the executor lease (`phase: workspace_finalizing`) and the finalizer reports `lease_busy`. **Paperclip version or commit** Base `ff1d1dea5d`. The incident was on `819420a60a`. ## What Changed - `native-session-executor.ts`: after the rejected failure write proves an accepted result under the exact, unexpired lease, the executor releases that lease to finalization. It uses the same exact-authority predicates as the normal release. Before, the lease stayed held until its TTL expired. - `runtime-execution.ts`: for `runner_settlement_incomplete` on a local workspace (no workspace sync and no remote workspace authority), Paperclip records `workspace_finalize` as succeeded, finalizes the accepted result, and returns it to the ordinary completion path. - New `native-accepted-result-settlement.ts`: rebuilds the adapter result from the durable accepted result row, its terminal, the run's native session, and the accepted usage checkpoint. The result carries the closed `nativePostResultFailure` diagnostic. It contains no provider text. - Other post-result categories (for example `io_not_found`) keep their workspace-failure handling. They now also reach finalization at once, instead of after the lease TTL. - `doc/execution-semantics.md`: describes the settlement. ## Verification - Red, then green: `settles an accepted local result when runner close cannot prove suspension` and `keeps workspace failure handling for other accepted-result failures on a local workspace` in `server/src/services/heartbeat/runtime-execution.test.ts`. Both failed on the base revision with the executor lease still held. Both pass with this change. - Safety case `settles an accepted local result ... (workspace-check-failed)`: when the `workspace_finalize` check fails, the run keeps the existing failure path. Case `terminal-unreadable`: when the accepted result cannot be rebuilt, only a failed `workspace_finalize` record is written. - `pnpm exec vitest run src/services/heartbeat/runtime-execution.test.ts`: all pass (some cases time out at 30 s only under host load above 40; they pass on a narrow rerun). This includes the 14 existing post-result diagnostic and authority cases (expired, replaced owner, generation, attempt, boot, pid, process start, stopped, lock busy, and others). - `pnpm --filter @paperclipai/server typecheck`: pass. - Live cell `local-session-integrity.runner-acpx-claude.local.structured-question-restart-resume` (real ACPX Claude, local runner): 3 runs at host load average about 40. Runs 1 and 2 passed. Run 3 hit the fault naturally: the first run emitted `native.post_result_failure` (`runner_settlement_incomplete`) and now ended `run succeeded` (before this change it ended `failed/adapter_failed`). The cell then failed later, in the resume run after the answer: `runner_state_identity_mismatch: prior_owner_terminal_state_indeterminate; retirement_refused=target_not_sandbox_runner`. That is a separate follow-up (see Risks). ## Risks - A changed lease handoff. The executor releases the lease only after the rejected failure write proves an accepted result under the exact owner, attempt, controller generation, controller identity, and an unexpired lease. A stale or replaced owner does not release anything. A failed release is bounded and best effort. The old behavior then applies. - The run now completes as `succeeded` while the local Runner checkpoint is not proven suspended. The next run on the same session then meets the existing fence: the continuity-break retirement (`retireFinishedPerTurnRunnerdOwner`) accepts only sandbox Runners, so a local Runner is refused with `target_not_sandbox_runner` and its state root is quarantined. This PR does not change that fence. Extending the retirement to local Runners needs its own proof that the local Runner process stopped, and is a follow-up. - Settlement happens only when the accepted result and its terminal are rebuilt first, and the `workspace_finalize` record then succeeds. A failed branch or plugin workspace check keeps the failure path. - Synced and remote workspaces are not changed. They keep their copy-back retry path. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edits, test runs). Extended reasoning was on. ## 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 - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Related: #19126 added the closed post-result diagnostic that this change uses. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1206373a95 |
fix(environments): close Apex findings on managed plugin environments (#19526)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A managed instance gets its instance-level environments from the `PAPERCLIP_MANAGED_CONFIG` document, and #19508 added plugin environment entries to it > - Greptile's Apex review of #19508 arrived after it merged, with three findings > - A credential inside an array nested in an array passed the secret check and the response redaction > - Recovery of an archived environment listened on one worker handle, which a restart replaces, and its attempts were not tracked through idle checks and shutdown > - This pull request closes all three findings > - The benefit is that credentials never pass the check, an environment comes back after any worker restart, and shutdown never cuts off a recovery write ## Linked Issues or Issue Description Refs #19508 (the Apex findings this pull request fixes) **Subsystem affected** Server environments: the managed-config parser, the environment routes' response floor, the managed environment bootstrap, and the plugin worker manager. **Problem or motivation** Greptile's Apex review of #19508 found three problems. (1) `findSecretLikeConfigKey` and `redactSecretLikeConfigKeys` skip array elements that are arrays, so `{"groups":[[{"apiToken":"..."}]]}` passes the boot check and is echoed to restricted readers. (2) Recovery subscribes to `ready` on one worker handle. A restart such as a settings save replaces that handle, so the new worker's `ready` never reaches the listener, and the environment stays archived. (3) A recovery attempt is fire-and-forget. The worker manager's work token ends when the worker replies, but the database write continues, so an idle check or the database shutdown can overlap it. **Proposed solution** Walk arrays at any depth in both functions. Add a manager-wide `onWorkerReady` subscription and use it for recovery. Run every recovery attempt through a controller that tracks it as idle work and that shutdown stops and awaits before the database closes. **Alternatives considered** Re-subscribing on each new handle after a restart. That needs a hook in every restart path. The manager-wide subscription covers all of them in one place. **Roadmap alignment** This only fixes code that #19508 added or touched. It adds no product surface. ## What Changed - `server/src/services/managed-config.ts`: `findSecretLikeConfigKey` walks arrays at any depth (new `findSecretLikeConfigKeyInValue`). This covers sandbox `config` and plugin `driverConfig`. - `server/src/routes/environments.ts`: `redactSecretLikeConfigKeys` walks arrays at any depth (new `redactSecretLikeConfigValue`). - `server/src/services/plugin-worker-manager.ts`: new optional `onWorkerReady(listener)` on `PluginWorkerManager`. It fires on every worker `ready`, including a replacement handle from a restart. A listener error is logged and does not affect the worker. - `server/src/services/managed-environments.ts`: - Sandbox and plugin recovery subscribe through `subscribeWorkerReady`. It prefers `onWorkerReady` and falls back to the handle when the manager has no such method. - New `createManagedEnvironmentRecovery()` controller. Each attempt runs inside `trackIdleWork` for its whole duration. `stop()` removes every `ready` subscription, refuses new attempts, and awaits unfinished ones. - `server/src/index.ts`: creates the controller, passes it to `applyManagedEnvironments`, and awaits `stop()` during shutdown, after the delivery workers stop and before the application teardown and database close. ## Verification - `cd server && npx vitest run src/__tests__/managed-config.test.ts`: a secret-like key in an array nested in an array refuses boot, for plugin `driverConfig` and sandbox `config`. - `npx vitest run src/__tests__/environment-routes.test.ts`: a nested-array credential is removed from the response, and other nested values stay. - `npx vitest run src/services/managed-environments-plugin-entries.test.ts`: - With `onWorkerReady`, recovery never consults the handle. It ignores another plugin's `ready`, re-ensures on this plugin's `ready`, and then unsubscribes. - An attempt counts in `idleWorkSnapshot()` until its ensure settles. `stop()` unsubscribes, waits for the unfinished attempt, and later `ready` events start nothing. - `npx vitest run src/services/managed-environments.test.ts src/__tests__/managed-plugin-environments.test.ts`: pass. - `pnpm --filter @paperclipai/server typecheck` passes. - I did not run the full `pnpm test:run`, `pnpm -r typecheck` or `pnpm build`. CI covers them. ## Risks - The redaction now also walks nested arrays in sandbox config responses. Only credential-shaped keys are removed; other values are unchanged. - The boot check now refuses a document that hides a secret-like key in a nested array. A valid document has no such key. - `onWorkerReady` is optional, so other `PluginWorkerManager` implementations and test fakes keep working. - Shutdown now waits for an unfinished recovery attempt. An attempt is one worker call plus one short transaction. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, through Claude Code with tool use (file edits, shell, test runs). ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3b3037aab5 |
fix(ci): run the sandbox provider tests in required CI (#19533)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Sandbox provider plugins (Daytona, E2B, Cloudflare, Kubernetes, Modal, and others) run agents in remote sandboxes > - The providers are outside the root pnpm workspace, so `pnpm test:run` and the CI test lanes never run their Vitest suites > - paperclipai/paperclip#18839 typechecks those tests, but a test that fails at runtime still fails nothing > - The providers have about 1,040 tests, and they finish in about 15 seconds on Linux > - This pull request adds a required `Sandbox provider tests` job that runs them when a change can affect them > - The benefit is that a broken provider, plugin SDK, or shared contract fails CI on the pull request instead of in a release or a paid end-to-end run ## Linked Issues or Issue Description Refs #18839 **What happened?** No CI job runs the sandbox provider tests. `runner-full-stack-e2e.yml` builds the Daytona plugin for a paid nightly run, but it does not run any provider's `pnpm test`. **Expected behavior** A pull request that can break a provider runs that provider's tests, and a failure blocks `ci / verify`. **Steps to reproduce** Add `expect(1).toBe(2)` to a test in `packages/plugins/sandbox-providers/e2b/src`. Open a pull request. CI stays green. ## What Changed - Added `scripts/test-sandbox-providers.mjs` and its tests. The script installs the providers through `typecheckTestPrograms` with no programs, because that function already owns the provider install and the plugin SDK link. Then it runs each provider's `pnpm test`, four at a time, and prints each provider's output as one block. It fails if a suite fails or if no provider has a test script. - The script skips `cloudflare/bridge-template`, because Cloudflare's Vitest config already runs those tests. - Added the `sandbox_provider_tests` job to `.github/workflows/pr-trusted.yml`, and made `ci / verify` require its result. - Added the `//#ci:sandbox-providers` task to `turbo.json` and its lane to `.github/ci-task-matrix.json`. The CI planner runs the job only when a change touches `packages/plugins/sandbox-providers/**`, `packages/plugins/sdk/**`, or `packages/shared/**`. `packages/shared` is the plugin SDK's only workspace dependency. Global CI inputs, such as `scripts/**`, `.github/**`, `pnpm-lock.yaml`, or any `package.json`, still select every job. - The job turns off core dumps. A Daytona capacity test kills a process at a file-size limit inside the exported directory. If core dumps are on, the core file joins the export, and the test sees 12 files instead of 11. - Updated the job lists in `scripts/__tests__/e2e-shard.test.mjs` and the job count in `.github/scripts/tests/ci-profile.test.mjs`. Added planner tests to `.github/scripts/tests/ci-plan.test.mjs`. One of these tests fails if the plugin SDK gets a new workspace dependency that is not in the job's inputs. - Added the new test file to `test:release-registry`, and documented the script in `doc/DEVELOPING.md`. ## Verification - `node scripts/test-sandbox-providers.mjs` on macOS: 8 of 9 suites pass in 31.6 s, including the install. Kubernetes fails 12 `file-sync` tests, because macOS has no `/proc/self/fd`. Some Daytona tests skip, because they need GNU `tar` and coreutils. - The same script in a Linux container (`node:24`, a non-root user, core dumps off): all 9 suites pass, with 1,012 tests passed and 26 skipped. The script took 15 s after the install. - Kubernetes: 205 passed. - Daytona: 590 passed and 16 skipped. - With core dumps on, Daytona's `preserve literal capacity repair` test fails. The export includes a `core` file. With `--ulimit core=0`, it passes. - To look for timing failures under load, I ran the whole script 12 more times in the container, limited to 2 CPUs (8 runs) and 1 CPU (4 runs). All 12 runs passed. They took 45 to 76 s on 2 CPUs and 73 to 102 s on 1 CPU. - `ubuntu-latest` has `zstd`, and my container did not, so some Daytona tests skipped in those runs. With `zstd` installed, Daytona passed with 598 tests and 8 skipped, and only the live tests skipped. One earlier `zstd` run failed while the stress runs used the same CPUs, and I did not capture its output. The next run passed. - `node --test` on every test that reads `pr-trusted.yml`, the CI planner, or the task matrix: 816 passed. These include `.github/scripts/tests/*.test.mjs`, `scripts/__tests__/e2e-shard.test.mjs`, `scripts/ci/test-selection-workflow.test.mjs`, and the new tests. - `pnpm run test:release-registry` on macOS: 251 of 255 pass, including the new tests. The 4 failures are `wait_for_npm_package_versions` tests in `scripts/release-lib.test.mjs`. They fail the same way on master on the same machine, and CI runs this suite on Linux. - `actionlint` 1.7.7 reports no errors on the changed workflow. - I called `planTasks` from `.github/scripts/ci-plan.mjs` with one changed file at a time. `server/src/index.ts` and `ui/src/App.tsx` leave `sandbox_provider_tests` off. `packages/plugins/sandbox-providers/e2b/src/plugin.ts` and `packages/shared/src/index.ts` turn it on. This pull request cannot run its own new job. `pr.yml` calls `pr-trusted.yml@master`, so the job first runs on pull requests after this one merges. After merge, check the first pull request that changes a provider or `packages/shared`. ## Risks - **First real run is after merge.** I measured the time on Linux in Docker, not on a CI runner. CI runners are about 2 times slower than my machine, so I expect about 2 to 3 minutes for the job, including the dependency restore. The job runs in parallel with the other jobs, and the slowest required job takes about 13 minutes. So the job should not make CI slower. - **Open provider pull requests.** After this merges, open pull requests that change a provider run its tests. If those tests fail, `ci / verify` blocks them until they are fixed. - **`packages/shared` changes often.** Any change there runs this job. A Linux-only provider failure would block those pull requests. All suites pass on Linux today. - **One unexplained Daytona failure.** One Daytona run with `zstd` failed under CPU contention, and I did not capture its output. Some Daytona tests have short deadlines. If the job is flaky after merge, quarantine the failing test or revert the job, and do not leave `ci / verify` red. - **Path rules can be too narrow.** If a provider starts to import another workspace package, a change to that package does not run this job. The plugin SDK dependency test covers the SDK. It does not cover a provider that imports a workspace package directly. Today no provider does. - **Overlap with paperclipai/paperclip#19489 and paperclipai/paperclip#19522.** Both pull requests change the provider install in `scripts/typecheck-test-programs.mjs`. This pull request does not edit that file. It calls the same install function, so it uses their install changes when they merge. All three pull requests edit the same section of `doc/DEVELOPING.md`, so a small text conflict is possible. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Anthropic Claude Opus 5.5 (`claude-opus-5-5`), in Claude Code with tool use (shell, file edits, GitHub CLI, Docker). Extended reasoning was on. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
874fed09a4 |
test(restart-chaos): enable isolated workspaces for repo tasks (#19536)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The restart chaos harness (`tests/restart-chaos`) restarts the control plane while agents work, then grades the result > - Rule R2 says files an agent wrote before a restart must still be present afterwards > - Repo workloads failed R2 with `pre_fault_files_lost`. The first run worked in the shared project checkout, not in its own worktree > - The seed sets a `git_worktree` project policy, but the server applies that policy only when the instance `enableIsolatedWorkspaces` setting is on. The seed did not turn it on > - This pull request makes the seed turn on the setting (as users do on the Experimental page, and as `tests/runner-e2e` does) and fail if the server does not confirm it > - The benefit is that repo tasks get their own worktree, so one task's files cannot appear in another task's R2 snapshot ## Linked Issues or Issue Description Refs #19477 **What happened?** In a restart chaos wave, two repo tasks (B1 and B5) ran at the same time. Both ran in the project primary checkout (`strategyType: project_primary`). Task B1's `NOTES-*.md` file and its `cli.py` edit appeared in the B5 task's workspace before any fault. After a Retry, the B5 task moved to a fresh attempt directory, and R2 reported the B1 file as lost from B5. **Expected behavior** Each repo task gets its own git worktree, as the harness project policy asks. The R2 snapshot of a task contains only that task's files. **Steps to reproduce** 1. Run `tests/restart-chaos/main.ts --env local --adapters codex --workloads B1,B5 --concurrency 2`. 2. Read `currentExecutionWorkspace` for each task. It shows `project_primary` and the fixture checkout as `cwd`. 3. Read `GET /api/instance/settings/experimental`. `enableIsolatedWorkspaces` is `false`. **Root cause** `server/src/services/heartbeat.ts` reads `enableIsolatedWorkspaces` and `gateProjectExecutionWorkspacePolicy` (`server/src/services/execution-workspace-policy.ts`) returns `null` when the setting is off. The project policy is then ignored, and the task resolves to the shared primary checkout. `tests/restart-chaos/seed.ts` created the project policy but did not turn on the setting. ## What Changed - `tests/restart-chaos/seed.ts`: before it creates the project, the seed sends `PATCH /api/instance/settings/experimental` with `enableIsolatedWorkspaces: true`. If the response does not confirm the setting, the seed stops with an error. It records a `seed_isolated_workspaces` timeline entry. - `tests/restart-chaos/seed.test.ts`: new unit tests. One test checks that the seed turns on the setting before it creates the `git_worktree` project. One test checks that the seed refuses to continue when the server does not confirm the setting. - `tests/restart-chaos/README.md`: the `seed.ts` row says that the seed turns on the setting, and why. ## Verification - Red before the fix: `pnpm exec vitest run --config tests/restart-chaos/vitest.config.ts seed.test.ts` failed 2/2 (`expected -1 to be greater than or equal to 0`). - Green after the fix: the full restart-chaos unit suite passes, 5 files and 42 tests. - Live wave on this branch (`16f5c8c5d7`, local runner, native Codex, macOS, load average about 40): `tests/restart-chaos/main.ts --env local --adapters codex --workloads B1 --tasks 3 --concurrency 1 --faults K1 --seed 21` (wave `w-20261106T181436-s21`). The timeline shows `seed_isolated_workspaces`. Result: 3/3 pass. Each task crossed a SIGKILL restart. R1, R2, R4, R5, R6 and the workload oracle passed for each task, with no lost files. - Each task's pre-fault file list now has a `.git` file (a git worktree, not the primary checkout) and only its own `NOTES-*.md` file. In the failing wave, the B5 task's list had the B1 task's `NOTES-*.md` file. - Not exercised: no task needed a Retry in this wave, so the Retry-to-fresh-attempt path did not run. - Ledger preflight: install and runner builds passed. The provider-free restart suite had 2 failures in `runnerd-codex-transport-warm-attachment.test.ts` at load average 44; a rerun of that file passed 18/18. ## Risks - Low risk. The change is in the test harness only. It changes no product code. - The harness instance is temporary, so the experimental setting does not reach a real instance. - Repo tasks now run in git worktrees. Earlier waves ran in the shared checkout, so R2 results for repo workloads from earlier waves are not comparable with new waves. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with tool use (shell, file edits). Standard context window; reasoning enabled. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e3d8f42e19 |
fix(ui): recover stalled onboarding connection checks (#19505)
Bound the complete onboarding connection check to two minutes, abort its requests on timeout, and ignore late results. Keep the saved account available for retry and resume its matching unfinished setup after refresh. Verified 242 focused tests, workspace typecheck and build, and the complete hosted CI check set. Greptile is 5/5 with no unresolved review comments. Local full-UI timing failure and interrupted CI runner attempts remain documented in the PR evidence. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
beaa9a622d |
fix(native): re-bootstrap a runner killed before session.open (#19524)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Native runs start a local `paperclip-runnerd` process. The control plane keeps a durable state root for each session. > - When the control plane restarts, restart recovery classifies each live native run. A run whose runner died before any provider identity existed gets the `bootstrap_incomplete` disposition. Recovery then quarantines the old state root and starts the bootstrap again. > - The executor accepts that disposition only when the durable state root proves that no provider work started. The old proof required `connectionCount === 0` and every command `pending`. > - A restart chaos wave killed the server about 2 s after runnerd started. The runner had connected and completed `run.prepare`, but `session.open` was still pending. No event was committed, so no provider session or turn existed. > - The proof rejected that root. The run failed with `runner_state_identity_mismatch: bootstrap_not_proven_incomplete`, and the task stopped with `native_continuation_requires_reconciliation`. Nothing continued automatically. > - This pull request widens the proof to the exact window where provider work cannot have started, and keeps every other shape fail-closed. > - The benefit is that a restart during native runner bootstrap continues automatically for every native provider. ## Linked Issues or Issue Description **What happened?** A control plane restart that lands after the native runner connected and acknowledged `run.prepare`, but before `session.open` completed, fails the run with `adapter_failed` and `runner_state_identity_mismatch: bootstrap_not_proven_incomplete`. Startup recovery chose `bootstrap_incomplete` (controller generations 2 and 3), but the executor proof in `migrateRunnerdStateRootForExecution` refused it. The durable control-plane state had `connectionCount: 1`, `run.prepare` completed, `session.open` pending, no committed events, and `ackedSourceSeq: 0`. The runner journal had `nextSourceSeq: 1`, an empty outbox, and the provider state was `prepared` with no provider session ID. **Steps to reproduce** 1. Start a native task (seen with OpenCode on a local runner; the window applies to every native provider). 2. Kill the control plane about 2 s after runnerd starts, after `run.prepare` completes and before `session.open` completes. 3. Let the control plane restart. **Expected behavior** Recovery quarantines the state root and starts the bootstrap again. The task continues without a user action. **Actual behavior** The run fails and the task is blocked with `native_continuation_requires_reconciliation`. ## What Changed - `runnerdStateIsIncompleteBootstrap` (native-session-executor.ts) now also accepts a connected root when all of these are true: no committed event, `ackedSourceSeq === 0`, only `run.prepare` and `session.open` commands, `session.open` not completed, `run.prepare` pending or completed, no warm transition or staged attachment, and the bootstrap was not adopted. - When `runner/runner-state.json` exists, the runner journal must agree: empty outbox, `nextSourceSeq === 1`, `ackedSourceSeq === 0`, and no completed `session.open` or other processed command. An unreadable runner journal stays fail-closed. This covers events that the runner emitted but the controller never committed. - The remote "claim an untouched session" path keeps the old never-connected proof (`runnerdStateIsUnconnectedBootstrap`), so its behavior does not change. - Tests: a red test with the production state shape (connected, `run.prepare` completed, `session.open` pending), plus fail-closed counter-tests for a completed `session.open`, committed or acknowledged events, a turn command, a failed `run.prepare`, an adopted bootstrap, a warm transition, an uncommitted runner event, a runner-completed `session.open`, a runner-processed turn, and a torn runner journal. ## Verification - Red before the fix: `pnpm exec vitest run src/services/native-runtime/native-session-executor.test.ts -t "native incomplete-bootstrap evidence"` failed on "allows a fresh bootstrap after connect and run.prepare when session.open never completed" (`expected false to be true`). Green after the fix: 13/13. - The proof run against the kept durable state root from the failing wave (read only) returns `true` after the fix. - Related suites: `native-session-executor.test.ts` and `native-restart-recovery.test.ts` filtered to `bootstrap|restart|remote|unstarted|untouched`: 301 passed. - `tsc --noEmit -p server/tsconfig.json` passes. - Live restart chaos proof: see the comment on this PR. ## Risks - The proof now accepts a root where the runner received `session.open` but did not complete it. The runner can have started a provider process or created an empty provider session in that window. Recovery only proceeds when restart classification already proved that the runner process and its group are dead and no known provider process is alive. No provider turn can exist, because the controller never issued a turn command. The worst case is an orphaned empty provider session, not repeated work. - Every state with committed or acknowledged events, a completed `session.open`, a turn, a warm transition, an adopted bootstrap, or an unreadable runner journal still fails closed. - Out of scope: when the proof fails legitimately, the task offers the existing execution-blocker Retry. In the failing wave the issue interruption also showed a budget wait (`userAction: budget`). That is the RC-06 accounting issue and belongs to a separate fix. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Anthropic Claude Opus 5.5 (`claude-opus-5-5`), used through Claude Code with tool use (shell, file edits, test execution). Default context window. Extended reasoning was available. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a6c2280630 |
fix(ui): dock the announcement card in the sidebar so it never covers the task thread (#19511)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - A new user lands on their first task after onboarding. The task
thread and the composer are the core of that screen.
> - The app shows an optional in-app announcement (now "Introducing
Connectors"). On desktop the card floats at the bottom-left, 22rem wide,
but the sidebar is only 240px wide.
> - In the Nov 5 cloud onboarding test, the tester said: "onboarding
popup blocks core real estate".
> - At 1280x800 the card covers the left part of the composer. At
1440x900 and 1280x800 it covers the task thread area and the sidebar
account menu.
> - This pull request docks the card inside the desktop sidebar column,
in normal layout flow, above the account menu.
> - The benefit is that the card stays visible and dismissible, but it
can never cover the thread or the composer.
## Linked Issues or Issue Description
**What happened?**
On a new company's first task, the "Introducing Connectors" announcement
card floats over the task thread. At 1280x800 it covers the left part of
the composer.
**Steps to reproduce**
1. Create a new company and open its first task at 1280x800.
2. Wait 3 seconds for the announcement card to show.
3. See the card on top of the composer and the account menu.
**Expected behavior**
Optional announcements do not cover the task thread or the composer.
**Evidence**
The staging test company (Claude subscription path) has a local
dismissal for `2026-09-14-connectors`. The live feed at
`pages.paperclip.ing/announcements/v1/current.json` serves that
announcement. No other first-use overlay showed on the first task or the
dashboard. Toasts and the task-detail error notice are transient and are
not first-use popups.
## What Changed
- `AnnouncementWell` has a `placement` prop: `sidebar` (in-flow, no
`fixed`) or `floating` (the earlier fixed placement).
- `Layout` renders the `sidebar` placement inside the desktop
`SidebarShell`, above the account menu. It renders the `floating`
placement only on mobile, where it stays above the bottom nav.
- While the desktop sidebar is collapsed to a rail or closed, the card
waits. It shows again when the sidebar is expanded.
- In the sidebar, the card is capped at half the window height
(`--announcement-sidebar-max-height: 50dvh`) and scrolls. It uses the
existing compact image height token, so the sidebar navigation keeps
most of its height.
- New e2e spec `tests/e2e/announcement-placement.spec.ts`. It makes a
new company with a team lead and a first task. It serves the live
connectors announcement through the app's announcement API. At 1440x900
and 1280x800 it checks that the card does not overlap the composer or
`#main-content`, that the card is fully in the viewport and dismissible,
and that it does not overlap the account menu trigger and the Account
dialog opens.
- Unit test for the two placements in `AnnouncementWell.test.tsx`.
**Product decision for Dotta:** this PR does not hide announcements on
first use. Another option is to hold all announcements until the user
has used the product for some time (for example, after the first task is
done or after the first day). That is a one-line gate in
`AnnouncementWell`'s `enabled` condition, if we want it.
## Verification
Red on `master` (
canary/v2026.1106.0-canary.18
|
||
|
|
35280516c0 |
fix(ci): set up pnpm in the Vitest selection job like other trusted jobs (#19527)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Trusted CI workflows set up pnpm in a fixed way. Node comes first, and npm audit, fund and update-notifier calls are off. > - A policy test in the Runner support suite checks every pnpm setup in `pr-trusted.yml` for this shape. > - PR #18348 added a pnpm setup to the new Vitest selection job. That setup did not have the npm overrides, and setup-node was not directly before it. > - PR CI does not run the Runner support suite. So the policy test failed on master, and nobody saw it. > - This pull request gives the selection job the same pnpm setup as the other eight jobs. > - The benefit is that every trusted pnpm setup follows one rule again, and the policy test passes. ## Linked Issues or Issue Description Refs #18348 (added the job). Refs #18625 (its description noted the "nine bootstraps where eight are expected" failure). Related: #19229 also edits the pnpm steps in `pr-trusted.yml`. **What happened?** `pnpm test:e2e:runner:unit` fails on master in two tests: "keeps pnpm bootstrap registry telemetry out of trusted workflow setup" and "installs a modern Node runtime before every trusted pnpm bootstrap". `pr-trusted.yml` has nine pnpm setups. The ninth, in `test_selection`, has no `NPM_CONFIG_AUDIT`, `NPM_CONFIG_FUND` or `NPM_CONFIG_UPDATE_NOTIFIER` override. The step directly before it is the eligibility check, not setup-node. **Expected behavior** Every trusted pnpm setup has the three overrides, and setup-node is the step directly before it. **Steps to reproduce** Run `pnpm test:e2e:runner:unit` on master. Read the two failures above. ## What Changed - `pr-trusted.yml`, job `test_selection`: add "Setup Node.js for pnpm bootstrap" directly before "Setup pnpm". Both steps use the existing eligibility condition. The pnpm step now sets the three npm overrides. Both actions keep their pinned SHAs, with version comments added. - `tests/runner-e2e/workflow-security.test.ts`: the expected count of pnpm setups is now set per workflow: 8 for `runner-full-stack-e2e.yml` and 9 for `pr-trusted.yml`. The checks on each setup do not change. ## Verification - On this branch, the two pnpm tests in `workflow-security.test.ts` pass. Before this change, they failed with "expected 8, got 9". - The other three failures in that file are fixed separately in the companion PR that repairs the rest of the suite. - `node --test scripts/ci/test-selection-workflow.test.mjs`: 4 passed. - The workflow parses as YAML. The `test_selection` steps are now: checkout, setup-node, eligibility check, setup-node for pnpm, setup pnpm, restore dependencies, select, upload, publish. - `pr.yml` calls `pr-trusted.yml@master`. So this PR's own CI runs the master copy of this workflow. The change runs in CI only after merge. ## Risks - Low risk. The new setup-node step runs again with the same Node 24 version. It adds about one second when selection is eligible. When selection is not eligible, both steps are skipped, as before. - `test_selection` has `continue-on-error: true`. If the new steps fail, the original test matrix still runs. - #19229 removes `version: 9.15.4` from every pnpm step in this file. If both PRs merge, the second one needs a small rebase on this step. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), by Anthropic, in Claude Code with tool use (shell, file edits, git, GitHub CLI). ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b85557a122 |
Fix OpenAI subscription waiting labels (#19521)
Show “Waiting for auth…” while OpenAI subscription sign-in completes in onboarding and the shared provider connection screen. Preserve Claude’s code-entry label and align connection previews and existing regression expectations. Verified 246 component tests, workspace typechecks, the full production build, token gates, and all 54 applicable CI checks. Greptile scored the change 5/5 with no unresolved comments. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
17b4f972bf |
chore(lockfile): refresh pnpm lockfiles (#19534)
## Thinking Path > - Paperclip uses a dependency lock for reproducible builds. > - Dependency changes on master need matching lock entries. > - Paid eval reports also pin a reviewed offline correction. > - Reports must install before paid model work starts. > - This automated PR keeps the lock and report proof together. ## Linked Issues or Issue Description **What happened?** Dependency manifests on master changed after the last committed lock refresh. **Expected behavior** Frozen installs must match those manifests. Eval reports must retain matching offline lock proof. **Steps to reproduce** Run a frozen install before and after this lock refresh. ## What Changed - Refresh the dependency lock from the manifests on master. - Refresh the sandbox provider lockfiles from their manifests on master. - Include the matching offline report correction and hashes. ## Verification - Pinned pnpm resolution completed with lifecycle scripts disabled. - The report proof refresh accepted the reviewed committed baseline. - The workflow rejected changes outside the lock and report proof files. ## Risks The lockfile bot merges this PR without CI or review when every added package version is at least 3 days old on npm. Otherwise the PR waits for a maintainer, and the daily refresh tries again. The bot merges only the exact commit this workflow generated from manifests already reviewed on master. Unknown report lock baselines remain blocked. This update changes no permissions or paid enable gates. ## Model Used None — generated by pnpm and the deterministic report-lock updater. ## Checklist - [x] Thinking path and problem description included - [x] Generated lock and matching report proof included - [x] Release-age gate decides between bot merge and maintainer review Co-authored-by: lockfile-bot <lockfile-bot@users.noreply.github.com> |
||
|
|
e3041f5481 |
fix(ci): let the lockfile bot own the sandbox provider lockfiles (#19522)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The repository has a supply-chain rule: contributors do not commit lockfiles. A bot and verified Dependabot commits write them. > - The rule checks only the root `pnpm-lock.yaml`. > - The sandbox providers are outside the root workspace, so they have two more pnpm lockfiles. Pull requests edited them by hand, for example #12791, #18839 and #19489. > - This pull request makes the same rule and the same bot cover those two lockfiles. > - The benefit is one lockfile rule for every managed pnpm lockfile, and no lockfile edits in contributor pull requests. ## Linked Issues or Issue Description Refs #19489 **What existing behavior does this improve?** The lockfile policy and the lockfile refresh bot. **Subsystem affected** CI: `.github/scripts/lockfile-trust.mjs`, `.github/workflows/refresh-lockfile.yml`, and the sandbox provider installs. **Current behavior** `lockfile-trust.mjs` blocks contributor edits to `pnpm-lock.yaml` only. A contributor can edit `scripts/typecheck-sandbox-providers/pnpm-lock.yaml` or `packages/plugins/sandbox-providers/daytona/pnpm-lock.yaml`, and CI passes. The refresh bot does not update these files. `typecheck:test-programs` fails on a stale provider lockfile and tells the contributor to commit a new one. **Proposed behavior** The policy blocks contributor edits to all three lockfiles. The refresh bot regenerates all three after merge. CI resolves a stale provider lockfile in place for the run, as it does for the root lockfile. **Reason and benefit** Lockfile content comes only from the bot or from verified Dependabot commits. Contributors do not need to run pnpm in special folders. **Breaking changes** None. A pull request that edits a provider lockfile now fails the policy check. Remove the lockfile change from the pull request. The bot commits it after merge. ## What Changed - `lockfile-trust.mjs` now exports `LOCKFILES`: the root lockfile, the shared sandbox provider lockfile, and the Daytona lockfile. The policy blocks contributor edits to each of them. A Dependabot pull request must match a verified Dependabot commit for each lockfile that it changes. The `merged` command and the reporting-lock proof still use only the root lockfile. - `check-pr-lockfile.mjs` (advisory check) uses the same list. - `refresh-lockfile.yml`: - A job-level `LOCKFILES` env lists the same three files. A test checks that it equals the policy list. - A new step regenerates the two provider lockfiles with `--resolution-only --ignore-scripts --no-frozen-lockfile`. - The unexpected-changes check, the change check, and `git add` cover all three lockfiles. - The release-age gate checks the added versions in every lockfile. - A held refresh saves all three lockfiles at their repository paths, with a `source-locks.sha256` file. The next run reuses them only if every master lockfile is unchanged. An artifact in the old format is not reused. - The PR title is now `chore(lockfile): refresh pnpm lockfiles`. - `check-lockfile-release-age.mjs` accepts more than one `--base`/`--head` pair. - `scripts/typecheck-test-programs.mjs`: if the frozen install of the provider workspace fails, the script resolves the lockfile in place and installs frozen again. - `scripts/build-standalone-public-packages.mjs`: the same fallback applies to a package with a committed lockfile (Daytona). `release.sh` already restores tracked files when it exits. - `doc/DEVELOPING.md` and `doc/RELEASE-AUTOMATION-SETUP.md` describe the managed lockfiles. They also say that the Pi and Kimi distribution lockfiles stay hand-managed, because they hold attested provider pins. ## Verification - `node --test .github/scripts/tests/*.test.mjs scripts/__tests__/build-standalone-concurrency.test.mjs`: 731 tests pass. New tests cover: - the policy for provider lockfiles, including a real git history with a Dependabot commit and a later hand edit - the workflow and policy lists match - the unexpected-changes check, run with bash against a fixture - a held refresh round trip, and its invalidation when a provider lockfile changes on master - the release-age gate across lockfile pairs - the inline fallback in both install scripts - `node --test scripts/typecheck-test-programs.test.mjs`: the install tests pass. Two tests fail locally only because this worktree has no root `node_modules` (no `tsc`). They also fail without this change. - I ran the new bot commands on master with pnpm 9.15.4. No package version changed. pnpm 9.15.4 removes `libc:` fields and adds `deprecated:` notes, so the first refresh is a metadata-only change. The release-age gate passes it because it adds no versions. - I ran the release-age and held-lock steps locally with bash against this commit. The gate reported `eligible=true`, and `sha256sum --check` accepted the saved hashes. - The workflow YAML parses, and `bash -n` accepts every `run` block. - Not testable before merge: the policy job loads `lockfile-trust.mjs` from the base branch, so the new rule applies only to pull requests opened after this merges. The refresh workflow runs only on master. ## Risks - The release build now resolves a stale Daytona lockfile in place, from the registry. The root release install already resolves without a frozen lockfile, so this is the same level of trust. - The paid runner e2e job still installs Daytona with `--frozen-lockfile`. After a Daytona manifest change merges, its Daytona cases fail until the bot's refresh merges. This matches the rule that paid jobs refuse a new root lockfile until its refresh merges. - Dependabot pull requests for the provider entries in #19489 change a provider lockfile. They pass only if each lockfile matches a verified Dependabot commit, the same as for the root lockfile. - Low risk otherwise: the bot keeps its existing token, environment, and merge rules. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use (shell, file edits, and git). ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1485d3de1b |
fix(ui): hold announcements until the onboarding first task is finished (#19514)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - A new user's first session is the onboarding first task. The agent asks its onboarding questions in that task thread. > - The app also shows an optional in-app announcement. The live feed now serves "Introducing Connectors" (`2026-09-14-connectors`). > - The announcement well only waits while the onboarding wizard dialog is open. It shows 3 seconds after the user lands on the first task. > - In the Nov 5 cloud onboarding test (Claude API path), the tester said: "Don't show the connectors upsell until the second session. It's blocking the user flyout and potentially the onboarding questions." > - This pull request holds announcements until the company's onboarding first task is done or cancelled. > - The benefit is that a new user sees the onboarding questions and the account menu without an upsell on top of them. ## Linked Issues or Issue Description Refs #19511 (places the card in the desktop sidebar, above the account menu. That PR keeps the card off the account menu after onboarding. This PR adds the first-session gate that #19511 leaves open.) **What happened?** On a new company's onboarding first task, the "Introducing Connectors" card shows 3 seconds after the page loads. At 1440x900 the card (352x346 px, bottom-left, z-40) covers the sidebar account menu trigger and the left edge of the task composer. **Expected behavior** Optional announcements do not show in the first session, while the onboarding first task is open. **Steps to reproduce** 1. Create a new company through onboarding. The wizard creates the onboarding first task. 2. Open the first task at 1440x900 and wait 3 seconds. 3. See the announcement card on top of the account menu and the composer. **Paperclip version or commit** `master` at |
||
|
|
5d25117ffa |
fix(heartbeat): recover deferred dependency completion wakes (#19457)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Tasks wait for other tasks through saved dependency edges and wake requests. > - A dependency can finish while its assignee answers a question about the blocked task. > - Paperclip saves the completion wake behind that active turn. > - Recovery did not find that saved wake unless it also contained a comment or an interaction response. > - This pull request restores that wake through the existing release and dispatch checks. > - The task can then continue after the active turn and its cleanup finish. ## Linked Issues or Issue Description **What happened?** A task stayed blocked after its dependency finished. The completion wake was saved while an audit turn was active. The wake stayed deferred after that turn ended. It had no comment or interaction response, so both recovery queries and the input guard excluded it. **Expected behavior** Recover the saved dependency wake when the prior turn and its cleanup finish. Start one dependency continuation under the current execution rules. **Steps to reproduce** 1. Run a question response on a dependency-blocked task. 2. Finish the dependency while the response is active. 3. Save the completion wake behind that response. 4. Finish the response and its environment cleanup, or restart the workers. 5. Observe that the saved wake has no run. **Paperclip version or commit** Reproduced against `70d23612dd`. Rechecked against `2f35eb1080` with the final regression fixture. **Deployment mode** Source checkout on Linux x64, Node 25.9.0, pnpm 9.15.4, and isolated embedded PostgreSQL. Related work: #16673 added recovery for saved messages. #17288 and #16966 address assignment wakes after cleanup. This change covers dependency completion receipts with no message. ## What Changed - Include saved `issue_blockers_resolved` receipts in worker reconstruction and stranded queue selection. - Let these receipts pass the input guard without a comment or interaction response. - Use the existing release transaction and dispatch checks. Add no timer, migration, or direct task status change. - Add process-backed regression cases for restart, cleanup release, cancellation, agent pause, pending cleanup, and unresolved blockers. - Update the dependency wake recovery document. - Share the dependency-wake payload predicate and SQL fragment across reconstruction and stranded receipt admission, so these eligibility checks have one owner. ## Verification - The final regression fails on the base code: the receipt stays `deferred_issue_execution` and has no successor. The same test passes with the fix. - Six focused cases pass. They use real PostgreSQL and a real process adapter. They check saved output, one dependency continuation, clean task locks, repeat recovery, and execution gates. They do not call a model provider. - Three nearby suites pass: 125 tests across queue wiring, dependency scheduling, and dependency recovery. - Final-head server layered typechecking passes with `PAPERCLIP_TYPECHECK_LOW_MEMORY=1`. Whitespace and module boundary checks pass. Two parallel checker attempts reached the sandbox memory limit; the completed low-memory check covers all source and test layers. - The full deferred-receipt suite passes: 45 tests. Combined with the nearby suites, 170 local tests pass. These results cover head `8f9d3aa6ac19a6d2532bd869eb8adda7cf122a5a`. Required CI remains in progress. - Local workspace typechecking and build stop at the Runner Rust step because Cargo is absent. The full local test suite was not run. Required CI must supply broad coverage. - The hosted incident has not been recovered or deployed from this branch. - Review follow-up at `182385e9bc433adac06440997de7e4cfbf889a72`: normal merge of master `0d03610d2f73b7cd799547e43929bb2303319024`; no conflict or history rewrite. All 54 receipt/helper cases pass on macOS arm64, Node 26.4.0, real embedded PostgreSQL and process adapters. The six dependency continuation and safety cases also pass separately. Module boundaries and whitespace checks pass. No assertion, timeout, execution gate or retry policy changed for this extraction. - Current shared, SDK and adapter-utils source builds pass. The first broad local server typecheck read stale borrowed adapter declarations; rebuilding them exposed nominal type duplication between the borrowed workspace and this checkout. It is not counted as a server typecheck pass. All 30 SDK and 169 adapter runtime JavaScript outputs match the previously tested artifacts. Final revision `182385e9bc433adac06440997de7e4cfbf889a72` now passes all 55 applicable CI checks (three skipped, one neutral review), including clean-workspace typecheck, build and broad tests. [CI result](https://github.com/paperclipai/paperclip/actions/runs/42262173960). Greptile and Paperclip review both give 5/5 with no open findings. ## Risks - Recovery now considers a saved dependency receipt with no message. Current ownership, source finalization, cleanup, pause, cancellation, and dependency checks still control execution. - The scan remains bounded and uses existing transaction notifications and restart reconstruction. The integration checks verify that it becomes quiet after settlement. ## Model Used - OpenAI Codex, based on GPT-6. The exact provider model ID and context size are not exposed in this session. Used reasoning, tool calls, TypeScript edits, 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> |
||
|
|
e3fb4bc73a |
fix(native): recover saved listener startup failures (#19479)
## Thinking Path > - Paperclip manages AI agents and their work. > - Native recovery must preserve saved work and respect current task authority. > - A closed local API listener can stop startup before a provider starts. > - #19462 gives new failures a bounded bootstrap retry. > - Older saved failures still have a generic terminal code and a recovery hold. > - This pull request verifies the old producer outcome and uses the existing safe replacement path. > - Eligible tasks continue once. Unknown provider outcomes and user holds remain blocked. ## Linked Issues or Issue Description Refs: #19462 **What happened?** A restart can leave an old listener startup failure in `terminal_failure` with `native_session_interrupted`. Startup recovery does not claim it after #19462. A completed file recovery can also retain its no-replay hold. **Expected behavior** Continue automatically when durable records prove that no provider started, the source stopped, and the current files were saved. Keep the existing attempt budget and normal admission checks. **Steps to reproduce** 1. Close the actual API listener after a fresh native coordinator claim. 2. Persist the old executor's generic failure. Save the workspace and confirm source termination. 3. Restart recovery. The baseline schedules no successor. The repair schedules one successor after all receipts pass. **Paperclip version or commit** Baseline: `0252421d5c`. Old failure producer: `2f35eb1080`. **Deployment mode** Built from source. Local macOS arm64, Node 26.4.0, real embedded PostgreSQL, independent filesystem execution. No external AI service is used by the controlled acceptance. ## What Changed - Recognize the exact saved local-listener guard through one proof predicate shared by discovery and the locked transaction. - Require a fresh first attempt, matching execution bindings, no prior provider lineage, no provider events or tool calls, no checkpoint or admission receipt, and no cancellation. - Reuse current snapshot, exact stop, source block, attempt budget, and successor idempotency checks. - Wait for completed source file-recovery receipts. Retire only that same source's verified preservation hold in the successor transaction. - Bound discovery to 100 candidates per pass, keep completed history quiet, and wake through existing transactional producer notifications. - Retain retries after transient database and final ownership-proof errors; advance the bounded cursor and force a rescan so a failing page cannot hide later work. - Let only the replacement duty discharge a proved-empty notification during drain through its existing read-only readiness check, once per notification generation. Keep real work and concurrent changes outstanding without running replacement during drain. - Wake replacement at canonical local-stop proof writes and the existing dispatch eligibility boundary; these hints grant no new execution authority. - Add database regressions, scheduler quietness checks, real controller handoff coverage, and an opt-in controlled Runner continuation. ## Verification Final production head: `0df41b2fef2644195d1224d36984af3827da3e3b`. Local environment: macOS arm64, Node 26.4.0, TypeScript 7.0.2, Vitest 5.0.3, frozen-lockfile dependencies. Tests execute freshly; `--fsModuleCache` reuses only content-validated module transforms. - Native safe replacement: 62/62 PASS on isolated PostgreSQL 17 (63.18 seconds), including clean per-suite database disposal. All 62 assertions also passed on embedded PostgreSQL 18 beta, but its Darwin cleanup hook timed out; that invocation is not counted as a pass. Docker's disk was full, so the disposable PostgreSQL 17 data used a size-limited RAM mount, with no host mounts. The owned container was removed afterward. - Actual stopped-process producer suite: 31/31 PASS. A real exited child produces the stop receipt, and its replacement notification settles only after the outer transaction commits. - Workspace and separate-process drain regressions: 33/33 PASS (75.90 seconds). - Shared coordinator and execution-control lifecycle checks: 39/39 PASS (16.43 seconds). Pending work, concurrent changes, cold startup, shutdown, and passive drain boundaries remain covered. - Actual suppressed Cloud routine/drain workflow: PASS (34.64 seconds), using the unchanged test and timeouts. Its matching local RED reproduced CI's 462-query, zero-pending-query, one-idle-hold timeout after an admitted write committed during drain (69.46 seconds). - Actual historical controller handoff: PASS (48.40 seconds). A separate controller loads the original executor from `2f35eb1080`, writes its generic listener failure, and exits. Recovery at the final head admits exactly one successor. The real Rust Runner and controlled Codex process complete governed `paperclip_finish`; attempt two commits and the saved file remains intact. Only the child loads the old artifact, and both isolated artifacts are restored afterward. Rust source subtree `77961b70f177ec3c38d11abcaec92fd3d0e96ec0` matches the built binary. No external AI service is used. - Seven corrected existing fixtures pass: transactional stop-proof calls (5 cases) and notification inventory (2 cases). Their earlier CI failures exposed fixture calls outside the documented transaction and an outdated expected queue set. - Server typecheck: PASS. Red/green proofs also cover the original saved failure on baseline `0252421d5c` (zero successors instead of one), held-history quietness, bounded forward scanning through 100 parked rows and concurrent wakes, real snapshot/cleanup producer notifications, actual issue-row lock timeout, and task/history eligibility notifications. An obsolete prior-run question can be skipped to request another proof check; actual skip while the task is paused or the source owns provider tool activity schedules no successor. The failed-page regression starts with 25 persistent verifier errors and a healthy candidate 26. Baseline RED repeats the first page. Repair GREEN advances the healthy candidate, revisits the original failures after their fault clears, creates all 26 successors exactly once, and becomes quiet. Greptile is SUCCESS, 5/5 on the exact final head (2026-11-06 17:09:21 UTC). Final-head CI is GREEN: 56 successful checks, three intentional skips, one neutral result, and no pending or failing checks. [Attempt two and results](https://github.com/paperclipai/paperclip/actions/runs/42258352876/attempts/2) include passing build, typecheck, Rust, native server, package, browser E2E, serialized server, general server/chat tests, and final verification. Five jobs in the first attempt were interrupted together at 17:13:12 UTC; the native-server log records an explicit runner shutdown signal. The single infrastructure retry reran only those five jobs and dependent verification, preserving the 49 successful checks on the unchanged head. No timeout was weakened, and setup failures or interrupted invocations are not counted as passes. Focused commands from the repository root: ```sh ./server/node_modules/.bin/vitest run --project @paperclipai/server server/src/services/remote-workspaces.integration.test.ts server/src/services/native-runtime/native-runner-restart-preparation.integration.test.ts -t 'native safe-replacement decision|reconciles the drained remote backend' --fsModuleCache --reporter=dot ./server/node_modules/.bin/vitest run --project @paperclipai/server server/src/__tests__/execution-control-work.test.ts server/src/__tests__/delivery-work-coordinator.test.ts -t 'execution-control lifecycle|work coordinator' --fsModuleCache --reporter=dot node node_modules/typescript/bin/tsc -p server/tsconfig.json --noEmit --pretty false ``` The historical acceptance sets `PAPERCLIP_TEST_HISTORICAL_LISTENER=1` and uses an original-executor child artifact with a `try/finally` restore. Normal regressions require no Rust binary. ## Risks - Compatibility is deliberately limited to positively proved fresh remote startup failures. Other old ambiguous failures remain held. - Existing provider work, a changed task block, incomplete preservation, or revoked authority must prevent execution. Normal retry admission remains in control. - This PR does not deploy the change or mutate a hosted task. Hosted recovery still requires the repaired revision and all current proof gates. ## Model Used OpenAI GPT-6 through Codex, with reasoning, tool use, and code execution. The exact backend model identifier and context window are not exposed in this session. ## 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> |
||
|
|
30a0b5982e |
fix(runner): let Codex agents generate images with their existing provider key (#19516)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Paperclip runner launches Codex agents in an isolated app-server
with a sandboxed shell
> - On the Codex API path, the agent's OpenAI API key reaches only the
trusted app-server process, never the shell
> - The runner launched Codex with `--disable image_generation`, so
Codex's built-in image tool was not available
> - Codex then falls back to its `image_gen.py` script, which needs
`OPENAI_API_KEY` in the shell, so the agent asked the user for a second
key
> - This pull request keeps Codex's built-in image tool on, so it uses
the agent's existing provider auth, and lets the sandbox read only the
folder where Codex saves the images
> - The benefit is that a Codex agent can make images without a second
key, and the provider key still does not reach the shell
## Linked Issues or Issue Description
**What happened?**
Manual onboarding test on Paperclip Cloud staging (Codex API path):
"Open AI requests another api key for image model even though it has an
active api key powering the agent."
**Steps to reproduce**
1. Create a company with a Codex agent that uses an OpenAI API key.
2. Ask the agent to generate an image.
3. The agent asks for another OpenAI API key.
**Expected behavior**
The agent uses the OpenAI API key that already powers it.
**Root cause**
`createIsolatedCodexAppServerArgs` passed `--disable image_generation`
and the thread config set `features.image_generation: false`. The pinned
Codex 0.160.0 binary has two image paths. Its own instructions say the
built-in `image_gen` tool "Does not require `OPENAI_API_KEY`". The
fallback CLI path "Requires `OPENAI_API_KEY`". The runner removes
`OPENAI_API_KEY` from the shell on purpose
(`codex-shell-environment.ts`), so the fallback can never work and the
agent asks the user.
## What Changed
- `codex-security-config.ts`: remove `--disable image_generation` and
`features.image_generation: false`. Codex's built-in tool runs in the
trusted app-server with the agent's existing auth.
- `codex-security-config.ts`: in the workspace and plan permission
profiles, grant **read** on `CODEX_HOME/generated_images` only. Codex
saves images there and tells the agent to copy them into the workspace.
The rest of `CODEX_HOME` (for example `auth.json`) stays denied.
Preconfigured-home mode is unchanged (it does not deny `CODEX_HOME`).
- `eval-gateway-runtime.ts`: set `image_generation = false` in the
controller-gateway Codex config. That gateway admits no hosted tools
(`unqualified_hosted_tool`), so qualified evals keep the earlier
behavior.
- Tests: two new N-12 tests, and updates to the tests that asserted the
old disable flag.
## Verification
Red on `master` (
|
||
|
|
19239c11d1 |
fix(onboarding): move Use a custom gateway under Advanced on the API key step (#19510)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - New users connect a model in the onboarding wizard's "Connect a model" step. > - On the Claude API and Codex API paths, the step shows an API key card. > - The "Use a custom gateway" button sat directly under the key field, inside that card, with no gap. > - A tester reported that it hugs the field and that it belongs under Advanced. > - A custom gateway is an alternative route, not the next step after the key. > - This pull request moves the button into the step's existing Advanced disclosure. > - The benefit is a cleaner key card, and the gateway route stays one click away. ## Linked Issues or Issue Description No public issue exists. A manual new-user onboarding test on a staging cloud workspace found this bug. Related: #17253 added the custom gateway route. **What happened?** On the onboarding "Connect a model" step, API key path (Claude API and Codex/OpenAI API), the "Use a custom gateway" button is shown at the top level, directly against the API key text field. **Steps to reproduce** 1. Start onboarding and reach "Connect a model". 2. Choose "Use API key instead", then pick Claude (or OpenAI). 3. See "Use a custom gateway" directly under the "Enter API key here" field. **Expected behavior** The custom gateway control is in the Advanced section of the step. It is hidden until Advanced is opened. Source: manual new-user onboarding test on Paperclip Cloud staging (item N-16, P3, Claude API path). ## What Changed - `ui/src/components/OnboardingWizard.tsx`: render `OnboardingGatewaySetup` inside the connect step's existing `Advanced` `<details>` (above the runner select), not inside the API key card. It keeps the same gates as the card it left: the card is live and mounted, no connect progress, API credential mode, a created company, and a Claude or Codex adapter. - `ui/src/components/onboarding/OnboardingGatewaySetup.tsx`: accept an optional `className`. The wizard passes `-ml-3` so the ghost button's label lines up with the Advanced summary. - The gateway dialog and its save/select-model/connect flow are unchanged. - `ui/src/components/OnboardingWizard.test.tsx`: new test for the Claude API and Codex API paths. It asserts the button is in a closed `Advanced` disclosure, outside the key field's card, and that the gateway dialog opens after Advanced is expanded. ## Verification Red on unchanged source (`npx vitest run src/components/OnboardingWizard.test.tsx -t "custom gateway"`): ``` ❯ src/components/OnboardingWizard.test.tsx (212 tests | 2 failed | 208 skipped) × keeps Use a custom gateway in the closed Advanced disclosure on the Claude API key step × keeps Use a custom gateway in the closed Advanced disclosure on the Codex API key step AssertionError: Use a custom gateway should live inside the Advanced disclosure: expected null to be truthy Test Files 1 failed (1) Tests 2 failed | 2 passed | 208 skipped (212) ``` Green after the fix (same command): ``` Test Files 1 passed (1) Tests 4 passed | 208 skipped (212) ``` Neighbouring suites (`OnboardingWizard.test.tsx`, `ai-connections/AiProviderSetup.test.tsx`, `components/onboarding/*`), including the existing end-to-end custom gateway save/test/hire test: ``` Test Files 8 passed (8) Tests 270 passed (270) ``` - `pnpm check:token-gates`: all gates clean. - `tsc --noEmit` for `ui`: no new errors. The only errors are the existing unbuilt `@paperclipai/plugin-sdk/ui` import in `PluginOrganizationSwitcher`. Screenshots (Storybook `Onboarding/Agent arc › Connect · Claude API key`): | Before (master) | After (Advanced closed) | After (Advanced open) | | --- | --- | --- | |  |  |  | The red "This item doesn't exist or was moved." line in the screenshots comes from the story's runner fixture. It is present on master too and is not related. ## Risks - Low risk. This is a layout move of one control. The gateway flow and its gates are unchanged. - The control is now one click deeper. This is the intended outcome of the report. - After a gateway is saved, "Use a different API key" stays in the card, so a user can still leave the gateway route without opening Advanced. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with tool use (shell, file edits, browser for Storybook screenshots). ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
63e550b338 |
fix(ui): keep the audit hash out of the tool approval card (#19509)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents call connected-app tools (for example GitHub
`create_repository`) through the tool gateway, and a write call waits
for a human approval card
> - The pending approval card showed an "Arguments and audit hash"
disclosure. The disclosure printed the 64-character SHA-256 of the
reviewed arguments
> - A new user in onboarding testing called this "a long hash string in
the review approval card" that is confusing and unapproachable
> - The hash identifies the exact arguments for audit. The approver does
not need to read it to decide
> - This pull request removes the raw hash from the card text and keeps
it behind a "Copy audit hash" action
> - The benefit is a plain approval card, with the audit value still
available to people who need it
## Linked Issues or Issue Description
**What happened?**
On the pending tool-approval card, the "Arguments and audit hash"
disclosure printed the raw SHA-256 of the reviewed arguments (for
example `8ca5312877235a…`, 64 hex characters) under the arguments JSON.
**Expected behavior**
The approval card uses plain language. Internal identifiers do not show
as card text. A person who needs the audit hash can still get it.
**Steps to reproduce**
1. Let an agent call a write tool on a connected app (for example GitHub
`create_repository`).
2. Open the pending "Approve tool action" card ("Review request").
3. Expand "Arguments and audit hash". The raw 64-character hash shows
under the arguments.
**Paperclip version or commit**
Current `master` (
|
||
|
|
a0cba70f18 |
fix(runner): continue restart-interrupted ACPX and OpenCode turns (#19501)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Native runs keep their provider conversation when the control plane restarts > - A local Codex turn lost in a restart already continues once in the same conversation (#15297) > - ACPX (Claude and the other ACPX profiles) and OpenCode took a different path: the runner closed the lost turn as a provider failure > - The task then blocked with "Provider turn failed", and the user's queued follow-up stayed unread > - A restart must not need a user action when continuing is safe > - This pull request gives ACPX and OpenCode the same recoverable restart-loss cause and the same single continuation as Codex > - The benefit is that work interrupted by a controller restart finishes on its own ## Linked Issues or Issue Description Refs #15297 (Codex continuation), #19399 (restart-interrupted turn hook), #19471 (harness fix used for the live reruns). **What happened?** After a control-plane restart during an active native ACPX Claude turn, the task ended `blocked` with `native_provider_terminal_failed: Provider turn failed`. The queued follow-up comment was not consumed. No continuation started. The next run then failed with `native_session_cleanup_quarantined`. A native OpenCode run killed mid-turn ended the same way (`turn.failed` with `error: null`). **Expected behavior** When the old provider process is gone and the conversation can be restored, continue the task once in the same conversation. When that is not safe, record an honest restart cause instead of a generic provider failure. **Steps to reproduce** 1. Start a native ACPX Claude task on a local runner. 2. Submit a follow-up comment while the turn is active. 3. Restart the control plane while the turn is still running. 4. Run `pnpm test:e2e:runner -- --max-automatic-retries 0 --id everyday-workflows.runner-acpx-claude.local.recover-controller`. **Root cause** - `runner-core/src/acpx_provider_backend.rs` `restore_session_if_needed`: a saved `turn_active` state always closed the session. It recorded `turn.failed` with only a top-level `code: acpx_active_turn_recovery_closed` and no `error` object. It also emitted `run.terminal`. - The runnerd transport forwards only `error`, so the controller saw `error: null` and reported generic `provider_turn_failed`. - `runner-core/src/provider_backend.rs` gave the recoverable `provider_turn_lost_on_restore` cause only to `provider_label == "codex"`. OpenCode shares that backend. - `nativeRestartInterruptedTurnId` accepted only `codex_app_server`. The runtime continuation gate accepted only `provider.kind === "codex"`. - Two follow-on defects appeared once the continuation ran live: - The restored ACPX provider rejected the controller's result for the lost turn's open semantic call. That call never settled. - The restored provider's MCP request ids restart. The controller keys semantic calls by `hash(runId, callId)`. The continuation's first Paperclip tool call reused an earlier id, was treated as already answered, and hung. After the result was accepted, `runner_settlement_incomplete` failed the run. ## What Changed - ACPX runner: for an active turn lost to a runner restart, prove that the original provider lifetime lost its fence quorum. Then restore the same conversation and record `turn.failed` with `error: { code: "provider_turn_lost_on_restore", recoverable: true }`. Keep the run open for the controller's continuation. An accepted result, an unanswered runtime request, or an active goal keeps the old close behavior. So do an unproven lifetime fence and a failed restore. - ACPX runner fallback close: add a structured `error` (`acpx_active_turn_recovery_closed`, not recoverable, with an honest restart message), so the cause survives the transport. - ACPX runner: track open semantic calls durably. On restore, retire the lost turn's open calls. Acknowledge their controller results as `retired` and do not send them to the new provider. - ACPX sidecar: when the sidecar restores an existing session, prefix forwarded semantic `callId`s with a per-process namespace (`r<hex>:<id>`). Fresh sessions keep raw ids. - OpenCode runner: give OpenCode the same recoverable cause and open run as Codex (`restart_lost_turn_is_continuable`). Cursor Cloud stays excluded because its agent runs remotely. - Runner TS: `nativeRestartInterruptedTurnId` accepts `acpx_runtime` and `opencode_server`. The runtime continuation gate accepts `acpx` and `opencode`. All other Codex safety rules are unchanged: one continuation, admitted dead-runner restart only, no compaction, goal, semantic result, or pending runtime request. - Docs: `doc/execution-semantics.md` and `doc/run-log-events.md`. ## Verification Red, then green, at each layer: - Rust `tests/acpx_warm_attachment.rs` `restores_the_conversation_after_losing_an_active_turn_to_a_runner_restart`. Red: status `closed`. Then red on the lost call's result: "tool result does not match a pending provider call". Green after the fix. - Rust `tests/native_provider_backend.rs` `restart_lost_opencode_turn_records_a_recoverable_cause`, with the qualified OpenCode launch fixture. Red: `error` was `null`. Green after the fix. - Rust unit test `restart_lost_turns_continue_only_for_locally_restored_providers` (Codex, OpenCode yes; Cursor Cloud no). - TS `native-session-runtime.test.ts` "continues a restart-lost acpx_runtime / opencode_server turn once". Red: `NativeProviderTerminalFailure provider_turn_lost_on_restore`. Green: exactly one continuation, which does not resend the task. - TS `acpx-runtime-sidecar.test.ts`: a restored provider's call ids are namespaced and resolve. - Server `native-runner-restart-recovery.integration.test.ts`: a checkpointed `acpx_runtime` or `opencode_server` restart interruption is claimed as `resume_dead_runner`. Red without the driver gate. Green with it. - Two `native_provider_backend.rs` tests asserted the old close-on-restore result. They now assert the new contract. Their real invariant still holds: the delivered response is never replayed. Suites run locally on the final revision: - runner-core: `cargo test --no-fail-fast acpx`, `--test native_provider_backend` (38), `--test acpx_warm_attachment`, `--test codex_provider restart_lost` - runner TS: `native-session-runtime`, `runnerd-restart-continuation`, Codex driver recovery, contracts, and `acpx-runtime-sidecar` (all pass) - server: `native-runner-restart-recovery.integration.test.ts` (80 passed) - `pnpm --filter "@paperclipai/paperclip-runner..." build` (all generated-contract checks pass) Live runner E2E (local, real Claude via ACPX, macOS arm64): - `everyday-workflows.runner-acpx-claude.local.recover-controller`: PASS twice in a row on the final runner code. The restart crossed an active turn. The task finished `done`, the queued follow-up was consumed, the ZIP download was verified, and there were no failure codes. - `stock-harness.runner-acpx-claude.local.continuity-restart` regression: PASS. - Earlier failing reruns are kept as evidence of the follow-on defects: one harness `kill EPERM` while stopping the server (unrelated to this change), one hang on the colliding call id, and one `runner_settlement_incomplete`. ## Risks - A continuation can run more work in the restored conversation. Its prompt requires inspecting state before repeating an uncertain action, as for Codex. - The ACPX restore waits up to 5 s for the original provider lifetime fence before it falls back to the old close. - The ACPX provider state file gains two fields with defaults. Older saved states still load. A downgraded runner cannot read state that a newer runner wrote with these fields. - Restored ACPX sessions now forward prefixed call ids. Only fresh sessions keep raw ids. Live proof covers Claude. Codex-ACP, Pi, Cursor, Copilot, Grok, Kimi, and Antigravity share this path, but this PR proves them only by unit tests and code review. - OpenCode is covered by the runner fixture and gate tests. The live OpenCode restart-chaos cell was not rerun here. - `runner-core` `unconfirmed_suspension_state_becomes_recoverable_only_after_cleanup_and_suspend` is flaky when run in parallel. It also fails with the new path disabled, so it predates this change. ## Model Used - Anthropic Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use, code execution, and extended reasoning. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
98ebeefbc9 |
feat(environments): let managed config declare plugin environments (#19508)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A managed instance gets its instance-level environments from the `PAPERCLIP_MANAGED_CONFIG` document > - #19490 added the plugin environment rows, their marker index and write floor, but nothing creates them yet > - The managed config still accepts only one sandbox entry, so a managed instance cannot get a plugin-provided environment without manual setup > - This pull request lets the managed config declare plugin entries beside the sandbox entry, and provisions one locked row for each at boot > - The benefit is that a managed instance gets its plugin environments with no manual setup, and the rows recover by themselves when their plugin restarts ## Linked Issues or Issue Description Refs #19490 (the plugin environment rows this pull request provisions) **Subsystem affected** Server environments: the managed-config parser, the managed environment bootstrap, and the environment routes' write floor. **Problem or motivation** A managed instance cannot get a plugin environment from its managed config. The `environments` section accepts one entry, and that entry always provisions the managed sandbox. Company settings cannot create a plugin environment either. So each managed instance that needs one requires manual setup. **Proposed solution** Let an `environments` entry declare `driver: "plugin"`. Boot provisions one locked row for each plugin entry through the row layer from #19490. A later pull request adds a one-time instance default for one plugin entry, and advertises the new managed-config capability. **Alternatives considered** Manual setup on each instance, and a new control-plane endpoint. Both need a step outside the deploy. The managed config already delivers the sandbox entry. **Roadmap alignment** This extends the existing managed-config `environments` section, which already provisions the managed sandbox. It adds no new product surface. ## What Changed - `server/src/services/managed-config.ts`: an `environments` entry may set `driver` to `"sandbox"` or `"plugin"`. An entry without `driver` is still the sandbox entry, so today's documents parse unchanged. - A plugin entry has `name`, optional `description`, `pluginKey` (the plugin's manifest id), `driverKey` and optional `driverConfig`. - At most one sandbox entry. Names are unique across the section, and `pluginKey`–`driverKey` pairs are unique. Secret-like keys in `driverConfig` refuse startup, as for sandbox `config`. An unknown key or driver still refuses startup, and that includes `instanceDefault` until the follow-up pull request. - Plugin entries parse into a new `pluginEnvironments` list. The `environments` list keeps only the sandbox entry, so its consumers do not change. - `MANAGED_ENVIRONMENT_DRIVERS` lists the accepted drivers. - `server/src/services/managed-environments.ts` (`applyManagedEnvironments`): - Boot refuses to start unless an auto-installed catalog entry installs each plugin entry's `pluginKey`. `plugins.autoInstall` holds catalog keys, not manifest ids, so the parser cannot check this. Boot uses the manifest ids `createApp` already resolves from the bundled and distribution catalogs (`app.locals.managedBundledPluginKeys`, passed in by `server/src/index.ts`). Without that list it resolves the catalog itself. - Plugin entries run after the sandbox entry and reuse its readiness handling. Boot awaits `pluginsReady`, then requires the plugin `ready`, declaring the entry's driver, and running a worker. Otherwise boot archives the row and re-ensures it on the worker's `ready`. - Recovery re-ensures on every worker `ready` until one ensure succeeds or the plugin rejects the config (HTTP 422). - If the ensure fails because the worker died, boot archives the row and waits for `ready`. If the worker is still running (for example the call timed out), boot keeps the row as it is, retries once at once and on the next `ready`, and otherwise at the next boot. No timer is added. - Removing an entry never archives its row. - A plugin-only document does not conflict with `PAPERCLIP_EXECUTION_MODE`, because plugin rows do not use the sandbox slot. - Plugin entries never change the instance default. The sandbox stamp still fills only an unset or local default, so it never replaces a plugin default. - `server/src/routes/environments.ts`: `isPlatformSlotEnvironment` treats a marked plugin row as a live slot while the document declares its entry. So the marker-clear escape hatch is refused then, and allowed again after the entry leaves the document. - `doc/DEVELOPING.md`: new "Managed environments" section. ## Verification - `cd server && npx vitest run src/__tests__/managed-config.test.ts`: old documents parse unchanged. Sandbox plus plugin parses. Each of these refuses startup: two sandbox entries, `driver: null`, duplicate names, a duplicate pair, a secret-like key, an unknown key (including `instanceDefault`), an unknown driver, a malformed `driverKey` or `driverConfig`. - `npx vitest run src/services/managed-environments-plugin-entries.test.ts` (the sandbox-entry tests stay in `managed-environments.test.ts`; shared fixtures are in `managed-environments.test-support.ts`): - A plugin entry is ensured with the validator's worker manager, after the sandbox entry. - Boot refuses a plugin entry whose `pluginKey` no auto-installed catalog entry installs, both from the passed list and from its own catalog lookup. - A plugin that is down archives the row, and worker recovery re-ensures it. A missing plugin, a plugin without the driver, or a worker manager without `call` counts as unavailable. - Recovery stays armed after a failed attempt and stops after one success or a permanent config rejection (422). A `ready` during an attempt queues one more. - A worker that dies during the boot ensure archives the row and arms recovery. A failure while the worker keeps running leaves the row as it is and retries. - Removing an entry does not archive. Plugin entries and the sandbox stamp never change a plugin default. - An ensure failure is counted, not thrown. - `npx vitest run src/__tests__/managed-plugin-environments.test.ts` (embedded Postgres, real `applyManagedEnvironments`): the first boot creates the row and the second changes nothing. A plugin that is down archives the row, and recovery brings it back. Removing the entry does not archive the row. The instance default stays unset. The row tests from #19490 still pass. - `npx vitest run src/__tests__/environment-routes.test.ts`: the marker clear is refused while the document declares the entry, and allowed after it leaves. - `npx vitest run src/__tests__/instance-settings-service.test.ts src/__tests__/instance-settings-cloud-defaults.test.ts`: pass with the new `pluginEnvironments` field. - `pnpm --filter @paperclipai/server typecheck` passes. - I did not run the full `pnpm test:run`, `pnpm -r typecheck` or `pnpm build`. CI covers them. ## Risks - No schema change. - A managed-config document with a plugin entry now provisions a row at boot. A document without one behaves as before. - Adoption renames an unmarked row for the same plugin and driver to the declared name and resets its driver config to the declared one. The row keeps its id, agents and other metadata, and it is then locked like the managed sandbox. - Boot now refuses to start when a plugin entry names a plugin that no auto-installed catalog entry provides. This is the same fail-closed posture as an unknown sandbox provider. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, through Claude Code with tool use (file edits, shell, test runs). ## 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 - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
543aa4d331 |
fix(tasks): show a pending native same-run resume instead of a competing Retry (#19503)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - When a native run cannot reattach to its provider after a control-plane restart, the native coordinator resumes the same run by itself after about 30 seconds, up to three attempts > - The run projection shows this correctly as "Retry scheduled" with only `inspect_run` permitted > - The task interruption shown on the issue read only system-recovery evidence and `scheduledRetryAt`. It never read the coordinator's `nextAttemptAt`, so it showed `state: failed, canRetry: true` and offered a manual Retry while the automatic resume was still pending > - The two surfaces disagree, and a user click can race the automatic resume. That is one way a Retry gets parked behind "execution checks" > - This pull request makes the task interruption read the coordinator's pending resume time > - The benefit is one consistent story: while the automatic resume is pending, the task shows it with its time and no competing Retry. If recovery ends, exactly one Retry returns ## Linked Issues or Issue Description Refs #19487, #19497, #19426. **What happened?** A native run failed with `native_session_interrupted` after a restart. Its finalization row had `phase: retryable_failure` and `nextAttemptAt` 30 seconds later. The run projection said "Retry scheduled". The issue interruption said `state: failed, canRetry: true, nextRetryAt: null`. **What did you expect?** While the automatic resume is pending, the task shows that it is resuming, with the time, and no Retry. When the resume fails for good or never runs, exactly one Retry that works on the first click. **Steps to reproduce** 1. Run a native task and restart the control plane so the native attach fails. 2. Read `GET /api/issues/:id` while the coordinator's resume is pending. 3. Observe `interruption.canRetry: true` while `execution.phase` is `retry_scheduled`. ## What Changed - `task-interruption-presentation.ts`: the batched run read selects the native coordinator's `next_attempt_at` when the native run failed, has no result, and its finalization phase is `retryable_failure`. - A pending resume in the future shows `retry_scheduled` with its time and `canRetry: false`. - A due resume shows `working` for up to two minutes while its dispatch runs. After that, or once recovery is terminal, the task falls through to the existing single Retry. ## Verification - `task-interruption-presentation.test.ts`: new case for a future resume, a due resume, a stale resume, and no resume. It fails before the change (the task offered Retry) and passes after. - `task-interruption-query.test.ts` (embedded Postgres): the real batched query shows the pending resume for both list and detail reads, and returns one Retry after the coordinator becomes `terminal_failure`. - All 40 presentation tests and 17 query tests pass. Server typecheck passes. ## Risks - Low risk. This changes only the read model for tasks whose native coordinator holds a pending same-run resume. - If a resume never dispatches, Retry is hidden for up to two minutes after its due time before it returns. - The `POST /issues/:id/retry` endpoint is unchanged. A client that ignores `canRetry` can still submit a Retry during the resume. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with tool use (shell, file editing, embedded Postgres tests). ## 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 - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ff1d1dea5d |
fix(tools): reject non-tool connections before any write (#19492)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Apps and tools connect to Paperclip through tool connections > - `POST /api/companies/:companyId/tools/connections` creates a tool connection through `createConnection` > - The request schema accepts a channel purpose and the chat and voice transports, but `createConnection` cannot store them > - The database then rejects the row, so the caller gets a 500 and a stray tool application stays behind > - This pull request rejects those requests with a 422 before any write, and stores `config` as `transportConfig` when the caller leaves `transportConfig` out > - The benefit is a clear error, no leftover rows, and consistent connection rows ## Linked Issues or Issue Description No issue exists. The new test typecheck in #18739 found this problem. #18739 listed it as a possible product problem and did not change it. **What happened?** 1. `createConnection` never writes `input.connectionPurpose`, so every row gets the database default `tool`. 2. The schema allows `transport: "chat_sdk"` and `transport: "voice"`. The database check `tool_connections_channel_transport_check` does not allow these transports on a `tool` row. 3. No error mapping exists for a check violation, so the route returns 500. 4. When the request has no `applicationId`, the service inserts a `tool_applications` row first, outside the connection transaction. That row stays after the failure. 5. The schema gave `transportConfig` a default of `{}`. The service stores `input.transportConfig ?? config`, so it stored `{}` instead of a copy of `config`. **Expected behavior** The route returns 422 for a purpose or transport that it cannot create, and writes no rows. When the request leaves `transportConfig` out, the row stores `config` there. **Steps to reproduce** 1. Send `POST /api/companies/:companyId/tools/connections` with `{ "name": "x", "transport": "chat_sdk" }`. 2. See the 500 response. 3. See the new `tool_applications` row for the company. ## What Changed - `server/src/services/tool-access.ts`: `createConnection` rejects a purpose other than `tool`, and the `chat_sdk` and `voice` transports, with 422. The check runs before any lookup or write. Channel connections still come from their own services. - `packages/shared/src/validators/tool-access.ts`: `transportConfig` is optional and has no default. The update schema already removed defaults, so it does not change. - `server/src/__tests__/tool-access-service.test.ts`: added tests for the three rejected inputs (no rows written), and for a request that sends only `config`. ## Verification - `pnpm exec vitest run server/src/__tests__/tool-access-service.test.ts server/src/__tests__/generic-mcp-connection.test.ts server/src/__tests__/tool-connection-health-work.test.ts server/src/__tests__/openapi-routes.test.ts server/src/__tests__/tool-access-routes.test.ts packages/shared/src/validators`: 27 files, 995 tests pass. - `cd server && pnpm typecheck`: passes. This includes the source layers and the three test-tree shards. - Not run locally: the full `pnpm test:run`, the root `pnpm build`, and the UI typecheck. Required CI covers them. ## Risks - Low risk. A client that sends a channel purpose or a chat or voice transport to this route now gets 422 instead of 500. The request failed before this change too. - A client that sends only `config` now gets the same value in `transportConfig`. Every reader checks `config` first, so this changes no behavior. - The OpenAPI output for this body no longer shows a default for `transportConfig`. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use (shell, file edits) and extended thinking. ## 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 - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [ ] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f5de197536 |
fix(ui): keep a busy-workspace wait as a quiet status, not a notice (#19507)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The task page tells the user what an agent is doing and what needs their attention > - When another task uses the same workspace, the agent waits and Paperclip starts the work automatically > - A tester reported that the "Waiting for workspace" notice is not helpful, because the user cannot do anything about it > - On `master`, the task page shows this wait four times: a pinned top banner, a conversation banner, the composer strip and the Workspace property row > - This pull request removes the two banners and keeps the two quiet status surfaces > - The benefit is that a transient system wait no longer looks like something that needs the user ## Linked Issues or Issue Description Refs #19369 **What happened?** During new-user onboarding tests on Paperclip Cloud staging (Claude subscription path), the tester saw a "Waiting for workspace" notice and reported: "this isn't helpful and the user can't do anything about it." **Steps to reproduce** 1. Start work on a task while another task uses the same workspace. 2. Open the waiting task. 3. See the pinned "Waiting for workspace" banner under the title and a second "Waiting for workspace" banner in the conversation. **Expected behavior** A busy workspace is a transient system wait with no user action. It shows only as a quiet status on the task. It does not show as a banner or notice. **Investigation** - No server inbox, Needs-you item or activity entry is created for a workspace wait. The live-updates toast already skips `workspace_busy` (`LiveUpdatesProvider.tsx`), and the chat thread already hides the cancelled admission attempt. - The remaining notices are UI banners: `IssueMonitorBanner` (pinned top banner) and `IssueScheduledRetryCard` (conversation banner). - #19369 stopped showing resource waits as failed attempts, but it kept these banners. ## What Changed - `IssueMonitorBanner`: render nothing for a workspace wait. The composer strip still shows "Waiting for workspace" and "You can keep sending instructions while the agent waits." - `IssueScheduledRetryCard`: render nothing for `workspace_busy`. The Workspace property row still shows "Waiting for workspace". - `DESIGN.md`: add the rule that a busy-workspace wait is a quiet status only, never a pinned banner, conversation notice or toast. - Tests: replace the old "shows the banner" assertions with N-14 regression tests. ## Verification Red on `master` (`819420a60a`), test change only: ``` cd ui && npx vitest run src/components/IssueMonitorBanner.test.tsx src/components/IssueScheduledRetryCard.test.tsx × does not raise a pinned banner for a workspace wait the user cannot act on (N-14) 7ms ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯ FAIL src/components/IssueMonitorBanner.test.tsx > IssueMonitorBanner / IssueMonitorComposerStrip rendering > does not raise a pinned banner for a workspace wait the user cannot act on (N-14) AssertionError: expected 'Waiting for workspaceAnother task is …' to be '' // Object.is equality FAIL src/components/IssueScheduledRetryCard.test.tsx > IssueScheduledRetryCard > raises no conversation notice for workspace contention the user cannot act on (N-14) AssertionError: expected 'Waiting for workspaceAnother task is …' to be '' // Object.is equality Tests 2 failed | 32 passed (34) ``` Green after the fix, same command: ``` Test Files 2 passed (2) Tests 34 passed (34) ``` Neighbouring suites: ``` cd ui && npx vitest run src/components/TaskChatThread.test.tsx src/pages/IssueDetail src/components/issue-properties src/lib/issue-monitor Test Files 7 passed (7) Tests 687 passed | 1 skipped (688) ``` - `pnpm check:token-gates`: all gates clean. - `tsc --noEmit -p ui`: no errors in the changed files. Local errors come only from unbuilt workspace packages (`@paperclipai/plugin-sdk/ui`, Storybook feature map). CI typecheck covers the full build. ## Risks - Low risk. UI-only change. Two banners no longer render for `workspace_busy`. Other scheduled-retry reasons (failures, pool exhaustion, continuations) are unchanged. - A long workspace wait now has no prominent notice. No actionable next step exists in the current data (the API does not name the task that holds the workspace). A persist-past-threshold escalation with a link to that task can follow when the data exists. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with tool use (shell, file edit, browser for read-only staging evidence). ## 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 - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |