mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
f0ddccff71f3bbda975cac8a86053a67c5d554f3
7726
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f49c92870a |
Clarify safe response and saved-wait recovery after secret denial
Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
b68826b5df |
test(runner): join real finish diagnostic producer to retention
Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
88c4f35718 |
test(evals): recognize current unresolved team skill conflicts
Accept truthful exact-skill unresolved installation reports while rejecting unrelated or denied conflicts and independent current recovery claims. Keep saved state, actor, phase, links and original bounds strict. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
bf311b939b | fix(evals): await mounted personal secret navigation controls | ||
|
|
1cb6160d0c | fix(evals): discover personal secure entry through visible menus | ||
|
|
bb32fea713 |
test(chat): declare company completion diagnostic provenance
Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
f9ac63b821 |
test(evals): supply the existing multipart form as task context
Record only current question metadata through the ordinary source description. Bind first-turn duplicate detection to the real human query and native run, while preserving later follow-up questions and all submitted-answer checks. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
76918553c8 |
fix(runner): emit finish diagnostics only after a bound rejection
Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
e72803a2b6 |
fix(runner): retain closed OpenCode finish diagnostics
Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
a4bb059f59 |
fix(heartbeat): retry transient sandbox provider lease refusals and explain the block (#19412)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - A task runs in an execution environment. A managed sandbox
environment gets a lease from a sandbox provider (for example Daytona)
before the agent starts.
> - When the provider answered sandbox create with a temporary error
(HTTP 502), the run failed as the generic `setup_failed`.
> - `setup_failed` is the fail-closed code for unknown setup errors. It
gets no automatic retry, so the task went to `blocked` at once. The task
showed only "Couldn't finish this attempt".
> - A user had to find and press Retry. The Retry then worked, which
shows that the failure was temporary.
> - This pull request classifies temporary provider refusals during
lease acquisition from structured fields. It retries them within the
existing bounded bootstrap budget, and it explains the block when the
retries are used up.
> - The benefit is that a short provider outage no longer stops a task.
When the outage continues, the task shows the cause and a Retry that
works.
## Linked Issues or Issue Description
**What happened?**
A task was assigned to an agent that uses a managed Daytona sandbox
environment. Sandbox creation returned `HTTP 502`, and the cleanup of
the provisional sandbox could not be confirmed. The run failed with
`setup_failed` and the error `Failed to acquire lease for environment
"Paperclip Sandbox" (sandbox): Daytona sandbox creation failed (HTTP
502); cleanup could not be confirmed for paperclip-create-…`. Three
seconds later the task was `blocked`. No automatic retry was scheduled.
The task showed no specific reason. A manual Retry started a new run
that completed normally.
**Expected behavior**
A temporary provider error (HTTP 5xx, 429, timeout) before the agent
starts must retry automatically with bounded backoff. The task must show
why it waits. If the retries are used up, the task must show the cause,
a next action, and a Retry that works on the first click.
**Steps to reproduce**
1. Configure an agent with a sandbox environment.
2. Make the provider return HTTP 502 on sandbox create.
3. Assign a task to the agent.
4. See that the run fails with `setup_failed` and the task goes to
`blocked` with no retry.
**Paperclip version or commit**
`master` at `612da1e8e0`.
## What Changed
- Plugin SDK: an `environmentAcquireLease` error now carries a closed
diagnostic envelope (HTTP status or a closed errno code) across the
worker RPC. It takes the status of the create call only, not of the
compensating cleanup lookup. The existing creation-cleanup ownership
envelope is unchanged. New module: `environment-acquire-error.ts`. The
resume diagnostic helpers are shared.
- Server: new `EnvironmentProviderUnavailableError` and code
`environment_provider_unavailable`. The environment run orchestrator
sets it as the cause of `lease_acquire_failed` only when structured
evidence shows a transient status (408, 425, 429, 500, 502, 503, 504, or
a closed connection errno). It never parses message text. Saved-sandbox
resume and stopped-worker refusals keep their own codes.
- Setup failure (`failRunSetup`): a fresh, pre-dispatch run records
`environment_provider_unavailable` with a `providerFailure` (phase
`startup`, retryability `transient`). The run uses the bounded bootstrap
retry (30 s, then 60 s). An accepted interaction continuation keeps its
own continuation retry, as for `plugin_worker_unavailable`.
- Recovery: the new code is registered with the other bootstrap codes.
It is not sent to the generic continuation retry. It does not create an
execution-reconciliation hold, because no provider work started. On
exhaustion the stranded notice has the title "Sandbox provider was
temporarily unavailable" and a specific next action.
- UI: `ProviderFailureNotice` labels the `sandbox` provider as "Sandbox
provider". The task notice shows the provider failure, "Paperclip will
retry automatically" while a retry is scheduled, and Retry after
exhaustion.
- Docs: `doc/execution-semantics.md` describes the policy and the
sandbox ownership rule.
## Verification
Red before the fix, green after the fix:
- `server/src/__tests__/heartbeat-process-recovery.test.ts` › "transient
sandbox provider refusal during lease acquisition":
- "retries automatically and runs exactly one successful successor
without user action": the real heartbeat. The lease runtime returns a
Daytona 502 once, then delegates. Before the fix: one `failed` run and
no successor. After the fix: one `scheduled_retry` successor with no
hold and a `retry_scheduled` presentation. After promotion, exactly one
successor succeeds.
- "explains the block with a working Retry once automatic retries are
used up": three refusals. The task is `blocked` with a "Sandbox provider
was temporarily unavailable" notice. The presentation is `{ state:
"failed", canRetry: true, providerFailure: { httpStatus: 502 } }`, and
no execution-reconciliation hold exists. One `requestTaskRetry` is
accepted, and its run succeeds.
- `server/src/services/heartbeat/run-completion.test.ts` › "routes a …
sandbox provider lease refusal through bounded bootstrap recovery": the
real orchestrator wrapping. Only the fresh, structured 502 is retried. A
message-only 502, a 400, a run after provider dispatch, and a retained
native run stay `setup_failed`.
- `packages/plugins/sdk/tests/environment-acquire-error.test.ts`: the
real worker RPC. The test checks the create status (not the 404 cleanup
lookup), the errno, no provider text, the unchanged cleanup envelope,
and that forged envelopes are rejected.
Commands:
```sh
pnpm --filter @paperclipai/plugin-sdk exec vitest run tests/environment-acquire-error.test.ts tests/environment-creation-cleanup.test.ts tests/environment-resume-error.test.ts
cd server && npx vitest run src/__tests__/heartbeat-process-recovery.test.ts src/services/recovery/ src/services/heartbeat/run-completion.test.ts src/services/heartbeat/retries.test.ts src/services/legacy-execution-recovery.test.ts src/services/task-interruption-presentation.test.ts src/services/task-retry.test.ts src/__tests__/environment-runtime.test.ts src/__tests__/heartbeat-retry-scheduling.test.ts
cd packages/plugins/sandbox-providers/daytona && npx vitest run
pnpm --filter @paperclipai/plugin-sdk typecheck && pnpm --filter @paperclipai/server typecheck
```
I did not run a live Daytona 502 on staging. Re-check on staging after
deploy (see Risks).
## Risks
- Sandbox leaks or double creation: each retry is a new run with a new
durable creation record and a new provider name
(`paperclip-create-<operationId>`). The failed attempt stays owned by
its own record, and the existing creation reconciler settles it. A retry
never reuses or abandons it. While a provisional sandbox waits for
reconciliation, up to two more sandboxes can be created for the same
task. This is the same as a manual Retry today.
- Plugins built with an older SDK do not send the envelope. Their
refusals stay `setup_failed`, which is the current behavior.
- An in-process error with `status`/`statusCode` 500–504 or a connection
errno during lease acquisition is now retried twice. The budget is
bounded and shared with the other bootstrap codes.
- An RPC timeout on the host side is not classified by this change.
> 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 through
Claude Code as an agent 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
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
canary/v2026.1106.0-canary.5
|
||
|
|
558a5143ed |
fix(workspaces): reject damaged Git entries before workspace capture (#19160)
Workspace capture now checks that each .git entry is the root of its own repository before it reads any Git state. A structurally damaged entry (an empty .git directory, a gitfile for a removed worktree, an invalid HEAD, or missing objects/refs) stops capture with the closed reason remote_workspace_archive_invalid_git_repository instead of a raw "Command failed: git ..." error, and capture no longer saves an enclosing repository's state as the workspace's own. Other Git failures (dubious ownership, a full disk, a timeout, a failing Git program) keep their existing diagnostics. Metadata reads are bounded and never block on named pipes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
205ffcb606 | Verify live Claude and Codex ACPX eligibility before parking (#19295) | ||
|
|
b918ee165d |
test(evals): pin coherent chat feature follow-ups (#19411)
## Thinking Path > - Paperclip manages agent work and its permission boundaries. > - The chat catalog runs feature checks from an immutable source. > - The last full-ten attempt had five reported passes, four failures and one missing original. > - Local evidence identified narrow assertion, setup and evidence-retention repairs. > - This PR updates the trusted source pin and its exact descriptor snapshot. > - The next five-case measurement can test the affected features under the same model, clocks and native cap. ## Linked Issues or Issue Description Refs: #19395, #19405, #19093, #19084, #19112. The previous source remains preserved. This PR selects reviewed source `f5692c57dafaf9afbef479c25fcd277815932516` on `codex/chat-full-ten-coherent-qualified-f5692c57-20261106`, tree `b9e302d2ea7b605a60125dec8c81672eac7a3c57`. The full catalog and all core PRs remain unmerged. Core #19405 is included only as a source overlay. ## What Changed - Update the source target, exact descriptor snapshot and the matching pin checks. Preserve historical conditions, frozen source anchors and the prior query clarification. - Record Company ACK handling and Company/Personal negation corrections, plus the skill-update preview negation correction. A denied or unapplied action still fails when the agent claims it completed. - Disclose the Team setup change: a disposable catalog package selects the approved OpenCode runtime and same-company account. Other file bytes and non-runtime sidecar fields stay unchanged. Original and effective package identities and hashes are recorded. A real PostgreSQL install without caller overrides proves the saved runtime. This gives no end-to-end runtime-migration or override-handling credit. - Include defensive failed-turn evidence retirement and generic, schema-valid human-input examples. The old case 53 missing-original ENOENT cause is not diagnosed. The examples are a usability hypothesis, not a schema repair or native-success claim. All 244 YAML definition fields (query, assumed context and expected outcome) remain byte-identical versus source 936c. The Team derived runtime fixture and task setup context change is disclosed above. Native/human bounds, clocks, DeepSeek 1017, credentials, SDK506/efa, launcher, private interfaces, native cap two and zero automatic retries remain unchanged. Only descriptor `sourceFiles` and oracle versions change. The 196/244 gate stays unchanged. ## Verification Pin-local checks: ```sh node --test tests/runner-e2e/chat-full-catalog-testops-manifest.test.mjs tests/runner-e2e/chat-full-catalog-testops-adapter.test.mjs tests/runner-e2e/chat-completion-testops-contract.test.mjs ``` - All 49 pin/admission controls pass locally. They join all 244 identities, historical query overrides, native bounds and the exact 242-row descriptor snapshot. - Source qualification: 447 controls across 32 suites, full E2E TypeScript checks and normal contract generators pass. The separate 21 core contract controls are carried-forward component evidence; they are not added to 447. Local source checks used Node 26.4.0. AWS runtime settings are unchanged. - The selected source descriptor SHA256 is `24fe88af167724c6fb120d3984296aab47c993a65782158e03d80a936fbd94ed`. - The actual source launcher is byte-identical to 936c: SHA256 `2b9a6953ee51f0ea0dbbd475b0491d9a6c39a1d6835eeaa912e0dbb7cf987724`, Git blob `c88df15f509b2ac8638af826647a97d45f16bd1f`. - All 506 registered SDK inputs and the ten trusted helper bytes are unchanged. Five required source-helper joins pass. No SDK renewal or native build is part of this PR. - The current controller's 67 dependency inputs match the qualified frozen reporting graph. No reporting-lock, package, permission or workflow change is included. - Exact head `b833036fb477b55b05cbdf1f4feb2e2c5387b420` is green: CI run 42166216108 attempt 2 succeeded, Windows succeeded, and both current reviews are 5/5 with zero unresolved threads. The check rollup has 56 successes, three configured skips and one neutral result. New representative cases 53, 578, 580, 585 and 610 are unrun. A later uniform-ten regression is separate. - CI attempt 1 remains retained: one cloud-auth browser fixture read a trailing health response after context teardown began, after all task-recovery assertions had passed. One authorized rerun of only that failed job and its dependent checks succeeded on the unchanged head. This does not fix or qualify fixture reliability; no eval or model run was retried. The previous full-ten outcome remains five reported passes, four failures and one missing-original/reporting gap. Case 585 reached only its first preview before the false rejection; its update phase is unqualified. Case 610's old noncompletion and runtime-override journey remain unresolved. All original failures and evidence remain retained. No old result is regraded or replaced. ## Risks The local repairs may not produce a native pass. The new Team setup tests installation with an already selected runtime package; it does not qualify runtime migration. Failed-turn retirement has local regression coverage but does not establish the cause of the old case 53 reporting gap. The core guidance overlay remains unmerged and its native benefit is unproved. Normal protected merge and a new authenticated measurement are still required. ## Model Used OpenAI `gpt-6.1-sol` through Codex. It used code inspection, local test execution and GitHub tooling. The context window size is not exposed. No model inference or provider call was made for this PR's local checks. ## 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> |
||
|
|
66d3fa97a9 |
fix(workspaces): preserve ignored files in archived Git imports (#19402)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Durable workspace migration must preserve archived files and saved recovery data. > - An archived Git workspace can contain ignored recovery receipts and bundles in its encrypted restore point. > - The importer reconstructs a private repository from a verified source before it applies that restore point. > - Reconstruction omitted the source repository's local ignore rules. The restored files then changed classification, and the exact restore-point check failed. > - This pull request copies the verified local ignore rules into the private repository before restoration. > - The source stays unchanged, and the original restore-point integrity checks still apply. ## Linked Issues or Issue Description **What happened?** An archived workspace with saved files under an ignored recovery directory failed migration with `restored_contents_mismatch`. The task commit and staged index matched. The ignored files were restored, but the private repository treated them as untracked work because its `info/exclude` rules were missing. **Expected behavior** Reconstruction from an existing verified source must preserve its local ignore policy. Saved ignored files must survive migration and a cold object-store restore with their original classification. Import must not follow a symlink to an unrelated ignore file or edit the source repository. **Steps to reproduce** 1. Exclude a recovery directory in the managed source repository's `info/exclude` file. 2. Save staged edits, unstaged edits, an ignored recovery receipt, and an ignored binary bundle in an encrypted restore point. 3. Archive and remove the task worktree. 4. Import the archived workspace. Before this change, restore-point validation fails because the reconstructed repository lacks the local exclusion rule. **Paperclip version or commit** Reproduced on master at `879140cbbd`. **Deployment mode** Cloud workspace migration with a retained verified Git source, PostgreSQL, and encrypted durable object storage. The tests use real disposable PostgreSQL, Git repositories, and encrypted filesystem-backed object storage. Related migration work: #18826, #19138, and #19396. No duplicate open fix was found. ## What Changed - Copy the verified source's `info/exclude` file into the private reconstructed repository before applying its restore point. - Bound the read to one MiB. Reject symlinked, non-regular, hard-linked, or changing source files. - Keep the original source bytes unchanged. - Add regressions that check staged and unstaged edits, ignored recovery files, cold restore, and rejection of a symlinked ignore file. - Document the reconstruction behavior. ## Verification - Before the repair, the saved-ignored-files regression fails with `restored_contents_mismatch`. - After the repair, both focused cases pass. - At rebased head `81a0b1c041`, all 206 tests across migration, snapshot storage, archive, and migration concurrency suites pass. The merged interrupted-upload and nested-source approval regressions remain intact. - Server package typecheck passes. - Full repository typecheck and build pass at the rebased head. The full local test suite is running. The prior local full-test run was interrupted for rebase, and is not a passing result. - Initial CI hit a 15-second timeout in unchanged `activity-routes.test.ts`; the exact case passed in a focused local run. The browser shard lost its self-hosted runner, as confirmed by its check annotation; GitHub did not retain its full log. Those failures remain recorded. An unchanged retry was superseded by the required conflict rebase. At exact head `81a0b1c041`, all 53 CI checks and both required aggregate gates pass; Greptile is 5/5. The full local suite remains in progress, so no local full-suite PASS is claimed. A second automated review noted the existing nested-checkout counterpart. This PR fixes root reconstruction. The nested linked-checkout repair and its saved-ignored-files/cold-restore regression are implemented in #19409, which is required before the migration rollout uses nested imports. This PR leaves that sibling recovery path unchanged. The two identical scope warnings are addressed through that linked repair. ## Risks The copy applies only when an archived Git restore-point source is still present and verified. It changes only the private reconstruction directory. Missing sources retain the existing restoration path. Unsafe or unstable local ignore files block import and retain the original migration obligation. No database migration or saved format changes are included. ## Model Used OpenAI GPT-6 in Codex, with code editing, tool use, and test execution. The exact runtime model identifier and context-window size 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>canary/v2026.1106.0-canary.4 |
||
|
|
f5692c57da |
test(chat): declare question failure-retirement provenance
Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
8c01c75733 | fix(evals): retire failed chat work before bounded diagnostics | ||
|
|
612da1e8e0 |
test(server): drain throttled follow-up in busy recovered-source execution-control test (#19403)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The server runs durable execution-control workers, which include the workspace-recovery sweep > - `execution-control-work.test.ts` has a test for a Retry that commits during a busy recovered-source pass > - This test fails intermittently in PR runs that do not touch it, so it blocks unrelated PRs > - The test assumed that one `settle()` pass always ends idle, but the worker can correctly owe a throttled follow-up pass > - This pull request makes the test drive fake time through that throttle window while it waits for the sweep to finish > - The benefit is a deterministic test that still proves every source is visited and the worker becomes quiet ## Linked Issues or Issue Description **What happened?** `execution-control durable work > visits a newly committed earlier Retry during a busy recovered-source pass, then becomes quiet` failed in 2 of the last 250 failed `PR` workflow runs (jobs 127272079294 and 127206639630). Both failures were identical: `AssertionError: expected 1 to be +0` at `vi.waitFor` in `settle()` (`execution-control-work.test.ts:146`). The test took about 6s, which is the 5s `waitFor` limit plus overhead. **Expected behavior** The test passes on every run. Its result does not depend on runner speed. **Steps to reproduce** 1. Add a 300ms real-time delay in `registerExecutionControlWork` after `await duty.run()` and before `await duty.next()`. This simulates slow database I/O on a loaded runner. 2. Run `pnpm exec vitest run src/__tests__/execution-control-work.test.ts -t "busy recovered-source pass"` in `server/`. 3. The test fails 3 of 3 times with the same CI error. **Root cause** - `registerExecutionControlWork` lets a duty make one full pass per `EXECUTION_RECONCILIATION_INTERVAL_MS` (15s), measured from the start of the previous full pass. - The released recovery jobs call `worker.wake()` when they finish. If a wake arrives while the full pass is still reading its next deadline, `hasPending()` stays true. The coordinator then keeps its idle-tracked intent until the next permitted pass, up to 15s of fake time later. - `settle()` advances fake time by 15s and then waits 5s in real time for `idleWorkSnapshot().active === 0`. The full pass starts inside that 15s advance, so the follow-up is due after the window ends. The wait then expires. - On a fast machine the jobs usually finish after the deadline read, so the test passes. On a slow runner the order changes, so the test fails. - This is correct product behavior: the worker owes the follow-up pass. The defect is in the test's timing assumption. ## What Changed - In the busy recovered-source test, replace the bounded `settle()` loop with a `vi.waitFor` that advances fake time by `EXECUTION_RECONCILIATION_INTERVAL_MS` on each attempt. It waits until every source was visited and no idle-tracked work is active. - The test still calls `quiet(c)` after this, so it still asserts no further scheduling and no database calls during the next 90s. - The shared `settle()` helper is not changed, because `reschedules an earlier deadline…` asserts an exact `nextWakeAt()` after `settle()`. ## Verification - Red: with the 300ms delay injected (not committed), the test failed 3/3 times with the CI assertion. - Green: with the same injection at 300ms (3 runs) and at 1000ms (1 run), all four recovered-source Retry tests pass. - Without injection, the full `execution-control-work.test.ts` passed 5/5 times with 12 CPU-burner processes running. - `pnpm exec vitest run src/__tests__/execution-control-work.test.ts` in `server/`. ## Risks - Low risk. This is a test-only change. The test still requires every source to be visited, the worker to become idle, and then 90s of quiet. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edits, GitHub CLI). Default context window. No extended thinking mode. ## 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> |
||
|
|
1d529b4e95 |
test(chat): match production confirmation prompt whitespace
Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
c619ebbb7b |
test(evals): state team runtime fixture provenance accurately
Record original and effective package identities, distinguish preserved non-runtime fields from rewritten sidecar bytes, and document one-shot supervisor bootstrap with non-overwrite coverage. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
5dc8314e39 |
test(evals): normalize disposable team fixture runtime
Preserve the shipped team graph and non-runtime sidecar fields while selecting the campaign OpenCode account through the catalog package. Verify the actual no-overrides installer without model execution. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
c9e3418fef |
test(chat): declare human-input guidance provenance for affected cases
Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
754788b2be |
chore(runner): regenerate composed human-input guidance contracts
Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
88781d8386 |
fix(runner): show complete human-input invocation examples
Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
6be8ef50d1 |
fix(evals): scope negated skill-update completion claims
Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
223687bbb9 |
test(server): size heavy chat-channel tests to their workload to stop timeout cascades (#19404)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Chat channels connect GitHub, Slack, and other providers to tasks,
and `chat-channels.integration.test.ts` covers them against a real
embedded Postgres
> - Two parameterized tests do much more database work than the other
tests in the file, but they use the global 15s `testTimeout`
> - On a slow CI runner they time out. Vitest then keeps the timed-out
test body running, and that body breaks later, unrelated tests in the
same file
> - This failure blocks PRs that do not touch chat code
> - This pull request gives the two heavy tests a time budget that fits
their workload
> - The benefit is that a slow runner no longer turns one heavy test
into a cascade of 2–7 failures
## Linked Issues or Issue Description
**What happened?**
In run 42098299218 (job 127495756270, shard `General tests (chat
(3/3))`), 7 chat tests failed in two Vitest processes on a PR that
changed only unrelated test files:
- Process 1: `reactivates denied retry notices when authority is
restored during bounded pass` timed out at 15000ms. Then `… during
requester merge` failed with `A function to advance timers was called
but the timers APIs are not mocked`.
- Process 2: `preserves one current GitHub Retry across backlog_51` and
`… consumed_backlog_52` timed out at 15000ms. About 110 tests later,
three pre-run FIFO tests failed: `advances pre-run FIFO selection past a
suppressed page with microsecond timestamps (manual)` (`expected +0 to
be 1`), `retires an already-visible pre-run FIFO notice once without
publishing scheduler failures` (`expected [] to have a length of 1`),
and `retries a contended pre-run FIFO receipt before I/O and observes
cancellation after the lock releases` (`expected undefined to match
object { state: 'retry' }`).
**Expected behavior**
These tests pass on a slower runner. A timeout in one test does not
change the result of a different test.
**Steps to reproduce**
1. On `master`, in `server/`, run:
`pnpm exec vitest run src/__tests__/chat-channels.integration.test.ts -t
"GitHub Retry across (backlog_51|consumed_backlog_52)|advances pre-run
FIFO selection past a suppressed page|retires an already-visible pre-run
FIFO notice once|retries a contended pre-run FIFO receipt before I/O"
--testTimeout=1200`
2. The lower timeout simulates a slow runner. The two backlog tests time
out, and then the same three FIFO tests fail with the same assertions as
in CI. The `scoped` FIFO case passes, as it did in CI.
3. Run the same command without `--testTimeout`. All 6 tests pass.
**Root cause**
- The workload is large. `backlog_51` and `consumed_backlog_52` deliver
51–52 real inbound GitHub messages through the full delivery path.
`bounded pass` inserts and authorizes 100 rejected retry notices.
- CI timing data supports this. On passing runs these tests take
5.4–7.7s (backlog) and 3.6–5.3s (bounded pass), and lighter tests in the
same file take 0.5–1s. On the failing runner, the lighter tests were
2–3x slower (for example, `idle` took 1856ms instead of 500–680ms). This
put the heavy tests over 15s.
- When Vitest times out a test, it does not stop the test body. The body
keeps running while the next tests start:
- The unfinished backlog tests left 51+ `deferred_issue_execution`
inbound wakeups. The pre-run FIFO tests use unscoped, bounded passes
(`enqueueInboundWakeupPublications`, `processPendingPublications`). The
leftover rows filled those pages, so the new test's row was not reached.
- The unfinished `bounded pass` body later ran its `finally` block,
which calls `vi.useRealTimers()`. This disabled the fake timers that
`requester merge` had just installed.
## What Changed
- `preserves one current GitHub Retry across %s`: set an explicit 60s
timeout.
- `reactivates denied retry notices when authority is restored during
%s`: set an explicit 60s timeout. Increase the `bounded pass` wait for
its held 100-row reconciliation from 10s to 30s, so that the inner wait
does not become the new limit.
- The same file already uses explicit 20s, 25s, 30s, 40s, 60s, and 90s
budgets for other heavy tests. No product code changed.
## Verification
- Red: the reproduction command above fails on `master` with the CI
cascade. A tighter `--testTimeout=1600` on `reactivates denied retry
notices` also shows that later modes fail after `bounded pass` times
out.
- Green: the same reproduction command with this change (still
`--testTimeout=1200` for all other tests) passes 10/10 selected tests,
including the three FIFO tests.
- `pnpm exec vitest run src/__tests__/chat-channels.integration.test.ts
-t "GitHub Retry across|reactivates denied retry notices|pre-run FIFO"`:
46 passed.
## Risks
- Low risk. This is a test-only change. A real hang in these two tests
now fails after 60s instead of 15s. This is well inside the job limit.
- The cascade can still occur if one of these tests needs more than 60s.
That would mean a runner about 10x slower than a normal one.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits, GitHub CLI). Default context
window. No extended thinking mode.
## 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>
|
||
|
|
8cb77e382f |
fix(evals): retain direct ACK identity and wake policy
Co-Authored-By: Paperclip <paperclip@paperclip.ing> |
||
|
|
ec94e43c4a |
test(server): budget the 130 MB isolated re-home test for its data volume (#19400)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Isolated repository re-home moves a task workspace onto the shared project clone, so the private clone can be deleted > - One re-home test moves two 65 MiB files on purpose, to prove that large changes still move > - On CI this test times out at the 15000 ms default in pull requests that do not touch re-home > - The test does a fixed, large amount of CPU work, and a loaded runner cannot finish it in 15 seconds > - This pull request gives that one test a 60000 ms budget that matches its data volume > - The benefit is a stable server shard, with no change to what the test proves ## Linked Issues or Issue Description **What happened?** A survey of failed `PR` workflow runs found intermittent failures of one test in pull requests that do not touch re-home: ``` FAIL src/__tests__/isolated-repository-rehome.test.ts > isolated repository source re-home > re-homes a workspace whose changes exceed the default restore point limits Error: Test timed out in 15000ms. ``` Examples: jobs 127465954304, 127499682176, and 127538360101 (shard `general-server-without-chat` 7/12). The survey counted 4 such failures. The other 27 tests in the file passed in each of these runs. **Expected behavior** The test passes on every run, and it still proves that a 65 MiB untracked file and a 65 MiB saved ignored file move with an exact snapshot match. **Steps to reproduce** Run the unchanged test on master while the CPU is oversubscribed: ```sh cd server node load.mjs 72 pnpm exec vitest run src/__tests__/isolated-repository-rehome.test.ts -t 'exceed the default' --reporter=verbose ``` `load.mjs` starts 72 busy-loop Node processes (4 per core on an 18-core machine) and runs the command. The test fails with `Test timed out in 15000ms`. **Root cause** The file sizes are part of the test contract. #17410 added the test to prove two fixes that only show above 64 MiB: `applyRestorePoint` streams `git diff --binary` into `git apply`, and the ignored-file read sizes its buffer from the tree. So the files cannot get smaller. Each full pass over the workspace writes both files into a Git object store, so it hashes and deflates 130 MB. One test run makes about eight passes: the test's `before` snapshot, the re-home `before` snapshot, the capture, the import into the shared clone, the apply, the two verification snapshots, and the test's final snapshot. That is about 1 GB of SHA-1 and zlib work, plus the byte comparison. I timed the phases with the re-home hooks on an idle machine: snapshot 0.6 s each, capture and import 1.9 s, worktree add and apply 0.9 s, verification 1.0 s. With 2 busy processes per core the same phases took 1.3 s, 3.9 s, 1.3 s, and 2.1 s. The work is CPU-bound and scales with load. No phase waits on a timer, a lock, or a background task. The 15000 ms default is the cause. ## What Changed - Give `re-homes a workspace whose changes exceed the default restore point limits` an explicit 60000 ms test budget. - No other test changes. No production changes. No retries, no skips. ## Verification Red, on master, 72 busy-loop processes: ``` × ... re-homes a workspace whose changes exceed the default restore point limits → Test timed out in 15000ms. ``` Green, with this change, same load: ``` ✓ ... re-homes a workspace whose changes exceed the default restore point limits 30414ms ``` So the test needs about 30 s at 4 processes per core, which is half of the new budget. A hung re-home still fails inside 60 s. Full file, idle: 28/28 tests pass. ```sh cd server pnpm exec vitest run src/__tests__/isolated-repository-rehome.test.ts ``` ## Risks - Low risk. One test budget changes. - A real slowdown in the re-home path of up to 4 times would no longer fail this test. Other tests in the file keep the 15000 ms default. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edits) 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> |
||
|
|
9668e73b4b |
fix(native): continue a turn automatically when reattachment loses its runner (#19399)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Native runs in managed sandboxes recover through reattachment: after a server restart, and for a same-run retry when a turn ends without a semantic result > - Reattachment fails closed when the original runner or sandbox is gone (`native_remote_recovery_lease_mismatch`, `runner_remote_process_identity_unavailable`) > - Saved-work recovery then saves the files, but the task stays blocked with "Saved work recovered. Send a message to continue." The recovered hold offers no Retry > - A restart, deploy or maintenance window must not need a user message to continue work > - This pull request requests the task Retry automatically, once, when it is safe, and offers Retry on the recovered hold when it is not > - The benefit is that interrupted work continues after infrastructure events, and a user who must act gets a Retry that works on first use ## Linked Issues or Issue Description **What happened?** A native task in a managed sandbox was interrupted. In one case a server restart claimed a queued user-comment turn for reattachment. In another case a turn completed without a semantic result, and the same-run retry claimed the run for reattachment with no restart. In both cases the original runner or sandbox lease was gone. The run failed with `native_remote_recovery_lease_mismatch` or `runner_remote_process_identity_unavailable`. Recovery saved the work. The task stayed `blocked` with "Saved work recovered. Send a message to continue." The execution blocker had `canRetry: false`. A manual `POST /api/issues/:id/retry` started a run that succeeded. **Expected behavior** After an infrastructure interruption, the task continues automatically when the interrupted turn's outcome is known. Otherwise the task offers a Retry that works on first use. **Steps to reproduce** 1. Start a native task in a managed sandbox from a user comment. 2. Lose the sandbox lease or the remote runner process (for example, restart the server during a maintenance window). 3. Let reattachment fail and saved-work recovery complete. 4. See that the task waits for a new message and offers no Retry on the execution blocker. **Root cause** Reattachment (`reattach_remote_runner`) correctly refuses to replace a missing runner or allocation. Nothing then continued the user's request. The automatic continuation rule only allows a new user turn after reconciliation. `getExecutionBlocker` hid Retry whenever a workspace restore hold existed, also after the files were verifiably recovered. The same-run retry path also re-enters the restart-recovery owner, so its event said "after server restart" with no restart. ## What Changed - New `server/src/services/heartbeat/restart-interrupted-turn.ts`: after a reattachment claim executes, if the run failed because its runner or allocation was lost, request the same task Retry that `POST /issues/:id/retry` creates. It runs once per run (idempotency key `restart-interrupted-turn:<runId>`), under the authority of the user who sent the message, assigned the task, or clicked Retry. It requires that the run recorded no provider turn or that its last provider turn is settled. System wakes, in-flight turns, and a Retry that this hook already requested do not continue automatically. - `heartbeat/recovery.ts`: call the hook after each reattachment claim. Label same-run retry reattachment as "Reattaching to the existing remote runner to resume this run", and add `trigger: "same_run_retry"` to the event payload. - `execution-blocker.ts`: a failed run whose saved work is verifiably recovered now offers Retry (`canRetry: true`) with "Saved work recovered. Retry or send a message to continue." An intentional Stop keeps "send a message" only. - `task-retry.ts`: optional `initiatedBy: "restart_recovery"` records the activity as actor `system`/`restart-recovery` with `onBehalfOfUserId`. - `doc/execution-semantics.md`: document the rule. ## Verification - New regressions in `server/src/services/workspace-recovery.test.ts` use the real recovery, Retry and dispatch services on embedded Postgres: - `continues a user turn once ... after $title and recovery saved the work` (3 cases: restart lease loss before the turn, same-run retry with the runner gone after a settled turn, same-run retry with the lease gone after a settled turn). Each asserts one queued successor bound to the Retry and the user's authority after recovery, idempotent hook calls, system activity attribution, and no second automatic attempt for the automatic Retry's own failure. - `does not continue automatically when $title, and offers a Retry that works on first use` (in-flight turn, system wake, unrelated error). Each asserts no successor, `canRetry: true` on the blocker and the interruption presentation, and one successor after one Retry click. - Red before the fix: all 6 tests fail (no successor, blocker `canRetry: false`). Green after the fix. - `npx vitest run src/services/workspace-recovery.test.ts src/services/task-retry.test.ts src/services/task-interruption-presentation.test.ts src/services/execution-blocker-read.test.ts src/services/heartbeat/recovery.test.ts src/services/explicit-native-continuation.test.ts src/services/quarantined-task-retry.test.ts src/__tests__/workspace-restore-repair-route.test.ts src/services/native-runtime/native-failed-workspace-recovery.test.ts`: 1098 passed. - Not verified on staging. After the next deploy, check a task interrupted by a restart: it should continue without a message, or show Retry. ## Risks - The automatic Retry uses the original user's authority. Admission rechecks that user's current permission, stop proof, saved-work recovery, approvals, pauses, budget and ownership. A refused request leaves the visible Retry. - A settled turn that ended without a result is continued with a fresh Retry turn from saved work. A turn whose outcome is unknown (in flight) is not. - The hook runs after the claim's execution returns. If the server stops between the failure and the request, the task shows Retry instead. - Not in scope: same-run disposition recovery still cannot start a new runner in the existing sandbox after the original remote runner exits. This change continues the task with one fresh Retry turn instead. If that Retry turn also ends without a result and loses its runner, the task shows Retry. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edits, local 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 Related: #19355 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6a30965f80 |
fix(evals): acknowledge actual human secure secret saves
Co-Authored-By: Paperclip <paperclip@paperclip.ing> |
||
|
|
e7fb554647 |
test(server): load setup-token route modules during suite setup (#19398)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The setup-token login route lets an owner sign in to Claude for a
sandbox environment
> - The route suite `server/src/routes/setup-token-route.test.ts` fails
on CI in pull requests that do not touch it
> - The first test in the suite times out at 15000 ms, and the other 29
tests pass
> - The first test pays the one-time import cost of the agent route
graph inside its own test budget
> - This pull request moves that import to suite setup, which has a
30000 ms hook budget
> - The benefit is a stable server shard with no change to what the
tests prove
## Linked Issues or Issue Description
**What happened?**
A survey of failed `PR` workflow runs found intermittent failures of one
test in pull requests that do not touch this area:
```
FAIL src/routes/setup-token-route.test.ts > company-and-environment setup-token route — full path > drives start, status, prompt, code, and completion with no agent id
Error: Test timed out in 15000ms.
```
Examples: jobs 127423956746 and 127457298585 (shard
`general-server-without-chat` 12/12). The survey counted 5 such
failures.
**Expected behavior**
The full-path test passes on every run. The test budget measures the
route behavior, not the module import.
**Steps to reproduce**
Run the unchanged suite on master while all CPU cores are busy:
```sh
cd server
node load.mjs 24 pnpm exec vitest run src/routes/setup-token-route.test.ts --reporter=verbose
```
`load.mjs` starts 24 busy-loop Node processes and runs the command. The
first test fails at 15005 ms.
**Root cause**
`createApp()` called `vi.importActual("../routes/agents.js")` and four
other modules on each call. The first call loads and transforms the full
agent route graph. I timed it: `createApp()` took 11670 ms of the 11698
ms test on an idle machine. The HTTP steps took less than 30 ms. On a
loaded CI runner the import alone crossed the 15000 ms test budget.
Later tests reused the module cache, so only the first test failed.
## What Changed
- Add `loadRouteModules()`, which imports the route and logger modules.
- Call it once in the suite `beforeAll`, after `vi.resetModules()` and
the mock registration. The hook budget is 30000 ms in
`server/vitest.config.ts`.
- `createApp()` now reuses the loaded modules. Each test still builds a
fresh Express app, a fresh transport, and a fresh log capture.
- This follows the pattern in
`server/src/__tests__/agent-skills-routes.test.ts` (#16396).
- No timeout changes, no retries, no skips.
## Verification
Red, on master, with 24 busy-loop processes on an 18-core machine:
```
× ... drives start, status, prompt, code, and completion with no agent id 15005ms
```
Also red on an idle machine on 1 of 3 runs (15006 ms; the other runs
took 11790 ms and 13748 ms).
Green, with this change:
- Idle, 3 runs: 30/30 tests pass. The first test takes 42 to 46 ms.
- With 24 busy-loop processes: 30/30 tests pass. The first test takes 40
ms. The file takes 19.4 s in total.
```sh
cd server
pnpm exec vitest run src/routes/setup-token-route.test.ts --reporter=verbose
```
`tsc --noEmit -p server` reports no errors in this file.
## Risks
- Low risk. The change touches one test file only.
- The import cost now counts against the 30000 ms hook budget. Locally
it is 12 to 15 s idle. The mocks register before the import, as before,
so each test sees the same mocked services.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits) 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>
|
||
|
|
da34bcd48d |
fix(workspaces): resume archived imports from their saved upload (#19396)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Durable workspaces let a replacement web server restore saved task files. > - Legacy workspace imports keep a journal and an encrypted upload receipt across interruption. > - An archived Git import can rebuild a missing source from its saved task commit and edits. > - A fresh clone can also contain newer remote refs. Those refs change the archive manifest even when the task files are unchanged. > - This pull request recovers the exact authenticated upload for an interrupted archived Git import. > - The original journal, source binding, task binding, and execution checks still control publication. ## Linked Issues or Issue Description **What happened?** An archived Git workspace import stopped after its upload persisted. A retry rebuilt the source from the remote repository. New remote refs changed the archive manifest, so the original upload receipt rejected the retry with `remote_workspace_snapshot_operation_content_changed`. **Expected behavior** The retry must recover its original saved upload and complete the same import. It must keep the saved task commit, staged edits, unstaged edits, lifecycle state, and task binding. A changed source or task binding must still block publication. **Steps to reproduce** 1. Archive a Git task with staged and unstaged edits, save its restoration recipe, and remove its local repository. 2. Start the workspace import and lose the upload reply after object persistence, or interrupt its independent restore after the upload is acknowledged. 3. Advance the remote default branch and add an unrelated branch. 4. Retry the original import. Before this change, both interruption cases fail with the same content-change error. **Paperclip version or commit** Reproduced on master at `d8b0eae68772a3bd2bb4a8163b5ce95764b97cff`. **Deployment mode** Cloud workspace migration with external PostgreSQL and durable encrypted object storage. The regression tests use real disposable PostgreSQL, an HTTP Git remote, and encrypted filesystem-backed object storage. Related merged work: #18826 and #19138. No duplicate open fix was found. ## What Changed - Add scoped recovery of an acknowledged snapshot or a pending upload whose object persisted. Authenticate the archive before returning it. Recovery alone does not publish the snapshot. - Resume archived Git imports from those exact saved bytes. Keep the original operation and all publication checks. - Lock and recheck each saved nested source’s company, project, and repository identity in the final publication transaction. - Keep mutable local sources on the existing capture and content-comparison path. - Add regressions for both interruption points, unrelated remote changes, source revocation during recovery, and local edits after interruption. - Document the retry behavior. ## Verification - Before the repair: both new archived retry regressions fail with `remote_workspace_snapshot_operation_content_changed`; the mutable-source control passes. - After the repair: all 204 tests in the migration, snapshot storage, archive, and archive environment suites pass. The suites use real PostgreSQL and encrypted storage. - Server package typecheck and full repository typecheck pass on `bffebbab60edbdf50b42fc37a9e58beb01ceb94d`. - An added source-revocation test initially expected a later expiry error. The existing publication fingerprint rejected the change first. The test now requires that earlier `remote_workspace_migration_publication_changed` error and still checks that authority was not published. - The review finding was reproduced before the safety repair: changing a saved nested repository URL after upload still allowed publication. The new regression requires rejection, and unchanged approved nested sources still restore. - Full repository typecheck and build pass on the final head. The full local test command stopped at one Slack manager authorization fixture with `socket hang up`; the exact case then passed in isolation on the same head, and all three CI chat shards passed. The original local failure is retained. - The first CI server shard run had 4,724 passes and one runtime-process readiness failure in an unchanged fixture. The exact fixture passed in isolation. One unchanged rerun of that shard passed. The first failure remains recorded in the workflow history. - The earlier candidate’s full local test run was stopped after the confirmed review finding; that interrupted run is not reported as a pass. - All CI gates, including `ci / verify` and `ci / e2e`, pass on `bffebbab60edbdf50b42fc37a9e58beb01ceb94d`. Greptile is 5/5 on that head, with no remaining findings or unresolved review threads. ## Risks Recovery applies to archived Git sources, whose saved recipe and lifecycle checks still run before publication. A source change during recovery must keep the snapshot unpublished. Missing or corrupt saved objects still fail authentication or retain the existing import obligation. Mutable local sources still reject changed contents. This change adds no database migration and changes no saved archive format. ## Model Used OpenAI GPT-6 in Codex, with code editing, tool use, and test execution. The exact runtime model identifier and context-window size 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> |
||
|
|
5c185db77c |
fix(tree-control): resume work a subtree pause interrupted (#19394)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Task tree control lets the board pause a task tree and then resume
it.
> - A pause stops the runs that are active in the tree. The release must
then continue the work that was pending.
> - A child task whose run was active at pause time did not start again
after release. The child became blocked and asked for a user message.
> - A pause is an operator hold, not a failure. It must not strand work,
and it must not require reconciliation for a run that never started.
> - This pull request re-dispatches interrupted work one time on
release, and it settles a legacy Stop that never entered the adapter.
> - The benefit is that pause and resume keep a task tree moving without
manual recovery, and real safety gates stay in force.
## Linked Issues or Issue Description
**What happened?**
A parent task delegated two children. One child's run was active (in
host workspace preparation, before the adapter started) when the board
paused the parent's tree. The pause cancelled that run. After the board
released the hold, no new run started for that child. The child became
`blocked` with the execution blocker cause
`legacy_execution_requires_reconciliation`. Later the next action became
"Send a message to continue." A sibling child that had no active run at
pause time resumed normally.
**Expected behavior**
A run that a subtree pause cancels is an operator hold. On release, the
pending work starts again one time, automatically. It does not go
through execution reconciliation when the run never entered its adapter.
It does not wait for a user message.
**Steps to reproduce**
1. Create a parent task and a child task that is assigned to an agent.
2. Wake the child. While its run is in host preparation (or queued),
call `POST /api/issues/:parentId/tree-holds` with `{ "mode": "pause" }`.
3. Call `POST /api/issues/:parentId/tree-holds/:holdId/release` without
`wakeAgents`.
4. Before this change, no new run starts, and `getExecutionBlocker`
reports `legacy_execution_requires_reconciliation` for the child.
**Root cause**
- (a) The pause records each interrupted run in the hold member snapshot
(`activeRunId`). Nothing used that record on release.
- (b) Release woke tasks only when `metadata.wakeAgents === true`.
Without it, release relied on stranded-issue recovery. That recovery
stands down when the latest run was cancelled by an operator
(`isOperatorCancelledRun`). So the interrupted child had no owner.
- (c) The pause Stop of a legacy run in preparation is written directly
as `cancelled`. Legacy terminalization classifies a cancelled run with
no bootstrap evidence as an unknown outcome. It records
`legacy_execution_requires_reconciliation`, although the gated dispatch
never entered the adapter.
- Native runs: the verified native Stop does not create the
reconciliation hold, but gap (b) applies to native runs too. The release
change is runtime-independent.
## What Changed
- `issueTreeControlService.findOwedPauseResume` decides if a task is
owed one resume. The task's latest run must be an operator Stop that a
pause hold member snapshot names, recorded while that hold was active.
The task must still be executable by the same agent, and no pause may
still cover it. The obligation belongs to the run, so overlapping pauses
keep it until the last covering release.
- New `services/issue-tree-resume.ts` re-dispatches owed work with wake
reason `issue_tree_resumed` (`holdId` and `interruptedRunId` in the
context). A compare-and-set claim (`treeHoldResume`) on the interrupted
run makes the resume happen at most once. A failed wake returns the
claim.
- Pause release and `mode: "resume"` call it for every member of the
released hold, also without `wakeAgents`. When `wakeAgents` is true, the
existing loop wakes every executable member.
- A task that still has an execution blocker is not replayed. The
release writes `issue.tree_resume_wake_skipped`, the blocker stays
visible, and the obligation stays unclaimed.
- `releaseHold` updates only an `active` hold. A repeated release gets
`409`.
- `settleCancelledLegacyPreparation`: when the executor that owned a
cancelled legacy run proves that dispatch never entered the adapter, it
records bootstrap evidence and resolves that run's
`legacy-execution:<runId>` hold as `false_positive`. It also corrects an
automatic no-replay disposition and restores a `blocked` status only
when that disposition made the change. Then it re-dispatches owed work,
for a release that happened while the Stop was unwinding. It requires a
requested Stop with no existing recovery receipt, and no process,
session, native finalization, unreleased lease, or execution-start
event.
- `settleUnrecoverableExecutions` waits while a legacy executor still
holds its controller lease, and records a `legacyCancellationBlock`
receipt when it blocks a task for a cancelled legacy run.
- Docs: `doc/execution-semantics.md` and `doc/composer-stop.md` describe
the resume rule.
## Verification
- New regression
`server/src/__tests__/issue-tree-pause-resume-run.test.ts` uses embedded
Postgres, the real heartbeat, and the real tree-control routes:
- A run paused during preparation: nothing runs during the hold, there
is no blocker, and release creates exactly one `issue_tree_resumed` run
that succeeds. A second release returns `409` and creates no run.
- Release before the stopped preparation finishes unwinding (with the
periodic disposition run in between): exactly one resumed run after the
executor settles.
- An automatic no-replay disposition that ran first: the blocker clears,
the status returns to `in_progress`, and release resumes once.
- Overlapping child and parent pauses: no resume while the parent pause
remains, then exactly one resume after it is released.
- A queued run cancelled by the pause: release creates exactly one
resumed run.
- A task reassigned to a user during the hold: no resume.
- A mid-turn legacy Stop with unknown outcome: no replay, the
reconciliation blocker remains, and `issue.tree_resume_wake_skipped` is
logged.
- Red before each fix: the first 4 cases failed on master; the 3 review
cases failed on the first PR commit. All 7 pass now.
- Also passed locally: `issue-tree-control-routes`,
`issue-tree-control-service`, `heartbeat-workspace-busy`,
`heartbeat-undispatched-execution-wait`, `execution-control-work`,
`legacy-workspace-restore-recovery`, `legacy-continuation-authority`,
`task-retry`, `legacy-execution-recovery`, `agent-conversations`,
`heartbeat-retry-scheduling`, `satisfied-connection-intents`.
- Not verified here: a live staging pause/resume. Re-check on staging
after deploy.
## Risks
- Behavior change: release now starts the work that the pause
interrupted, also without `wakeAgents`. Before, that work stayed idle.
Other idle tasks still wake only with `wakeAgents`.
- The settlement trusts the executor's adapter-entry flag. The flag is
set only inside the atomic dispatch gate, which refuses a cancelled run.
The settlement also requires no process, session, lease, or execution
event.
- A native run whose Stop has not finalized at release time is not a
target. Stranded recovery and explicit wake still apply.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edit, 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
- [ ] 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>
|
||
|
|
6ca7b151f7 |
feat: compact an agent's context from the task Properties panel (#19378)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - A task's Properties panel shows the assignee agent's context window
(paperclipai/paperclip#19273)
> - The board can see that the context is nearly full, but it cannot act
on it
> - A full context makes the agent compact at an unplanned time, often
in the middle of work
> - This pull request adds "Compact now" to the Context hover card
> - The benefit is that the board can compact the agent's context
between runs, at a time it chooses
## Linked Issues or Issue Description
Refs paperclipai/paperclip#19273
**Problem**
The Context row shows how full the agent's context window is. The board
cannot ask the agent to compact it.
**Proposed solution**
A "Compact now" button in the Context hover card. It runs the harness's
own compaction: Codex `thread/compact/start`, and Claude's `/compact`
command through ACP.
**Alternatives considered**
A separate PRP `session.compact` command. Both harnesses run compaction
as a turn, so a compaction turn keeps the existing durable turn binding,
interruption, and recovery. A separate command needs a second turn
lifecycle.
## What Changed
- UI: the Context hover card shows Compact now with three states:
available, Compacting…, and unavailable with a reason (agent mid-run,
harness unsupported, task closed). The Storybook story shows each state.
- Server: `POST /api/issues/:id/context/compact` (board only,
`decideIssueAccess` `issue:mutate`, activity log
`issue.context_compaction_requested`). It queues a short run marked by
`contextSnapshot.contextCompactionRequestId`. `GET
/issues/:id/context-usage` returns the `compaction` state.
- Server: a compaction run does no task work. The native status arbiter
preserves the task status. The lifecycle skips handoff, liveness
continuation, review-path disposition and the issue comment, as it does
for goal-control runs.
- Runner TS: `startTurn({ compact: true })`. The Codex harness session
sends `thread/compact/start`. The runnerd transport maps it to PRP
`turn.start` with `compact: true` (Codex) or `text: "/compact"`
(Claude/ACPX). The native runtime option `sessionCompaction` requires a
resumed session and settles with a synthesized yielded result.
- Runner Rust: `turn.start` accepts `compact: true` for Codex.
`CodexProvider::start_compaction_turn` sends `thread/compact/start` and
binds the turn from the `turn/started` that follows the empty response.
- Fix (from #19273): Codex emitted `context.compacted` on both the start
and the end of one compaction item. It now emits it once, and the count
is distinct by `compactionId`.
- Fix (from #19273): the conversation event view dropped
`context.compacted`, so a finished run never showed the divider. The
view now keeps compactions, plus context readings for runs that
compacted. A finished run's compactions show as thread dividers.
- Fix: the Codex harness stamps before/after sizes on the compaction
event. Codex reports the compacted size before the compaction item ends,
so the old neighbour-based guess showed the wrong size.
## Verification
- `pnpm -r typecheck`, `pnpm build`, `pnpm check:token-gates`, `node
scripts/storybook-catalog.mjs`: pass.
- New tests: `server/src/__tests__/context-compaction.test.ts`,
`status-arbiter.test.ts`, `issue-context-usage.test.ts` (duplicate
compaction counts once), `ui/.../context-usage-row.test.tsx`,
`native-session-runtime.test.ts` (context compaction), Codex driver and
runnerd transport tests, Rust
`codex_compaction_turn_binds_the_turn_reported_after_its_empty_response`.
Rust `codex_provider` suite: 115 passed.
- `pnpm test:run` locally: one failure in
`chat-channels.integration.test.ts`. It reads
`.claude/skills/slack/SKILL.md` from the main checkout, which is not
present in this worktree. It is not related to this change.
- Live, local worktree instance, Codex agent on the Paperclip runner:
clicked Compact now. The run succeeded. Context went from 35k to 9.4k
tokens, and later 9.4k to 9.2k. The task stayed In Review. The thread
shows "Context compacted · 9.4k → 9.2k tokens" and the meter dropped.
- Live Claude proof: pending. The local Claude agent needs an Anthropic
API key. claude-agent-acp handles `/compact` as an echo-less turn, and
the runner already maps its "Compacting completed." notice to
`context.compacted`.
## Risks
- A compaction run resumes the agent's session. If the session cannot
resume, the run fails with `native_context_compaction_unavailable` and
the task is unchanged.
- Compact now is not available while the agent runs on the task, because
compaction interrupts the active turn.
- The conversation view now returns context readings for runs that
compacted. Runs that never compacted return the same events as before.
- An old runner binary ignores `compact` and rejects the `turn.start`
because it has no text. It does not start an ordinary turn.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, through 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
- [ ] 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>
|
||
|
|
240d46e2ec |
fix(native-runtime): join the deferred observer seen when a duplicate append arrives (#19397)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The native runtime writes runner events through
`PaperclipControlPlanePort.appendEvent`.
> - A terminal event can defer its post-commit observer. A duplicate
append of the same event must join that observer. It must not run
`onDuplicateEvent`.
> - The duplicate looked up the observer only after its own event
transaction finished. If the original observer settled during that
transaction, the observer left the map. Then the duplicate ran
`onDuplicateEvent`.
> - This made the test "joins the exact deferred observer on duplicate
append..." fail on slow CI (job 127633060467, and once on Windows).
> - This pull request captures the pending observer when the duplicate
arrives. The join decision no longer depends on database latency.
> - The benefit is a deterministic join and a stable test.
## Linked Issues or Issue Description
**What happened?**
The server test `paperclip-control-plane-port.test.ts > joins the exact
deferred observer on duplicate append and replays it after a new port
opens` failed intermittently with `Error: must join the original owned
observation`. The test gives the duplicate append 30 ms and then
releases the original observer. On a slow runner, the duplicate's event
transaction took longer than 30 ms. The original observer settled and
its `.then` handler removed the map entry. The duplicate then found no
observer after its transaction and ran `onDuplicateEvent`. (The CI
failure took 90 ms.)
**Expected behavior**
A duplicate append that arrives while the original deferred observer is
pending joins that observer. Database latency does not change this
result.
**Steps to reproduce**
1. Add a 60 ms sleep inside the `appendEvent` transaction when the
receipt disposition is `duplicate`.
2. Run `pnpm vitest run
src/services/native-runtime/paperclip-control-plane-port.test.ts -t
"joins the exact deferred"` from `server/`.
3. The test fails with `must join the original owned observation`. This
is the same error and stack as CI.
**Paperclip version or commit**
`master` at
canary/v2026.1106.0-canary.3
|
||
|
|
879140cbbd | Retain ACPX read events before idle sidecar replacement (#19290) | ||
|
|
958f9aa9f3 |
feat(issues): limit agent-created task trees to parent and child (#19312)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Agents split work into tasks. A task can have a parent task. > - Today agents can nest tasks to any depth: parent, child, grandchild, and deeper. > - Deep agent-made trees are hard to follow, and an agent that works on a child task makes grandchildren by default. > - This pull request limits agent-created trees to two levels: a top-level task and its children. > - A request for a grandchild does not fail. The server puts the new task beside the requester and tells the agent. > - Board users keep full control. They can still nest to any depth. Existing deep trees stay valid. > - The pull request also records which task created each new task, so the audit trail stays complete when the parent changes. ## Linked Issues or Issue Description No public issue exists. This is the feature description. Related: #16853 (budgets that include descendant work) also reasons about task trees; this change does not alter its traversal. **Subsystem affected** Issues (tasks): create, child create and move in the issue service and routes. Agent tools, chat suggestions and creation provenance use these paths. **Problem or motivation** Agents create subtasks as deep as they want. An agent that runs on a child task gets that child as the default parent, so its follow-ups become grandchildren. The trees become deep and hard to read. The parent link is also the only record of which task created a follow-up, so that record is lost when the parent changes. **Proposed solution** Limit agent-created trees to a parent and its children. When an agent asks for a grandchild, put the new task under the requester's parent and return `parentAdjusted`. Do not reject the request. Keep board users unrestricted. Record the originating task in a new server-set `origin_issue_id` column. Keep `origin_run_id` as the originating run. **Alternatives considered** Reject grandchild requests with an error. We did not do this, because agents then fail mid-task. Add a database constraint. We did not do this, because existing deep trees must stay valid and nesting can return later. ## What Changed - Add `MAX_AGENT_TASK_HIERARCHY_DEPTH` (2) and the `IssueParentAdjustment` type in `packages/shared`. - Add `resolveNewTaskParent` and `planIssueParentChange` in `server/src/services/issues.ts`. `create`, `createChild` and `update` use the agent limit by default (`parentDepthPolicy: "flatten"`). - The issue create, `/children` and PATCH routes pass `allow_nested` for board users. For agents, the routes check authorization against the parent that the server actually writes. - The requester keeps its behavior when the server moves a task beside it: `blockParentUntilDone` blocks the requester, and the new task inherits the requester's workspace and privacy. - The server never moves a task above a low-trust boundary root that contains the requester. - These subsystems keep any depth, because they find their work by parent: watchdog reviews, monitor recovery, email-channel placement, and routines that a person configured. - An agent move of a task that has children skips only the parent change and returns `reason: "has_children"`. - Add `issues.origin_issue_id` (nullable, `on delete set null`, indexed) in migration `0434`. Only the server sets it. The `createdFrom` detail field, the `createdFromIssueId` filter and task-creation provenance read it first. - Chat prompts now let a chat agent create one task, a task with children, or separate tasks. The conversation is never a parent. - The runner `create_task` tool accepts an optional `parentTaskId`. A chat agent can create a top-level task and then put subtasks under it. The run must be able to read and change the parent. A conversation is never a parent. The depth limit still applies. Project tools map the field to `parentId` through the issue route. The protocol manifest, semantic contracts and eval fixture are regenerated. - Watchdog runs that have an active watchdog keep their requested parent on all create paths, because their access is limited to the watched subtree. - Fix a bug that the test drive found: the chat (MCP) `create_task` ignored the requested `status`, so a `backlog` task became `todo` and started to run. It now uses the same rule as the native tool. - Update `skills/paperclip`, `doc/SPEC-implementation.md`, and add the plan `doc/plans/2026-11-05-two-level-task-hierarchy.md`. ## Verification - `cd server && npx vitest run src/__tests__/agent-task-hierarchy-depth.test.ts src/__tests__/agent-task-hierarchy-depth-routes.test.ts`: 19 new tests for service and route behavior, all pass. - Related suites (64 files: issues service, thread interactions, privacy, watchdogs, provenance, runner tool authority, decisions, routines, project tools): 1208 tests pass. Two tests changed because of the intended behavior and pass now. Two `email-channels.integration` tests fail locally because the test cannot find a bundled skill file from this worktree path. This change does not touch that code. - `packages/db`: migration safety, snapshot drift and journal tests pass. - Typecheck passes for `server`, `packages/shared`, `packages/db`, `cli` and `ui` (local `ui` errors come only from generated Storybook catalog files that are absent). - Local test drive on an isolated instance with real Claude agents: 29 of 29 use cases pass, including an agent on a child task (follow-ups placed beside it, with the notice) and a chat that creates a top-level task with three subtasks. - I did not run the full `pnpm test:run` or `pnpm build` locally. CI runs them. ## Risks - Behavior change for agents: some follow-ups that were grandchildren are now siblings. The parent of the requester is woken when all its children finish, which now includes these tasks. - The runner `create_task` input contract adds one optional field, so the protocol manifest and the pack contract change. The tool output does not change: it shows the actual `parentId`, and the activity log records `parentAdjusted`. - Migration `0434` adds a nullable column, a foreign key and an index. It does not backfill rows. - `IssueCreationSource.run` is now nullable. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use (shell, file edits, local 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 - [ ] 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> |
||
|
|
d8b0eae687 |
test(evals): pin completed timeline command account for full-ten measurement (#19395)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The chat catalog needs fixed source and explicit conditions for each measurement. > - The latest timeline run delivered a complete account within its original deadline. > - Its assertion missed ordinary exitCode notation and a command with an omitted subject inside a named worker bullet. > - The reviewed source corrects those bounded textual checks while retaining independent history and command proof. > - This pull request pins that immutable source for one full-ten measurement. > - Original failed grades and unresolved Company timing remain visible. ## Linked Issues or Issue Description Refs: #19392, #19086, #19388. This follows the prior source pin. The latest timeline response correctly described requirements, a real failed command, saved pause, human reassignment, repair and successful verification. Its original grade still failed one narrative assertion. This pin carries the reviewed correction without merging the full catalog or its production overlays. ## What Changed - Pin source `936c3bbe0e83ca15616254f23d9023b2065fc5fb`, tree `e5c64d67935497164b098e31e995f87ba7875d06`. - Advance only the timeline oracle from v11 to v12 in the candidate snapshot and manifest. - Retain all 244 current queries, contexts, expected outcomes, model, credentials, clocks and human/native accounting. - Record one planned full-ten bounded measurement at native cap 2 and zero automatic retries. - Change only the manifest, its target assertion and the exact candidate descriptor snapshot. ## Verification - I verified the immutable source branch readback before I published this pin. - The new narrative control fails on the parent. The fixed source passes 300 timeline, native-proof and declaration controls, including all 242 lazy declarations with zero fixture callbacks. - The response from result 5094 is accepted by the local diagnostic. The original grade is retained as FAILED; this is not a new native PASS. - Fresh full runner E2E types and the SDK guard over 506 inputs pass in the qualified source graph. An earlier isolated worktree type attempt had dependency-alias errors; the clean source qualification supersedes that setup gap. - Controller: `node --test tests/runner-e2e/chat-full-catalog-testops-manifest.test.mjs tests/runner-e2e/chat-full-catalog-testops-adapter.test.mjs tests/runner-e2e/chat-full-catalog-testops-reconcile.test.mjs` — 40 controls pass. - The snapshot equals the committed source descriptor bytes. All 244 rows compare equal to the prior pin except the timeline oracle version. - Source descriptor SHA256: `19185aa5298c93d159c3d8547661fd6ef22185e74be1c18b8497bae2ef8051c7`. - Controller manifest SHA256: `822494b170139a8204ff36b33891e2f4960ac0aba6800e9856f2d25dd24de7c6`. - Launcher, protected evidence wire and the accepted 506 SDK inputs remain unchanged. The source SDK registry stays `efa70621e9fb42aa185f7ee616a1a726aa3eccb367e67e28817cedf26ef2ade8`. - Exact pin head `31f47fcdfb600e6b15b35ed50c2a447d03681f06` has 56 successful checks and three configured skips. Both protected checks pass. Greptile is 5/5 for this head with zero open threads. Paperclip Review completed neutral at 5/5. GitHub reports CLEAN and MERGEABLE. These checks do not qualify the unrun native experiment. ## Risks - Original timeline result 5094 remains FAILED. All three native runs succeeded and the account was complete in 248,876 ms, but the original narrative check failed. Local calibration does not rewrite that grade. - Original Company result 5093 ended without first-CEO completion. Its model/runtime timing cause remains unresolved. This source makes no Company runtime or latency fix claim. - The full-ten measurement is unrun. It is a bounded experiment, not proof of deterministic timing, reliability or an all-ten pass. - Company retains two base human inputs plus at most one independently verified UI approval, minimum two and maximum three native runs, and 480,000 ms. Timeline retains three native runs, one human input and 720,000 ms. No condition is waived. - Secret journeys still require independent semantic safety review. Metadata shape alone does not establish that a proposed name is non-sensitive. - The interaction-accept and API-search overlays remain separate, reviewed, unmerged core PRs. This pin does not merge them or the full catalog. The 196/244 admission gate stays fixed. ## Model Used OpenAI Codex, GPT-6.1 Sol (`gpt-6.1-sol`), with reasoning, repository tools and code execution. The host does not expose an exact context-window size. No model or paid provider was called for local controls. ## 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 |
||
|
|
936c3bbe0e | test(evals): bind timeline command notation to worker bullets | ||
|
|
f8cb4957dc |
fix(ui): default Tasks to All tasks with a neutral sort header (#19393)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The Tasks page helps people inspect work across their company. > - A new viewer currently starts in Mine and can miss other company tasks. > - The task table also shows an active sort header before the viewer selects a sort. > - This pull request starts new viewers in All tasks and keeps the default sort header neutral. > - People see company work in newest-update order and keep their saved view choices. ## Linked Issues or Issue Description **What existing behavior does this improve?** The initial task view and the task table's default sort state. **Subsystem affected** ui/ — React board UI. **Current behavior** Tasks opens Mine when the viewer has no saved view. The table highlights Last updated even when the viewer has not selected a column sort. **Proposed behavior** Tasks opens All tasks when no saved view exists. The table keeps newest-updated tasks first without an active header or arrow. A column selection or an explicit sort URL activates the selected header. **Reason and benefit** New viewers can inspect company work immediately. The header distinguishes default ordering from an explicit sort choice. **Breaking changes** The first-visit view changes from Mine to All tasks. Saved views and explicit view URLs keep priority. Existing Inbox and Needs attention preferences retain their meaning. The change does not alter task access or database contracts. **Additional context** Refs #19363. This change builds on the grouped task menu from that merged pull request. It uses the existing menu and design tokens. ## What Changed - Default the Coordinator and combined task surfaces to All tasks. - Preserve saved personal views and migrate previous picker choices. - Keep implicit Last updated descending order separate from active header state. - Test default ordering, explicit sorting, reset behavior, view selection, and preference migration. - Update the product and implementation docs. ## Verification - Passed 118 focused tests across task routing, table sorting, views, and collection preferences. - Passed `pnpm check:token-gates`, `pnpm -r typecheck`, and `pnpm build`. - Full `pnpm test:run` is running. Its result will be updated here. - Greptile passed the current head with 5/5 and no findings or open review threads. - Browser check: All tasks uses the grouped My work and Organization menu. Last updated has no active highlight by default. Selecting the header adds the sort URL and active direction. Removing the explicit sort restores the neutral header. - Manual check: Open Tasks with no saved task view. Confirm All tasks opens. Select Mine, leave Tasks, and return. Confirm Mine remains selected. ## Risks - The new default shows more accessible company tasks on the first visit. Saved view choices remain intact. - Task tables share the sort-state change. Explicit sort links and column clicks retain active styling. Default row order stays newest-updated first. - No schema, API, permission, or execution changes. ## Model Used OpenAI Codex, GPT-6 family. This session does not expose the exact model ID or context-window size. Used reasoning, code editing, terminal commands, and browser verification. ## 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 - [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: Scott Tong <scott@scottsmbpm5max.lan> Co-authored-by: Paperclip <noreply@paperclip.ing>canary/v2026.1106.0-canary.2 |
||
|
|
5ab7b78f2b | Runner: retain the provider across hosted park and reclaim (#19140) | ||
|
|
7c04adb2d6 |
test(evals): pin company environment and API search repairs (#19392)
## Thinking Path > - Paperclip manages AI agents and their governed actions. > - The TestOps controller runs an immutable product-eval source. > - The last company-secret attempt rejected a safe metadata question before private entry. > - A governed approval also had a redundant server notification and wake path. > - Natural-language API search could hide the resource needed for a timeline answer. > - This pull request pins the reviewed source and its exact descriptor snapshot. > - New native evidence must still prove the affected journeys. ## Linked Issues or Issue Description Refs #19381, #19093, #19387, #19388. This is the next controller pin for those reviewed changes. The core pull requests remain unmerged. ## What Changed - Pin source `2c99c248e839f41b520fc2d7e3702169ac6cb0a2` and its exact descriptor snapshot. - Advance company oracle v12 to v13. Admit bounded environment-name questions without a required authored pattern or exact prompt template. Preserve independent human-answer validation. - Include the reviewed interaction-accept notification and API-search ranking overlays. Record their route and catalog dependencies in the company and timeline provenance. - Retain the existing cap of two, zero retries, DeepSeek1017 model, and all 244 current YAML definitions. Company keeps 480000ms, native2..3, base human2 plus one independently proved approval. Timeline keeps720000ms, native3/human1. ## Verification - Source:396 controls in9 suites,2046 API catalog controls,145 route controls, full runner TypeScript check, and unchanged SDK506 input guard passed. Fresh-master API PR has2059 controls; its other baseline tests are absent from this catalog source. - Controller:133 contract, API, adapter, reconcile, streaming and manifest controls passed. Existing report-lock preparation returned `already-reviewed`. - All current YAML definitions and launcher bytes remain unchanged from the prior source. No model or native eval ran for this pin. - The original company5059 and timeline5060 remain failed. Company private entry, approval and final binding remain unproved on this successor. - Exact-head normal checks are terminal:56 success,3 intentional skips, and one neutral Paperclip review. Greptile and Paperclip both report5/5 with no findings; review threads are empty. The next TestOps/AWS run follows the protected controller merge. ## Risks The new source is not a native feature pass. Its effect on model completion is unproved. The original480000ms company budget may still be too short for three native turns. No budget change or count waiver is included. The full catalog remains outside master and its existing admission gate remains unchanged. The controller reuses byte-identical SDK506 evidence; no compiler or registry renewal is claimed. Actual post-merge helper imports still need qualification against the merged controller lock. The CI virtual merge includes a newer upstream lock than the local frozen dependency proof. ## Model Used OpenAI `gpt-6.1-sol` through Codex, with repository tools and code execution. The host did not report its context window. ## 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 existing issues or described the issue in-PR - [x] I have not referenced internal or instance-local Paperclip issues or links - [x] My branch name describes the change and contains no internal ticket id - [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 risks above - [x] All Paperclip CI gates are green - [x] Greptile is5/5 with no open findings - [x] I will address reviewer comments before requesting merge Co-authored-by: Paperclip <paperclip@paperclip.ing> |
||
|
|
4033f7f17f |
fix(recovery): let a task Retry run keep its bounded transient retry (#19384)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A user can click Retry on a blocked task. Paperclip then admits one new run that continues in the saved workspace > - That run can fail before the provider starts, for example when the download of the saved workspace snapshot gets a transient `ECONNRESET` > - The scheduler classifies this failure as transient and tries to schedule the normal bounded retry > - The automatic-retry admission accepts only continuations that a user comment started. It refuses a Retry continuation (`commentId: null`, `taskRetryRequestId` set) with `continuation_user_authorization_missing` > - So the task does not retry. Paperclip escalates the restore failure, and the task becomes blocked again after one network reset > - This pull request lets a plain task Retry renew its authority for the bounded transient retry, with its own receipt for each new run > - The benefit is that a transient setup failure after Retry uses the normal retry budget and does not block the task ## Linked Issues or Issue Description **What happened?** A run started by the task Retry button failed during setup: `remote_workspace_restore_failed: step=snapshot_download; storage=transport_closed code=ECONNRESET`. The run event said "The automatic retry could not revalidate the original user continuation." (retry reason `transient_failure`, attempt 1 of 2). No retry was scheduled, and the task became blocked. **Expected behavior** A transient setup failure of a Retry-admitted run gets the same bounded automatic retry as a run that a user comment started. **Root cause** `admitExplicitContinuationRetry` in `server/src/services/explicit-native-continuation.ts` required `explicitUserContinuation.commentId` to be a GUID. A Retry continuation has `commentId: null`. Also, dispatch in `server/src/services/execution-continuation.ts` bound a Retry receipt to exactly the run that it admitted, so a successor run could not prove its authority. Refs #19207 (it named remote restore failures and left a board recovery action) Refs #19340 (it retries quarantined tasks in their saved workspace) Refs #19326 (same bounded retry path for an out-of-date Runner) ## What Changed - `admitExplicitContinuationRetry` now accepts two kinds of authority. The comment path is unchanged and moved into `readCommentRetryAuthority`. The new `readTaskRetryRetryAuthority` accepts a plain task Retry only. The Retry request must be coalesced, from a user, with the same generation and actor, strategy `continue`, and no chat, queued-message, canonical-intent or fresh-workspace readiness. Interrupt and failed-run retries stay one-run. - The successor run gets its own `explicit_user_continuation_retry` receipt with the same `taskRetryRequestId` and actor and `automaticRetry.sourceRunId` set to the parent. The Retry request stays bound to the run that it admitted. - Dispatch (`buildExecutionContinuation`) accepts a Retry successor only from its own receipt, its exact `retryOfRunId` parent, the current `executionRunId` and the unchanged Retry intent. The direct path for the admitted run is unchanged. - Tests in `server/src/services/task-retry.test.ts`: one regression for the full retry chain, and eight refusal cases. ## Verification - Red: before the fix, the new test failed with `{ outcome: "not_scheduled", errorCode: "continuation_user_authorization_missing" }`. This is the same refusal as the incident. - Green: `pnpm --filter @paperclipai/server exec vitest run src/services/task-retry.test.ts` gives 58/58. - The regression test proves these points: the first retry is scheduled with `{ previousRunId: parent, commentId: null, taskRetryRequestId }`; the Retry request stays bound to the admitted run; dispatch accepts the successor; a repeated schedule call adopts the same successor without a second receipt; a second transient failure retries from its immediate parent; the budget then stops with `retry_exhausted`; dispatch refuses the successor when its own receipt is removed. - The refusal cases prove that no successor and no receipt are created after: wrong actor, unadmitted request, wrong generation, request bound to another run, chat Retry, fresh-workspace readiness, reassignment, superseding work. - Nearby suites pass (601/601): `execution-continuation`, `legacy-explicit-continuation-recovery`, `explicit-native-continuation`, `execution-continuation-provenance`, `quarantined-task-retry`, `issue-queued-comments-routes`, `task-retry`. - I did not run the layered server typecheck locally because of a disk limit in the sandbox. CI must confirm it. ## Risks - Behavior change: a plain task Retry run that fails transiently now retries automatically within the existing bounded budget (2 attempts). The scheduler still decides which failures are transient. The same rule already applies to runs that a user comment started. - Chat, queued-message and fresh-workspace Retries, failed-run retries and Interrupt keep their one-run contracts. - No schema or migration change. - Documentation gap: `doc/execution-semantics.md` (line 1210) says "One-run Retry and Interrupt intents retain their separate admission contracts; they are not renewable user-message authority." That sentence must change to describe this rule. The author's publication path cannot carry the full 320 KB file, so that one-sentence edit is still pending. The proposed text: "A plain task Retry that continues in the saved workspace renews the same way: the successor's receipt names the same Retry request and actor, while the Retry request itself stays bound to the run it admitted. Dispatch accepts that successor only from its own receipt, its exact retry parent, and the unchanged admitted Retry request, so a transient failure before or during that run, such as a reset saved-workspace download, uses the normal bounded retry budget instead of blocking the task. Chat, queued-message and fresh-workspace Retries, failed-run retries, and Interrupt intents retain their separate one-run admission contracts; they are not renewable authority." > 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 as a Paperclip agent, with tool use and code execution (shell, Vitest on embedded PostgreSQL). ## 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 - [ ] I have updated relevant documentation to reflect my changes (the `doc/execution-semantics.md` sentence above is pending) - [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> |
||
|
|
96516a8622 | Keep ACPX output while Core reconnects (#19365) | ||
|
|
2c99c248e8 |
test(evals): declare API discovery ranking provenance
Co-Authored-By: Paperclip <paperclip@paperclip.ing> |
||
|
|
675f7283dd |
chore(lockfile): refresh pnpm-lock.yaml (#19389)
## 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. - 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> |
||
|
|
e0cce66b7b | fix(api-search): ignore function words when ranking operations | ||
|
|
c9c4949c86 |
build(deps): bump zod from 4.4.3 to 4.6.6 (#17364)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The shared validators use zod to check API input, and the Runner contracts check some of the same values again > - zod below 4.6.6 has a high-severity memory issue (GHSA-mrhx-c7xc-m3w5), so this bump is necessary > - zod 4.6 also changes string `min`, `max`, and `length`: they now count Unicode code points, not UTF-16 units > - The Runner question parser and the goal readers still use JavaScript string length, so emoji text can pass zod and then fail later > - This pull request bumps zod and keeps the string-length bound on those schemas > - The benefit is that we get the security fix, and these inputs still fail at validation ## Linked Issues or Issue Description Refs GHSA-mrhx-c7xc-m3w5. **What happened?** With zod 4.6, three CI tests failed on this bump. Two showed an emoji body that is over the agent commentary ceiling and that zod now accepts. The third is the scheduler timeout described under Verification. **Expected behavior** A dependency bump does not widen an API limit. Text over the limit gets a validation error. **Impact** Emoji text between the two bounds passed validation and failed later: - A question set is created, and then each answer gets a 422. The question stays pending and nobody can answer it. A schema and parser run confirmed the mismatch. This was not run over HTTP. - The Muse route returns a 500 because `parsePaperclipQuestionSet` throws an error that is not an `HttpError`. This comes from reading the code. It was not run. - A Runner goal normalizes to null and replaces the stored goal, or it is marked blocked. This comes from reading the code. It was not run. ## What Changed - Bump zod to `^4.6.6`. The lockfile resolves 4.6.7. (Dependabot) - Add `maxStringLength` to `packages/shared/src/validators/text.ts`. It checks zod `max` and JavaScript string length. - Use `maxStringLength` for the question-set text fields, the Runner goal objective, and the agent commentary body. - Add regression tests for the question-set bound and the goal objective bound. - Dependabot recreated the branch on current `master` (`@dependabot recreate`) to resolve `pnpm-lock.yaml` conflicts. This happened twice. The lockfile is Dependabot's. Each time, the two fix commits were applied again without changes. ## Verification - Compared zod 4.4.3, 4.6.5, 4.6.6, and 4.6.7 directly. The change to `max` is in 4.6.5 or earlier, and 4.4.3 does not have it. - Passed locally: `packages/shared` tests `agent-commentary`, `validators/issue`, and `runner-goal`, plus `tsc --noEmit` in `packages/shared`. - Passed locally on the first fix commit: server `agent-commentary.integration`, `agent-commentary-helper`, and `openapi-routes`. The later commit changes these schemas again. CI passed these tests at `b5e9dc7352`. - Local testing was focused. GitHub CI is the full regression check. CI passed at `b5e9dc7352`, including `ci / verify` and `ci / e2e`. - Greptile reviewed `b5e9dc7352`. The score is 5/5, with no inline comments. - After the recreate, the fix commits applied cleanly as `bad62c3ba5` and `3294552e65` on Dependabot's `46789d1487`. Their diff is the same as the diff Greptile reviewed. The lockfile still resolves zod 4.6.7. The focused `packages/shared` tests (`validators/issue`, `runner-goal`) and `tsc --noEmit` in `packages/shared` passed again locally. A second `pnpm-lock.yaml` conflict came before CI finished at `3294552e65`. - Second recreate: Dependabot ignored the first `@dependabot recreate` (no reaction and no update job after 20 minutes). It acted on the second command and pushed `83c328c15c`. The fix commits applied cleanly as `dd3e87d93e` and `95b450048f`, with the same diff. The lockfile still resolves zod 4.6.7. The same focused tests and `tsc --noEmit` passed again locally. Greptile first scored `95b450048f` 4/5 with one P2: the description did not explain the source changes. Greptile read Dependabot's replacement description, which was live during that review. The description was restored and the thread resolved. The new review scored `95b450048f` 5/5 with no findings. CI failed at `95b450048f` in two shards. Neither failure comes from the zod change: - `server (9/12)`: `server/scripts/reporting-lock-policy.test.mjs` fails with "Reporting lock changed; review the dependency correction before install". #19283 added this test today. It requires the committed `pnpm-lock.yaml` hash to match `tests/runner-e2e/report-lock.json`. Every pull request that changes the lockfile fails it until the hashes are renewed, for example #17172 and #17911. - `workspaces-a (1/2)`: `cli/src/__tests__/company-import-export-e2e.test.ts` timed out waiting for its child server. The same test failed the same way on two unrelated branches in the same hour (runs 42115721271 and 42109846042). A rerun of this shard at `95b450048f` passed. The rerun of `server (9/12)` failed again on the reporting-lock test. - #19356 fixed the reporting-lock gate for Dependabot lockfiles and merged. A re-run reuses the old merge commit, so the empty commit `482d76686e` starts a run against current `master`. `pnpm-lock.yaml` and the reporting-lock proof on `master` did not change since this branch's base. - CI at `482d76686e`: `ci / policy` passed, and `server (9/12)` skipped the reporting-lock test as #19356 intends. `e2e shard (6/8)` failed once in `cloud-auth.spec.ts` ("apiResponse.json: Response has been disposed", a Playwright race in a route handler). The same test passed at `95b450048f` with the same code. A rerun passed. All checks pass, including `ci / verify` and `ci / e2e`. - Earlier failure: `issue-monitor-scheduler.test.ts` ("lets the board check a scheduled monitor on an in_progress task") timed out in CI at `d60b5f56d8`. It passed 5 of 5 local runs with the first fix. The cause is unknown. The test waits 3 seconds for heartbeat runs to settle, so CI load is possible, but this is not proven. The test passed in the CI run at `b5e9dc7352`. This shows recovery. It does not explain the earlier timeout. ## Risks - All other zod string bounds now count code points. Emoji text can be up to 2N UTF-16 units and 4N UTF-8 bytes (it was N units and at most 3N bytes). This pull request does not change those bounds. - An audit of those bounds found no other concrete failure. It checked index limits, byte limits, `.length` guards, provider limits, and security `min` checks. It used code search and sampling, so it is not exhaustive. - Not verified: Slack `topic` and `purpose` use `.max(250)`, and we did not confirm how Slack counts that limit. - `.min(N)` is now stricter for emoji text. A single emoji no longer passes `.min(2)`. - `@dependabot rebase` or `@dependabot recreate` on this branch will remove the manual commits. This happened twice to resolve lockfile conflicts, and the commits were applied again each time. ## Model Used - Anthropic Claude Opus 5.5 (`claude-opus-5-5`), 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 (focused tests only; see Verification) - [x] I have added or updated tests where applicable - [ ] I have updated relevant documentation to reflect my changes (no documentation change needed) - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green (at `482d76686e`) - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups (5/5 at `95b450048f`) - [x] I will address all Greptile and reviewer comments before requesting merge --- Bumps [zod](https://github.com/colinhacks/zod) from 4.4.3 to 4.6.6. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/colinhacks/zod/releases">zod's releases</a>.</em></p> <blockquote> <h2>v4.6.6</h2> <blockquote> <p>Six malicious commits pushed to <code>main</code> on September 21 with a compromised git credential were reverted the same day in <a href="https://redirect.github.com/colinhacks/zod/issues/6620">#6620</a> and have since been removed from the history of <code>main</code>. None were ever published to npm. The npm provenance for 4.6.6 names 284243fb, the pre-rewrite copy of 9c219833 with identical contents.</p> </blockquote> <h2>Commits:</h2> <ul> <li>0cd3af5e9f50ad11bbcd4407a46a21b8848ba574 docs: load the en locale directly in Zod Mini</li> <li>9d5e60f162997b764b40d45aa02481888db50dea docs: import locales from zod/locales, not z.locales</li> <li>34a626f307871ecbb0123947559328dc52ed5e3e docs: tighten the z.locales tree-shaking note</li> <li>74304ce5b5796b5edb9a88ceb8e170b6ea3e82df release: stage npm versions instead of publishing them (<a href="https://redirect.github.com/colinhacks/zod/issues/6622">#6622</a>)</li> <li>b2ef983e0711ff6517d6020b021071e2e5653efa docs: add Code for Japan to bronze sponsors</li> <li>8fb2faf0584828a6a027d8279db36f6f52784462 docs: crop the Code for Japan logo and list it first in bronze</li> <li>bacad518c41610d654658172d325874bf0ee6290 docs: square the Code for Japan logo to match the other bronze tiles</li> <li>c42596bd1c87e941d7241d10e012c5c196adcc10 release: hold the run on the release-approval environment until the staged versions are approved (<a href="https://redirect.github.com/colinhacks/zod/issues/6638">#6638</a>)</li> <li>a2ba4a67986a3e6b2b131be30084a2f460d9ea67 fix(types): declare record output and input as members, not base type arguments (<a href="https://redirect.github.com/colinhacks/zod/issues/6645">#6645</a>)</li> <li>a0ebf970f2d752e5f58527cd2942ff48be4f46bd fix(v4): check array, set and map size bounds before parsing elements (<a href="https://redirect.github.com/colinhacks/zod/issues/6652">#6652</a>)</li> <li>529f78dd9431b07f49647fee3aa0819b3f8b66b7 4.6.6</li> <li>9c219833c3a3764fa1f1ea9c5ecec8254a63c0b1 ci: stage at verbose so a failed OIDC token exchange says why</li> </ul> <h2>v4.6.5</h2> <h2>Commits:</h2> <ul> <li>d2b135cfb7a3582b9eb515756b9166bcb9521f4a docs: add the 4.6.x patch highlights to the 4.6 post</li> <li>f1448f7cee00df9fe1e9ad84a000aa1828cc8bc1 docs: fold the 4.6.x patch highlights into the 4.6 post's own sections</li> <li>de65a5cb39ed22a507fac935788f718fa88d104f docs: lead the properties section with the check and add a Zod Mini tab (<a href="https://redirect.github.com/colinhacks/zod/issues/6598">#6598</a>)</li> <li>56222cd1532c07bcb91b67df529cab4c0a215330 feat(instanceof): key the .properties() shape off the instance type (<a href="https://redirect.github.com/colinhacks/zod/issues/6600">#6600</a>)</li> <li>ca0229a404818290e6cdcfefcd7eb2d04bcbb543 Revert "feat: add z.currencyCode() over a vendored ISO 4217 list, refreshed weekly by CI (<a href="https://redirect.github.com/colinhacks/zod/issues/6595">#6595</a>)"</li> <li>cc4cd4ee9c52fcaa10964e48cc144541e41a5ed9 Revert "Revert "feat: add z.currencyCode() over a vendored ISO 4217 list, refreshed weekly by CI (<a href="https://redirect.github.com/colinhacks/zod/issues/6595">#6595</a>)""</li> <li>0f3f5ee3ca56c7574bf849e54f79e9a6e02562ee 4.6.5</li> <li>59bbc03e10c636b9eb3c393dfeb552819774ec21 chore: re-pin the integration peers to the workspace zod after the 4.6.5 bump</li> </ul> <h2>v4.6.4</h2> <p>A patch on top of <a href="https://github.com/colinhacks/zod/releases/tag/v4.6.3">4.6.3</a>.</p> <ul> <li><a href="https://github.com/colinhacks/zod/commit/d6bc1e30"><code>d6bc1e30</code></a> feat: add <code>z.currencyCode()</code> over a vendored ISO 4217 list, refreshed weekly by CI (<a href="https://redirect.github.com/colinhacks/zod/pull/6595">#6595</a>)</li> <li><a href="https://github.com/colinhacks/zod/commit/ad32d751"><code>ad32d751</code></a> perf: <code>z.url()</code> rejects an invalid URL with <code>URL.canParse()</code> instead of a throwing constructor, about 50x faster; fewer allocations on the validation path (<a href="https://redirect.github.com/colinhacks/zod/pull/6588">#6588</a>)</li> <li><a href="https://github.com/colinhacks/zod/commit/2bb08717"><code>2bb08717</code></a> chore: re-pin the integration peers to the workspace zod after the 4.6.4 bump</li> <li><a href="https://github.com/colinhacks/zod/commit/f6e1701a"><code>f6e1701a</code></a> chore(deps): bump next to 15.5.25 and vite to 7.3.6 (<a href="https://redirect.github.com/colinhacks/zod/pull/6153">#6153</a>)</li> </ul> <h2>v4.6.3</h2> <p>A patch on top of <a href="https://github.com/colinhacks/zod/releases/tag/v4.6.2">4.6.2</a>.</p> <ul> <li><a href="https://github.com/colinhacks/zod/commit/413cce9a"><code>413cce9a</code></a> fix(v4): make z.properties() a check again (<a href="https://redirect.github.com/colinhacks/zod/pull/6594">#6594</a>) — removes the standalone <code>z.properties()</code> schema from 4.6.0; <code>z.instanceof().properties()</code> and <code>.check(...z.properties())</code> are unchanged</li> <li><a href="https://github.com/colinhacks/zod/commit/75d63ee1"><code>75d63ee1</code></a> docs: show only the <code>.properties()</code> method form in the 4.6 post</li> <li><a href="https://github.com/colinhacks/zod/commit/46da9572"><code>46da9572</code></a> docs: match the error-message examples to what the parsers emit</li> </ul> <h2>v4.6.2</h2> <p>A patch on top of <a href="https://github.com/colinhacks/zod/releases/tag/v4.6.1">4.6.1</a>.</p> <ul> <li><a href="https://github.com/colinhacks/zod/commit/9446b5cc"><code>9446b5cc</code></a> fix: preserve undefined prefault outputs and object keys (<a href="https://redirect.github.com/colinhacks/zod/pull/6587">#6587</a>) — closes <a href="https://redirect.github.com/colinhacks/zod/issues/6585">#6585</a></li> <li><a href="https://github.com/colinhacks/zod/commit/0c483c58"><code>0c483c58</code></a> docs: the <a href="https://zod.dev/blog/zod-4-6">Zod 4.6 announcement post</a> (<a href="https://redirect.github.com/colinhacks/zod/pull/6546">#6546</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/colinhacks/zod/commit/9c219833c3a3764fa1f1ea9c5ecec8254a63c0b1"><code>9c21983</code></a> ci: stage at verbose so a failed OIDC token exchange says why</li> <li><a href="https://github.com/colinhacks/zod/commit/529f78dd9431b07f49647fee3aa0819b3f8b66b7"><code>529f78d</code></a> 4.6.6</li> <li><a href="https://github.com/colinhacks/zod/commit/a0ebf970f2d752e5f58527cd2942ff48be4f46bd"><code>a0ebf97</code></a> fix(v4): check array, set and map size bounds before parsing elements (<a href="https://redirect.github.com/colinhacks/zod/issues/6652">#6652</a>)</li> <li><a href="https://github.com/colinhacks/zod/commit/a2ba4a67986a3e6b2b131be30084a2f460d9ea67"><code>a2ba4a6</code></a> fix(types): declare record output and input as members, not base type argumen...</li> <li><a href="https://github.com/colinhacks/zod/commit/c42596bd1c87e941d7241d10e012c5c196adcc10"><code>c42596b</code></a> release: hold the run on the release-approval environment until the staged ve...</li> <li><a href="https://github.com/colinhacks/zod/commit/bacad518c41610d654658172d325874bf0ee6290"><code>bacad51</code></a> docs: square the Code for Japan logo to match the other bronze tiles</li> <li><a href="https://github.com/colinhacks/zod/commit/8fb2faf0584828a6a027d8279db36f6f52784462"><code>8fb2faf</code></a> docs: crop the Code for Japan logo and list it first in bronze</li> <li><a href="https://github.com/colinhacks/zod/commit/b2ef983e0711ff6517d6020b021071e2e5653efa"><code>b2ef983</code></a> docs: add Code for Japan to bronze sponsors</li> <li><a href="https://github.com/colinhacks/zod/commit/74304ce5b5796b5edb9a88ceb8e170b6ea3e82df"><code>74304ce</code></a> release: stage npm versions instead of publishing them (<a href="https://redirect.github.com/colinhacks/zod/issues/6622">#6622</a>)</li> <li><a href="https://github.com/colinhacks/zod/commit/34a626f307871ecbb0123947559328dc52ed5e3e"><code>34a626f</code></a> docs: tighten the z.locales tree-shaking note</li> <li>Additional commits viewable in <a href="https://github.com/colinhacks/zod/compare/v4.4.3...v4.6.6">compare view</a></li> </ul> </details> <br /> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nicky Leach <nicky@paperclip.ing> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8f79a32898 |
fix(connections): keep agent access consistent through setup and permission changes (#19352)
Task-hosted tool setup kept requester-only defaults, and audience edits could leave runtime installations restricted after permission bindings were widened. Large provider catalogs could also fail before creating an access card. Use the normal agent audience defaults for new tool setup, preserve explicit selections through verified OAuth and draft resumption, and synchronize permission bindings and runtime installations atomically. Enforce agent-edit authority and preserve existing access during additive account reuse. Validation: 915 focused regression tests, typechecks, and builds passed. All 54 CI checks passed and Greptile Apex scored the reviewed head 5/5 with no unresolved threads. Broad local test failures, recoveries, and incomplete heartbeat coverage are documented in the PR. Co-Authored-By: Paperclip <noreply@paperclip.ing>canary/v2026.1106.0-canary.1 |
||
|
|
36643d9378 |
fix(native): retire finished owners whose continued turn rotated authority (#19373)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The native runner runs per-turn agent sessions in managed sandboxes.
A controller keeps a durable session root for each session.
> - When a per-turn owner finishes without a saved runner checkpoint,
the next turn retires that owner. Retirement first checks that the
session root holds only known entries.
> - The runner transport writes two entries that the check does not
know: `authority-epochs/` and `runtime-context.json`. A continued turn
writes `authority-epochs/` when it rotates authority from the turn
before it.
> - So the third turn of an ephemeral-lease task cannot retire the
second turn's owner. The run fails with
`native_continuation_requires_reconciliation` and the task waits for a
user message.
> - This pull request classifies every session-root entry in one
contract module, and adds a test that scans the writers so the list
cannot drift again.
> - The benefit is that follow-up turns continue without manual help,
and any future refusal names the unexpected entry.
## Linked Issues or Issue Description
Refs #18846, Refs #19061, Refs #19355
**What happened?**
On staging, the third turn of every task for one agent (gpt-5.6-sol,
native runner, ephemeral sandbox lease) failed. The error was
`runner_state_identity_mismatch:
prior_owner_terminal_state_indeterminate;
retirement_refused=unexpected_root_entry`. The first two turns
succeeded. The task then went to `blocked` with "Saved work recovered.
Send a message to continue."
**Expected behavior**
The third turn retires the finished second-turn owner and continues the
conversation.
**Steps to reproduce**
1. Assign a task to a native agent that uses an ephemeral sandbox lease.
2. Let the first turn finish. Reopen the task with a comment so a second
turn runs and finishes.
3. Reopen the task again. The third turn fails with the error above.
**Paperclip version or commit**
Staging near 9a79384958; reproduced locally on master
|
||
|
|
0e281cc0ba |
test(evals): declare company approval route provenance
Co-Authored-By: Paperclip <paperclip@paperclip.ing> |