mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 16:02:36 +01:00
0f2c01b63ac8ee132661bcabfafcc23e4372cfcf
6550
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0f2c01b63a |
fix(server): retry one Cloud portfolio connection reset (#19033)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - On Cloud instances, `GET /api/cloud/stacks` gets the user's stack portfolio from the Cloud control plane. > - The route sends one `fetch` to `/v1/tenant/portfolio`. It has a 10 s deadline. > - The Cloud edge can reset a pooled keep-alive connection when the request is written. The request then fails in a few milliseconds and the user gets a 502. > - The diagnostics from #15340 now show this failure mode on staging. > - This pull request retries the idempotent GET one time for a pre-response connection reset, inside the same deadline. > - The benefit is that a transient connection reset no longer shows an error to the user. Real Cloud failures and deadlines are still reported. ## Linked Issues or Issue Description Refs #15340 (bounded portfolio diagnostics). Refs #10850 (portfolio proxy). I searched the open and closed PRs for "portfolio". No other PR fixes this failure. **What happened?** On the hosted staging pool stack, one `GET /api/cloud/stacks` returned 502. The diagnostics were `phase: fetch`, `networkCode: ECONNRESET`, `upstreamStatus: null`, `elapsedMs: 19`. The Core process had started about 7 minutes before. CPU and memory were normal. The Railway HTTP logs for the Cloud harness show no failed or missing portfolio request at that time. All portfolio requests in a 12-minute window returned 200 in 15–91 ms. The next Core portfolio fetch, 0.4 s later, reached Cloud and returned 200. Thus the failed request did not get to the Cloud application. The connection was reset between Core and the Cloud edge. This failure is different from the older stalls on the same stack. Those failures had `DEADLINE_EXCEEDED` after more than 60 s, during whole-process freezes. This change does not retry those failures. **Steps to reproduce** 1. Start an HTTP server that resets (or closes) the socket for the first request and returns JSON for the second request. 2. Set `PAPERCLIP_CLOUD_API_ORIGIN` to that server and call `GET /api/cloud/stacks` with the native `fetch`. 3. Before this change, the route returns 502 with `networkCode: ECONNRESET` (or `UND_ERR_SOCKET`). Native `fetch` does not retry. **Expected behavior** The route retries the idempotent GET one time when the connection is reset before a response. The retry uses the same 10 s signal. Other failures stay unchanged. ## What Changed - `server/src/routes/cloud.ts`: If the fetch phase fails with `ECONNRESET` or `UND_ERR_SOCKET`, the route sends the same GET one more time. The retry uses the same `AbortSignal.timeout(10_000)`. The total time stays at or below 10 s. - The route classifies the first error with the existing bounded `CloudPortfolioError` diagnostics. Thus a reset after the deadline stays `DEADLINE_EXCEEDED` and gets no retry. - The first reset gets a warning log with the same safe fields (`Paperclip Cloud portfolio connection reset; retrying once`). It does not send a Sentry event. Thus the logs still show how frequently this occurs. - A second reset, a deadline, HTTP error statuses, body failures, `ECONNREFUSED`, `ENOTFOUND`, connect timeouts and unknown errors behave as before: one report and no retry. Failures are still not cached. - `server/src/__tests__/cloud-routes.test.ts`: The old test "classifies a fetch reset without retrying" now expects one retry. New tests cover a second reset, the codes that get no retry, a reset after the deadline, and two real-socket cases. `doc/observability.md` describes the diagnostic context. Its fields and semantics do not change. Per `CONTRIBUTING.md`, the change adds no code comments; the retry rationale is in this description. ## Verification - Red before the fix: with the old route and the new tests, `pnpm exec vitest run server/src/__tests__/cloud-routes.test.ts` gave 5 failed, 19 passed. The failures were the two mocked retry cases, the second-reset case and the two real-socket cases. Thus native `fetch` on Node 25.9 does not retry these resets itself. - Green after the fix: `pnpm exec vitest run server/src/__tests__/cloud-routes.test.ts server/src/__tests__/cloud-portfolio-error.test.ts` gave 31 passed. - Real dependency: the real-socket tests use `node:http` and the native global `fetch`. `socket.resetAndDestroy()` gives `ECONNRESET`. `socket.destroy()` gives `UND_ERR_SOCKET`. Each test makes sure that the second request reaches the server with the same authorization, the route returns 200, and no Sentry event occurs. - The second commit only removes the four-line comment that Greptile flagged. It has no runtime effect; a syntax check passed locally and CI reruns the suite. - Not run locally: `server/src/__tests__/sentry.test.ts` fails to load in my sandbox on the unchanged base and with this change. The error is the same: `Failed to resolve entry for package "@paperclipai/plugin-sdk"`. The workspace packages were not built. The full `tsc -p server` was stopped by the 8 GB sandbox memory limit. A typecheck of only the changed files gave the same output on the base and with this change, apart from a 5-line offset. CI runs the full typecheck and test suite. - Remaining limit: the staging evidence shows that the request did not reach the Cloud application. It does not show if the reset socket was a reused keep-alive connection or a new connection. The retry is correct in both cases. ## Risks - Low risk. The retry applies only to an idempotent GET that has no side effects on Cloud. It occurs only for a fetch-phase reset before a response. - The worst case is one more portfolio request to Cloud for each reset. The deadline is the same as before. - If the Cloud edge resets all connections, the user sees the same 502 as before, after two fast attempts. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), through Claude Code, with tool use (shell, code execution, Sentry, Railway and GitHub connectors). ## 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 - [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> |
||
|
|
db95539d46 |
fix(agents): refuse agent purge when cost history references it (#19022)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A board user can permanently delete (purge) a terminated agent > - The purge deletes the agent's heartbeat runs, but cost and finance events still point to those runs > - Postgres then rejects the delete with a foreign key error, and the API returns a 500 > - Cost and finance events are accounting history. The purge must not remove them > - This pull request makes the purge check for this history first and return a clear 409 > - The benefit is a clear, expected response instead of a 500, and accounting history stays intact ## Linked Issues or Issue Description **What happened?** Permanent agent deletion failed with `Failed query: delete from "heartbeat_runs" where "heartbeat_runs"."agent_id" = $1`. The cause is `update or delete on table "heartbeat_runs" violates foreign key constraint "cost_events_heartbeat_run_id_heartbeat_runs_id_fk" on table "cost_events"`. The server reported this as an unexpected error. Sentry: PAPERCLIP-BACKEND-4Z. **Steps to reproduce** 1. Run an agent so that a heartbeat run records a cost event. 2. Terminate the agent. 3. Permanently delete (purge) the agent. **Expected behavior** The purge stops before it deletes anything. It returns a clear conflict, and the agent stays terminated. This matches the existing `agent_case_history_retained` behavior. **Actual behavior** The transaction fails on the `heartbeat_runs` delete with a raw foreign key error (500). Related, older approaches: #9130 and #1987 tried to delete cost and finance rows during agent removal. This PR keeps the ledger instead. ## What Changed - `deleteAgentDependencies` now checks for `cost_events` and `finance_events` rows that reference the agent or one of its heartbeat runs. It does this before any destructive cleanup. - If such rows exist, it throws `409 agent_cost_history_retained`. Termination stays available. - The activity-log cleanup reuses the same agent-runs subquery. - New embedded-Postgres regression test `server/src/services/agent-cost-history-deletion.test.ts`. It covers a cost event on a run, a cost event attributed to the agent, a finance event on a run, and a normal purge with no cost history. ## Verification - Without the fix, 3 of the 4 new tests fail with the exact Sentry error. Examples: `cost_events_heartbeat_run_id_heartbeat_runs_id_fk`, `cost_events_agent_id_agents_id_fk`, `finance_events_heartbeat_run_id_heartbeat_runs_id_fk`. - With the fix, all 4 pass. - Related suites pass (8 files, 249 tests): `lifecycle.test.ts`, `execution-admission/adapters/postgres.test.ts`, `decision-models.test.ts`, `prompt-injection-prevention.test.ts`, `fast-responses.test.ts`, `agent-timer-deadlines.test.ts`, `case-deletion.test.ts`, and the new test. - `tsc --noEmit` in `server/` reports 0 errors. ## Risks - Behavior change: agents with recorded cost or finance history can no longer be purged. Before this change, these purges already failed with a 500, so no purge that used to succeed is now blocked. - No schema change and no migration. - Docs follow-up: `doc/SPEC-implementation.md`, `doc/DEVELOPING.md` and `maps/agent-access-map/agents.md` describe `agent_case_history_retained`. They do not yet mention the new `agent_cost_history_retained` code. I can add that in this PR if reviewers want it. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), agentic coding 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 - [ ] 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> |
||
|
|
08f3e7ebec |
Retain passive Codex output while Core reconnects (#19029)
## Thinking Path > - Paperclip manages work that AI agents execute through a Runner. > - Core controls the work while the Runner owns the provider process. > - Core can restart while an admitted provider turn is active. > - Reconnect waits used to leave provider output in the process pipe. > - Ordinary provider polling can also wait on a provider RPC before Core reconnects. > - This pull request drains passive local Codex output into bounded durable storage between reconnect attempts. > - Core receives the original events after recovery and keeps authority over tools and approvals. ## Linked Issues or Issue Description Refs #17594. Related work: merged #19353 supplies the Codex receipt-limit fence and deadline-aware reconnect scheduler. This PR now contains only shared passive intake and the already-live native Codex adapter. A controller outage must preserve admitted work and event identity without restoring a provider, sending a provider RPC, or executing a tool during reconnect. ## What Changed - Add a default no-op disconnected-poll hook. Managed providers and ACPX retain that default. - Drain passive frames from the existing local Codex process between reconnect attempts. Requests, terminal reconciliation and lineage checks wait for the connected path. - Persist each accepted event before acknowledging the provider queue. Soft-limit pressure retains the rejected suffix until Core ACKs free space. Hard failures stop the owned provider and preserve the durable prefix. - Preserve credential observations and the existing lease, runtime, grace and provider deadline bounds. Document the intake contract and test replay through the real PRP controller. ## Verification The slice was replayed onto master `bf10bc695a`. `git range-diff f453509e6f..20626230b9 bf10bc695a..HEAD` reports an unchanged patch. The diff has 11 files and contains no ACPX adapter or sidecar changes. Targeted local checks on this head: - `cargo test --manifest-path packages/paperclip-runner/runner/Cargo.toml --locked -p paperclip-runner-core --lib disconnected -- --test-threads=1`: 12 passed. These cover passive deferral, no restoration, the real managed HTTP worker, durable backpressure, oversized output and existing receipt-limit cleanup. - The 10 `credential_observer::tests::` checks passed. The Runner daemon and recovery example build, and workspace Rust formatting passes. - `vitest run src/control-plane/runner-recovery-fixture.test.ts --maxWorkers=1`: four passed. Both controller replacement modes retain the same process and replay event identities. The deadline and silent-handshake cases also pass. - `vitest run src/live/runnerd-codex-transport.test.ts -t late-remote-release-error --maxWorkers=1`: one passed. Whitespace and added-line secret checks pass. Earlier review found that disconnected ordinary polling could perform synchronous managed history reads. The real-worker regression retained here proves no history or stream-reopen request occurs until connected polling. Earlier slice preparation hit a sandbox loopback denial and a missing local daemon; the permitted loopback test and the test after building the daemon both passed. The current-base checks above run with those prerequisites in place. Historical CI on the old combined head failed an unordered AI-connections migration comparison. The faithful full-file local check passed 158 tests, and the failure remains recorded [here](https://github.com/paperclipai/paperclip/actions/runs/42112385671/job/127543554397). Earlier receipt-limit delivery failures are addressed by merged #19353. Previous review scores belong to the old combined diff. On current head `b92d4b604ee6992eeefbc02c8d41ec4f2a608a4d`, [CI run 42125840239](https://github.com/paperclipai/paperclip/actions/runs/42125840239) passed. All 59 checks completed with 55 successes and four intentional skips. Standard review 13 and Apex review 14 each scored 5/5 on this exact head, with zero comments and no unresolved threads. The single Apex request is [recorded here](https://github.com/paperclipai/paperclip/pull/19029#issuecomment-6671993072). ## Risks During an outage, native Codex intake uses 25 ms ticks between reconnect attempts. A provider request or terminal waits for Core. Saturation can pause intake or produce a failure. Managed remote streams retain their existing limits. Tests use controlled local providers; live provider qualification and deployment are separate. ## Model Used OpenAI GPT-6.1 Sol (`gpt-6.1-sol`), high reasoning effort, through Codex. Used source inspection, local native and PRP tests, and code review. ## 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> |
||
|
|
5ac05871b5 |
fix(onboarding): unify sign-in actions and recover failed logins (#19336)
## Thinking Path
> - Paperclip lets people create agents and connect their AI accounts.
> - Onboarding uses the shared login panel to connect Claude and OpenAI
accounts.
> - The sign-in link did not update the footer, so the footer could open
sign-in again.
> - A failed or expired sign-in also left onboarding without a retry
action.
> - This pull request gives both sign-in controls one action and makes
failed attempts recoverable.
> - Users can return with a code or start a fresh attempt while keeping
their setup.
## Linked Issues or Issue Description
**What happened?**
The onboarding instruction link opens Claude but leaves the footer
offering Sign in to Claude. If a code is entered without a paste event,
the footer opens provider sign-in again. A failed code, expired session,
or failed sandbox request can also leave an error with no retry control.
Component tests reproduce these failures.
This PR addresses that explicit footer action. It does not establish a
fix for unsolicited navigation of the original Paperclip tab to Claude
before code entry. That reported hosted-Cloud behavior remains under
investigation and needs browser-level evidence.
**Expected behavior**
The link and footer should use the same sign-in action. Both should open
one provider tab and put the footer into a disabled Waiting for code…
state. The code field should stay available. Failed or expired attempts
should show a clear message and an enabled Retry sign-in button.
**Steps to reproduce**
1. Choose Claude during onboarding with no saved subscription login.
2. Open Sign in to Claude from the instruction link.
3. Return and enter the authorization code without a paste event, such
as by typing it.
4. Press the footer action. Before this fix, it opens Claude again.
**Paperclip version or commit**
Reproduced in component tests against master at
canary/v2026.1105.0-canary.22
|
||
|
|
002455ea06 |
fix(issues): finish a reassignment whose native stop settles after the request (#19354)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A user can stop or interrupt a running native task and then send a message to give new direction > - When the stopped run left a workspace hold, the dispatch gate withholds the follow-up run until recovery saves the work > - The gate must save the user's message on its receipt so that the deferred-receipt worker continues it after recovery > - Two conditions made the gate skip the message instead of saving it, so the task stayed in progress with "Saved work recovered. Send a message to continue." although the user already sent one > - This pull request saves the message in both conditions > - The benefit is that the message the user already sent continues once, automatically, after recovery ## Linked Issues or Issue Description **What happened?** After a user stopped or interrupted a native task with an isolated sandbox workspace and sent a message, the follow-up run was cancelled with `execution_reconciliation_required`. Its error said "Saved work recovered. Send a message to continue." Nothing re-woke the task. In the interrupt case, a periodic sweep started a run about four minutes later. In the case where the follow-up first waited for the stopped run's workspace save (`workspace_busy`), each later retry parked on the same resolved recovery action, and the task did not continue. **Expected behavior** When the recovery that the follow-up waited on resolves, the message that the user already sent continues once, automatically. The product does not ask for a second message. **Steps to reproduce** 1. Run a native task in a managed sandbox with an isolated workspace. 2. Stop the run (or send a comment with interrupt), then send a message while the stop and workspace save are still settling. 3. Wait for the workspace recovery to resolve. 4. See that the follow-up run is cancelled with `execution_reconciliation_required` and that no run continues the message. **Paperclip version or commit** `master` before this change. **Root cause** `deferUnstartedUserComment` (run dispatch) refused to save the withheld message in two cases: 1. It compared the message time with the source run's `finishedAt`. A native run finishes only after its provider stop and workspace save settle, which takes seconds. A message written after the Stop request but before that settlement was treated as input written before the Stop. The receipt was marked `skipped`. 2. It required the run's own receipt to be user-owned. A workspace-busy retry or an admission retry carries the user's message under a system-owned receipt, so the message was dropped with the retry. ## What Changed - Add `userStopBoundaryAt(run)` in `run-cancellation.ts`. For a run cancelled by a user Stop or interrupt, it returns the recorded Stop request time (`cancellation.recordedAt`) when that is earlier than `finishedAt`. For all other runs it returns `finishedAt`. - Use this boundary in the dispatch gate's message deferral, in native explicit-continuation admission, and in the remote-Stop continuation check, so the three places agree on "message written after the Stop". - When the withheld run is a system-owned retry, walk a bounded chain (max 8) of retries that did not reach the provider (`workspace_busy` with `providerWorkStarted: false`, or a verified pre-dispatch `execution_reconciliation_required`) to the originating user receipt. Save the message on that receipt with the user's original wake context. Mark the retry's system receipt `skipped`. - Update the "input written before the Stop" test so that the Stop request, and not only the settlement, comes after the message. ## Verification - New regressions in `server/src/services/workspace-recovery.test.ts` (`continues a message ... without a second message, after recovery already resolved`): a direct follow-up and a workspace-busy retry. Each runs recovery to completion first, then lets the gate withhold the follow-up, then runs only the deferred-receipt worker. Each asserts that exactly one successor run is queued with the user's message and the user's authority, and that no provider work starts. - Red before the fix: both tests fail because the receipt is `skipped` instead of `deferred_issue_execution`. Green after the fix. - `npx vitest run src/services/workspace-recovery.test.ts` (384 passed). - `npx vitest run src/services/explicit-native-continuation.test.ts src/modules/run-dispatch src/services/execution-continuation.test.ts src/services/run-cancellation.test.ts` (581 passed). - 19 more heartbeat, retry, queued-comment, and native recovery suites (1272 passed). One failure, `heartbeat-process-recovery > redacts opaque environment-bound credentials from Sentry diagnostics: result`, also fails on unchanged `master` and is not related. ## Risks - A message written after the Stop request but before the stopped run settled now continues. The stopping run cannot consume new input after its Stop request, and admission still removes comments that a run already delivered (`undeliveredLegacyUserCommentIds`). All admission gates (holds, stop proofs, approvals, budget) still apply. - Only user Stops and interrupts move the boundary. Other cancellations keep `finishedAt`. - The retry walk is bounded, stays in one company, agent, and issue, and accepts only retries without provider work. It revives a user receipt only from `cancelled` or `skipped`, so a second retry cannot save the same receipt twice. - Not in scope: a run that fails during native preparation with no user message (for example `runner_state_identity_mismatch: prior_owner_terminal_state_indeterminate; retirement_refused=unexpected_root_entry` on a system wake). After its recovery resolves, the task still waits for a user message. That is a separate root cause in the native session executor's prior-owner retirement proof. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f9d366acdf |
fix(runner): require a managed AI connection for plugin Runner-task environments (#19341)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - A plugin can provide Runner execution through an environment driver that declares `supportsTasks: true`. Agent computers use this path. > - Core let an agent without a managed AI connection run on that path. The run then used the account that was installed on the plugin's execution host. > - That is not a supported credential source. When the host Runner did not match, the run failed three times, 30 seconds apart, and then blocked the task with a generic error. The error did not name the fix. > - The plugin cannot refuse these runs itself. `describe` has no connection data, and Core replaces plugin error text before the user sees it. > - This pull request makes a managed AI connection a requirement for every plugin Runner-task environment. Core refuses a run without one before it acquires a lease or calls the plugin. > - The benefit is one clear notice that tells the operator to choose an AI connection, and no wasted retries or plugin work. ## Linked Issues or Issue Description Refs #19326. That pull request makes a Runner revision mismatch retry. It noted that runs without a managed AI connection reach the Runner check on a different path, which still blocks after 3 attempts. This pull request removes that path for plugin Runner-task environments. **Problem or motivation** A `paperclip_runner` agent with no managed AI connection can run in a plugin environment that provides Runner tasks. The run uses the account installed on the execution host. If the host Runner does not match, the native session retries 3 times and then blocks the task with "Plugin-provided Runner execution operation unavailable". The operator cannot tell what to fix. **Proposed solution** Refuse these runs in Core before any lease, plugin call, secret resolution, or provider work. Use one stable reason, `ai_connection_required`, for run admission, the setup probe, and agent verification. Show a task notice that names the fix. **Alternatives considered** A refusal in the plugin. It cannot see the connection at `describe`, which runs first. Its error text is also replaced twice before the user sees it. A new structured plugin error code would still not cover `describe`. ## What Changed - Add `server/src/services/plugin-runner-ai-connection.ts`. It holds the `ai_connection_required` code and message, the probe result, and the manifest lookup for a Runner-task driver. The lookup moves here from `agent-runner-selection.ts`, so run admission and runner selection use the same rule. - `heartbeat/execution-configuration.ts`: refuse a new `paperclip_runner` run in a Runner-task environment when it has no managed AI connection. This also applies when `homeMode: "preconfigured"` drops the connection. The run fails as `configuration_incomplete` with reason `ai_connection_required` and an action URL to the agent's runtime settings. The check runs before observability, secrets, the managed runtime, the workspace, and the lease. A resumed native run keeps its admitted contract. - `recovery/stranded-notice.ts`: add an "AI connection required" task notice. - `run-failure-report.ts`: treat this pre-dispatch refusal as an owner action, not an unexpected failure. - `adapters/registry.ts`: the unmanaged probe for a Runner-task target now returns a failed `ai_connection_required` check. It calls no task operation. Managed probes still go through `testManagedEnvironment`. - `agent-harness-verification.ts` and `agent-lifecycle/application/failure.ts`: classify the check as `ai_connection_required` with a fixed message. Verification waits for the configuration to change instead of retrying. - Docs: `PLUGIN_RUNNER_EXECUTION.md` states the requirement. `PLUGIN_ENVIRONMENT_TASKS.md` no longer mentions installed accounts. `agent-config-ui.md` notes the requirement. - Tests: the "plugin-owned account" heartbeat test described the behavior that this change removes. It now checks the assembled heartbeat refusal: no plugin call, no native boundary, no adapter, no workspace files. ## Verification - `npx vitest run` in `server/` for these files. All tests passed: - `src/__tests__/heartbeat-execution-configuration.test.ts`. New cases: refused without a connection, refused for a preconfigured home, managed passes, resumed native input passes, non-task plugin driver passes. The refused cases also check that observability, secrets, and the managed runtime did not start. - `src/__tests__/heartbeat-plugin-environment.test.ts`. One test, "inherits the instance default environment across companies…", also fails on unmodified `master` in my environment. This change does not touch it, because its driver does not declare `supportsTasks`. - `src/adapters/registry.test.ts`, `src/__tests__/agent-harness-verification.test.ts`, `src/modules/agent-lifecycle/__tests__/diagnostics.test.ts`, `src/services/recovery/stranded-notice.test.ts`, and `src/services/__tests__/run-failure-report.test.ts`. - 17 more suites that use plugin Runner-task environments, with 705 tests: `plugin-runner-task.integration`, `plugin-warm-tasks.integration`, `task-creation-recovery.integration`, `native-startup-cleanup-recovery`, `environment-task-authority.integration`, `plugin-task-credential-writeback`, agent-lifecycle `lifecycle`, `setup`, and `environment-plugins`, `workspace-preparation`, `agent-runner-selection`, `agent-harness-verification-provider-api`, `agent-harness-verification-remote`, `plugin-environment-driver-seam`, `environment-task-runtime`, `agent-test-environment-routes`, and `adapter-registry`. - `pnpm typecheck` in `server/` reported no errors in the changed files. Its only errors came from sandbox-provider packages that my local install did not link. - Not run: `plugin-runner-probe.test.ts`, because it needs a Cargo build. This change does not modify `probePluginRunnerEnvironment`. Also not run: the full `pnpm test:run` and `pnpm build`. CI runs them. ## Risks - Behavior change: an agent in a plugin Runner-task environment with no managed AI connection now stops before dispatch. Before, it ran with the host's installed account. The agent's owner must choose an AI connection. This includes agents with `homeMode: "preconfigured"` in these environments. - Runs that were already admitted and resume from saved native input are not refused. They keep the earlier behavior until they finish. - The check reads the plugin manifest on each new run admission for plugin environments. This is the same lookup that runner selection already does. - This pull request and #19326 both edit `stranded-notice.ts` and `failure.ts`. The changes are additive, but whichever merges second needs a small rebase. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, in Claude Code with tool use (file edits, shell, test runs). Default context window, extended thinking on. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bf10bc695a |
feat(task): show the agent's context window in task properties (#19273)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents run through a harness (Codex app-server, or Claude through ACPX), and the runner turns harness events into Paperclip events > - Both harnesses report how full the model's context window is. Codex sends `modelContextWindow` with its token usage. Claude sends `used` and `size` in ACP `usage_update` > - Paperclip dropped both values. The task view could not show how close an agent is to compaction, and a compaction left no visible mark in the task chat > - This pull request carries the context-window occupancy through the runner, stores it with the existing usage events, and shows it as a "Context" row in the task Properties panel > - It also shows a "Context compacted · 230k → 38k tokens" divider in the task chat when the agent compacts its context > - The benefit is that operators can see when a long task is near its context limit and when the agent lost detail to a compaction ## Linked Issues or Issue Description Refs #19252 (same pattern: a canonical harness event turned into task-chat UI). Refs #19267 (status notices; it turns Claude's compaction banner into a notice, and this PR reads that notice too). **What existing behavior does this improve?** The task view shows no context-window information, and a compaction is invisible in the task chat. **Subsystem affected** Runner usage normalization (Rust and TypeScript transport), the PRP usage schema, a read-only issue API, the task Properties panel and the task-chat transcript. **Current behavior** Codex `modelContextWindow` and ACP `used`/`size` are dropped in the runner. `context.compacted` always has null sizes, Claude compactions emit no `context.compacted` event at all, and the live runner turn hides every marker except "interrupted". **Proposed behavior** `usage.reported` carries an optional `context {usedTokens, windowTokens}`. The task Properties panel shows a Context row ("10% of 258k") with a hover breakdown (tokens used, input and cached input, output, cost, compactions). The transcript shows a compaction divider, with sizes from the usage before and after the compaction. **Reason and benefit** Operators can see when a long task is close to its context limit, and they can see where the agent's earlier context was summarized. **Breaking changes** None. The new schema field is optional, and the new endpoint is read-only. ## What Changed - PRP `usage.schema.json`: add an optional `context {usedTokens, windowTokens}` object. Regenerate the schema bundle and the protocol manifest. - Rust `provider_events.rs`: add `codex_context_usage` (last request `totalTokens` and `modelContextWindow`) and `acpx_context_usage` (ACP `used`/`size`). ACPX `usage_update` now sets `context`. - Rust `provider_backend.rs`: Codex `thread/tokenUsage/updated` sets `context` on live (non-historical) usage. - Rust `provider_events.rs`: emit `context.compacted` for the completed `context_compaction` provider notice that #19267 produces from Claude's compaction banner. Only this path marks a Claude compaction, so the server counts each compaction once. - `runnerd-codex-transport.ts`: `rehydrateRunnerdUsageNotification` keeps a valid `context` on `tokenUsage`, so it reaches the stored `item.completed` usage item. - Server: new `GET /api/issues/:id/context-usage` (issue read access is required) and `issueContextUsage` service. It reads only the assignee's current session (the session of its newest usage report), and returns that session's latest context reading, non-zero totals, cost, and the count of compactions across all of the session's runs. Added to OpenAPI. Shared type `IssueContextUsage`. - DB: migration 0432 adds two partial indexes on `heartbeat_run_events`, built concurrently. One indexes usage items by run and time. The other indexes `context.compacted` events by company and session. `EXPLAIN` shows that both lookups use them. - UI: new `ContextUsageMeter` (bar, percentage, hover card) and `IssueContextUsageRow` in the Execution section of Properties. The row refreshes every 8 s while a run is active, and once more when the run ends. - UI transcript: `native-run-events.ts` maps `context.compacted` to a `context` activity entry with pre/post sizes from neighbouring usage items. `transcript-adapter.ts` turns it into a new `context_compacted` marker variant. The live runner turn keeps that marker as its own timeline row. - Storybook: `task-context-window.stories.tsx` (Properties panel in a task, all states, hover breakdowns, divider). ## Verification - `cargo test -p paperclip-runner-core --lib provider_events` (new tests: ACPX context, Codex context, banner compaction, notice compaction) - `pnpm --filter @paperclipai/paperclip-runner exec vitest run src/live/runnerd-codex-transport.test.ts` - `cd ui && npx vitest run src/components/ContextUsageMeter.test.tsx src/components/transcript src/components/task-chat src/components/issue-properties` - `cd server && npx vitest run src/__tests__/issue-context-usage.test.ts` (embedded Postgres) - `node scripts/typecheck-low-memory.mjs`, `pnpm check:token-gates`, `pnpm --filter @paperclipai/paperclip-runner run check:protocol-types` - Live check on a local worktree instance through the runnerd path: - A Codex `gpt-5.5` task showed "10% of 258k" (27k of 258k, input 77k with 49k cached, output 1.1k). - A Claude Sonnet 4.6 task showed "17% of 200k" (34k of 200k). - Storybook: Tasks and conversations / … / Task chat / Context window. - Not verified live: a real compaction. The divider is covered by unit tests through the same projector path the task chat uses. ## Risks - The schema field is optional, and older runners omit it. The UI renders nothing until a harness reports a window. - Migration 0432 builds two partial indexes concurrently on `heartbeat_run_events`, which is a large table. The indexes cover only usage items and compaction events, so they stay small. They add a little write overhead to those events. - Claude in-progress usage rows still record zero tokens with `requests: 1`. The final turn usage is correct. This PR does not change accounting. - A "Compact now" action is out of scope. It needs a compaction-only run that does not produce a run result. It will come in a follow-up. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edits, in-app browser for Storybook and the live instance). ## 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> |
||
|
|
b61a815d3d |
Enforce Codex receipt-limit deadlines while Core is offline (#19353)
## Thinking Path > - Paperclip uses a Runner to execute agent work. > - The Runner keeps a record of each tool call, called a receipt. > - When that storage reaches its limit, the Runner asks Codex to stop. > - Core can stop responding while the stop request is pending. > - Codex may already have written its completion behind earlier output. > - The Runner must keep its stop deadline and preserve that real completion. ## Linked Issues or Issue Description Refs #19029. This is its first isolated slice; #19029 remains unmerged. Related #17594 covers controller replacement. Related #17516 retains receipts during explicit stop; this change handles the autonomous receipt-limit deadline. ## What Changed - Keep the existing 2s/30s stop deadlines while Core is offline, including when a connection waits for a reply. Other lease and runtime deadlines still apply. The Runner sleeps between connection attempts and deadline checks. - Stop the owned local Codex process when its deadline expires. Read its remaining output within the existing size and message limits, then process it in order. Only after output has ended and all earlier messages have been processed can the Runner report a missing completion. - After the local process has stopped as requested, skip replies that only reject or cancel old requests. Commands, user responses and actual tool results still require successful delivery. - Keep the stop behavior, regression tests and transport documentation in this slice. ## Verification Standalone checks on this branch: - Seven existing Codex tool-record limit integration cases pass. - Twenty focused checks cover deadline cleanup and retained output, including 273 distinct messages followed by one real completion, missing completion, and a pipe that stays open. - Separate checks cover strict tool-result delivery, three process-stop cases, and lease/runtime/reconnect time limits. - Four controller process tests pass. They include Core accepting a connection but never replying, and a reconnect delay longer than the allowed recovery period. - Existing warm attachment still retains the completed turn's remaining output and usage. - Rust formatting, diff checks and the local secret scan pass. The extraction first omitted an existing test helper and the connected retry preamble. Standalone compilation and receipt tests caught both omissions. Restoring them produced the results above. Full regression checks run in GitHub CI. Hosted CI passed both native platforms and typecheck, but [server11 failed](https://github.com/paperclipai/paperclip/actions/runs/42118725143/job/127565828671) one unchanged status-card test. Its fake clock advances while real database work runs; the final clock assertion was 50 ms past its limit. The identical test, service and scheduler passed all 36 cases in [the earlier baseline shard](https://github.com/paperclipai/paperclip/actions/runs/42111889388/job/127541685625). The local comparison first needed a built SDK, then stopped at embedded Postgres startup before any test ran. It did not reproduce the assertion. The single failed-jobs-only retry passed. The original failure and the blocked local comparison remain recorded here; the retry does not establish a precise timing cause. At exact head `f453509e6f`, all 59 hosted checks completed: 55 succeeded and four were skipped. Snyk also passed. Standard review round 1 and selected review round 2 each scored 5/5 on this head, with no unresolved threads. ## Risks The Runner now stops its local Codex process at the original deadline even while Core is unavailable. If another program keeps the output pipe open, or queued output exceeds the existing limits, the Runner cannot confirm the outcome. It reports that uncertainty and keeps the records already written to disk. Tests use real child processes with controlled provider fixtures. Live provider and deployment qualification remain separate. ## Model Used OpenAI GPT-6.1 Sol (`gpt-6.1-sol`), high reasoning effort, through Codex. Used source inspection, native subprocess tests and targeted TypeScript tests. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [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> |
||
|
|
a6ab6a301c |
fix(server): retry a run when its remote Runner is out of date (#19326)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - An agent can run on a remote Runner that an environment plugin
provides
> - A release can update the app before the remote Runner, so for a
short time the app sees an older Runner
> - In that window the plugin can refuse the task, but Core cannot tell
this refusal from other errors
> - Today the run fails as `setup_failed` with no retry, and the task
goes to board recovery. Before #19246, it was also reported as a project
authentication conflict
> - Agent verification also counts the refusal as a failed harness test,
and it parks the agent after three tries
> - This pull request gives the plugin a stable error code for a Runner
mismatch, and Core waits and retries when it gets that code
> - The benefit is that a release no longer blocks tasks or parks
agents, and a real authentication conflict keeps its own clear notice
## Linked Issues or Issue Description
Refs #19246 (it stopped reporting every precheck error as a project
authentication conflict)
Refs #19249 (the same bounded bootstrap retry for a stopped plugin
worker)
Refs #19302 (shares `environment-task-error.ts`; its diagnostic envelope
is unchanged)
Refs #19172 (related open PR: it reports an undescribable plugin Runner
as a retryable check in the same precheck)
Refs #18667 (a real `ai_connection_incompatible` conflict, which now
gets its own notice)
**What happened?**
A run started while its remote Runner was older than the app. The
environment plugin refused the task because the installed Runner did not
match the revision it expects. Core recorded a generic setup failure,
scheduled no retry, and blocked the task. Before #19246, Core reported
this as a project authentication conflict. An agent in `verifying`
counted the same refusal as a failed harness test. After three tries it
stopped verification until a person chose Retry.
**Expected behavior**
Core treats a Runner mismatch as temporary. A run retries without
blocking its task. A `verifying` agent checks again until the Runner
matches. Only a real `ai_connection_incompatible` result blocks the
task, with a notice that names the conflict.
**Steps to reproduce**
1. Use an agent on a plugin environment whose installed remote Runner
revision differs from the revision the plugin expects.
2. Wake the agent on a task, or put a new agent through verification.
3. The run fails with `setup_failed` and the task is blocked.
Verification parks after three tries.
**Paperclip version or commit**
master at
|
||
|
|
4ece2107b8 |
fix(connections): offer aggregator Slack sign-in on Cloud instead of a customer OAuth app (#19328)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Connectors let people give agents tools, such as Slack, from the
Connectors catalog or from an agent's inline "Connect" card
> - On Paperclip Cloud, the only Slack path is "Slack needs its own
OAuth app". The user must create a Slack app, paste a callback URL, and
enter a Client ID and secret
> - Managed Slack chat setup exists, but it is behind the experimental
Chat connectors setting, which is off on Cloud. Composio's and Arcade's
managed Slack sign-in exists, but the native Slack row hides it
> - In an onboarding test on Cloud, a tester got this card from the
onboarding agent, spent 7 minutes on it, and declined it. No Slack
connection was made
> - This pull request follows the existing Google Workspace pattern: on
Cloud, a native connector whose every setup path needs a
customer-created OAuth app yields new setup to the aggregators' managed
sign-in, in the catalog and in agent connection discovery
> - The benefit is that Cloud users can connect Slack with a sign-in
instead of creating a Slack app
## Linked Issues or Issue Description
**What happened?**
On a Paperclip Cloud instance, the onboarding agent asked the user to
connect Slack. Connectors > Slack > Connect showed "Slack needs its own
OAuth app. Slack does not let Paperclip register itself automatically,
so this connector needs an OAuth app you create." The form asks for a
callback URL, a Client ID and a Client secret. It has no link to Slack
app settings. The method description said "Use the provider-hosted
connection for the quickest setup." The tester gave up. The original
notes said that the cloud connector asked for an HTTPS server. That is
not correct: no HTTPS or deployment-mode check is in this path. The real
blocker is the customer OAuth app requirement.
**Expected behavior**
Slack can be connected from the supported Cloud flow without setup steps
that only an operator can do.
**Steps to reproduce**
1. On a Cloud instance with Chat connectors off (the Cloud default),
open Connectors and search for "Slack".
2. Click Connect on Slack. The "needs its own OAuth app" form opens.
3. Or, let an agent call `connections_search("Slack")`. It returns
native `slack` first, and its card opens the same form.
**Additional context**
Evidence: the Slack gallery entry on the Cloud instance had
`ownershipAvailability` `platform_shared: false`, `platform_provisioned:
false`, `customer: true`. `GET
/api/companies/:id/chat-slack/setup-options` returned 403 "Chat
connectors are disabled".
## What Changed
- `packages/shared`: add `nativeConnectorRequiresCustomerOAuthApp(app,
settings)`. It is true when every available setup path of a native
connector needs a customer-created OAuth app: no Paperclip-managed
client, no dynamic client registration, and no enabled chat setup.
- `ui/src/pages/apps/Browse.tsx`: on Cloud, such a connector yields to
aggregators, the same as the built-in Google connectors. The catalog
shows the "Connect through Composio or Arcade" row, and its Connect
button opens the provider choice. A saved native connection, a chat
endpoint, Chat connectors on, a self-hosted instance, or no aggregator
route keeps the native row.
- `server/src/services/connection-intents.ts` and new
`connection-native-yield.ts`: agent `connections_search` uses the same
rule. On Cloud, it does not prefer native Slack for new setup, so the
agent asks the provider-choice question (Composio, Arcade or Zapier).
The rule applies the advertised Paperclip Cloud connector profiles, so
apps with a managed Cloud client (for example Asana) do not yield. If
the profiles cannot be read, the native connector stays.
- `slack.json`: correct the Slack tool method copy, which said "quickest
setup". Add a "register" console link to `https://api.slack.com/apps`,
so the form now shows "Open Slack app settings".
- Tests in all three layers.
## Verification
Red/green. I committed the tests first (`2386fd83df`) and ran them
against unchanged source code.
**Red (before the fix):**
```
server › aggregator connection fallback › Slack on Paperclip Cloud
× offers aggregator Slack routes while native Slack only supports a customer-created OAuth app
AssertionError: expected [ 'slack', 'google-calendar', …(7) ] to deeply equal [ 'via:composio:slack', …(2) ]
+ "slack", "google-calendar", "google-docs", ...
Tests 1 failed | 3 passed | 45 skipped (49)
ui › Connectors landing page
× offers aggregator Slack sign-in on Cloud while the built-in Slack connector needs a customer OAuth app
AssertionError: expected 'SlackSend and read messages in your t…' to contain 'Connect through Composio or Arcade.'
Received: "SlackSend and read messages in your team's channels.Connect"
Tests 1 failed | 5 passed | 92 skipped (98)
shared › Slack tool connection guidance
× describes the customer-created Slack app honestly and links to Slack app settings
AssertionError: expected 'Use the provider-hosted connection fo…' not to match /quickest/i
Tests 1 failed | 3 passed (4)
```
**Green (after the fix):**
```
✓ server › Slack on Paperclip Cloud › offers aggregator Slack routes while native Slack only supports a customer-created OAuth app
✓ server › Slack on Paperclip Cloud › keeps native Slack when Chat connectors provide its own setup path
✓ server › Slack on Paperclip Cloud › keeps native Slack for a company that already saved a Slack connection
✓ server › keeps native Slack on self-hosted instances, where operators register their own Slack app
✓ ui › offers aggregator Slack sign-in on Cloud while the built-in Slack connector needs a customer OAuth app
✓ ui › keeps the built-in Slack connector when Chat connectors offer its setup path
✓ ui › keeps the built-in Slack connector when the instance is self-hosted
✓ ui › keeps a saved built-in Slack connection on its native row on Cloud
✓ shared › describes the customer-created Slack app honestly and links to Slack app settings
✓ shared › flags Slack only while its chat setup is unavailable
✓ shared › does not flag connectors with a managed, dynamic-registration, or credential path
```
Neighbouring suites:
- `packages/shared` (all): 98 files, 1060 tests passed
- `ui` `src/pages/apps` + `src/features/connections`: 54 files, 980
tests passed
- `server` `connection-aggregator-fallback` 49/49,
`connection-intents-service`, `native-connection-baseline-fixture`,
`runner-connection-eval-fixtures` passed. `satisfied-connection-intents`
52/52 passed when run alone. It had a suite-setup error only when three
embedded-Postgres runs went in parallel.
- `pnpm check:token-gates`: clean. `tsc --noEmit` shows no errors in
changed files. Local runs show unrelated errors because the
`paperclip-runner` workspace package is not built. CI runs the full
typecheck.
Still needs a live Slack workspace to prove:
- Complete Slack sign-in through Composio or Arcade on a Cloud instance,
and an agent tool call to Slack.
- An agent run on Cloud that asks the provider-choice question for Slack
and then connects through the selected aggregator.
## Risks
- Behaviour change on Cloud only, for native connectors whose every path
needs a customer-created OAuth app and that an aggregator serves. Today
that is Slack (when Chat connectors are off), and Box and Xero if the
aggregator catalog has them. On Cloud, these move to the aggregator
route for new setup, the same as the built-in Google connectors. Saved
connections keep their native row and discovery.
- Cloud users who want their own Slack app can still open
`/apps/connect?source=slack`, but the catalog row no longer links to it
while the rule applies.
- Self-hosted instances keep the native connector unchanged.
- Follow-up, not in this PR: enable managed Slack chat on Cloud (Chat
connectors Cloud default and `PAPERCLIP_SLACK_MANAGED_SETUP_ENABLED`).
This is a flag and operations change. It needs the Cloud Slack broker to
be proven first. When it is enabled, this rule stops yielding Slack
automatically.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, browser inspection of a staging instance, file
editing).
## 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.1105.0-canary.21
|
||
|
|
390ef983d6 |
test(evals): pin coherent secure-entry and team repairs (#19346)
## Thinking Path > - Paperclip manages AI agents and their work. > - The protected TestOps catalog selects one immutable source revision. > - The current pin predates reviewed secure-entry and Team observation repairs. > - A new run must use the qualified source and its exact descriptor bytes. > - This PR updates only the source pin, retained descriptor snapshot, and expected-target test. > - Existing failures remain failures until a new native run proves the feature. ## Linked Issues or Issue Description Refs: #19333, #19084, #18971, #19112. **What happened?** The current catalog points to the earlier source. It does not include the reviewed personal entry-dialog observer, visible secure-entry guidance, company menu navigation, Team claim attribution, or completion-link guidance changes. **Expected behavior** The protected catalog must select the qualified immutable source and verify its exact descriptor snapshot. Model, original queries, run counts, clocks, private transfer interfaces, and retries must remain fixed. **Steps to reproduce** Read the manifest target and candidate descriptor digest. Compare them with source `7ccfc33bf074e37d8fd901a6e8453d1b45307e82` and its committed descriptor bytes. ## What Changed - Pin source `7ccfc33bf074e37d8fd901a6e8453d1b45307e82`, tree `5afa0cf2e8ad8d17b7563e62378b0994f7777c67`. - Store the exact candidate descriptor snapshot with SHA256 `e1b7de947567569d96fcc9dca607ef5807ffaba4bf994562c0c7ab46d1856df6`. - Update current version and source-file provenance while retaining the original anchors and prior conditions. - Update one expected-target assertion. This PR changes exactly three metadata/test files. The final target-only successor retains the previous pin commit and its CI results; descriptor bytes do not change. ## Verification - 41 model-free manifest, adapter, reconciliation, and CLI controls pass. Another 42 chat-admission controls pass. - Initial source qualification: 268 focused controls, five explicit offline Chromium controls, and full E2E types pass. The first six-path Company/Team amendment passes 233 affected controls and full E2E types. The final reviewed six-path followup passes 271 affected controls, two offline browser canaries and full E2E types. Unchanged source inputs reuse the prior qualification. The accepted completion-feedback core patch has 22 real PostgreSQL controls from its owner. - All 244 scenario YAML definitions and all 242 descriptor run counts, clocks, authentication, and setup policy remain unchanged from the previous source. Seven descriptor rows change only versions or source-file lists. - SDK506 inputs and registry `efa70621` remain unchanged. Launcher SHA256 `2b9a6953` and private transfer interfaces remain fixed. - Fresh-main reporting installation passes with pnpm9.15.4, frozen lockfile, ignore-scripts, and offline mode. The unchanged reviewed report policy returns `already-reviewed`. No lock or workflow change is included. - Repo-wide build/typecheck were not repeated for this three-file metadata update. Normal PR CI remains required. ## Risks - Both secret journeys require independent review of the actual native handoff before any PASS credit. Unsafe value requests cannot count as a feature pass even if an oracle accepts them. Two additional unexercised adversarial wording guards (comma-only plaintext destinations and mixed reference/password requests) are being hardened separately in #19093. They are not included in this source pin. No current native PASS is claimed. - This pin includes reviewed source changes whose separate core/catalog PRs remain unmerged. This PR does not merge those features into master. - New native measurements remain required. The prior personal failure remains failed; its first native took 580602ms, and completion within the unchanged 660s remains unproven. - The plain-comment save acknowledgment fixes a downstream phase that the failed attempt did not reach. Component checks do not prove native completion. ## Model Used OpenAI Codex, GPT-6.1 Sol (`gpt-6.1-sol`), with code execution and repository tools. The runtime does not expose its context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge |
||
|
|
789a71bc17 |
fix(runner): retry quarantined tasks in their saved workspace (#19340)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Native sessions retain execution ownership and action history across turns. > - Task Retry must prove that old execution has stopped before it starts another turn. > - An attempt can inherit its predecessor's identity and fail before it launches a provider. > - The cleanup proof searched under that failed attempt and compared unrelated event sequences. > - This pull request verifies the retained owner and starts one fresh authorized turn. > - Saved results and uncertain action outcomes remain intact. ## Linked Issues or Issue Description **What happened?** Task Retry accepted repeated clicks but kept the canonical request deferred with `executionWait.reason: local_cleanup`. It created no successor, although the retained local worker and provider had stopped. **Expected behavior:** After exact cleanup proof, admit one fresh turn. Preserve the old journal and result. Keep unresolved action outcomes unknown. **Steps to reproduce:** 1. Finish a local native turn with retained cleanup state and a saved result. 2. Let the next attempt inherit its identity and fail with `native_session_cleanup_quarantined` before any launch. 3. Upgrade across a tool-contract change, then click task Retry. 4. The old code requires the failed attempt's stop receipt and journal identity. It also requires the current tool fingerprint and equal sequence numbers across raw and normalized events. Those requirements cannot match the retained owner. **Paperclip version or commit:** Reproduced on `31bf6e4482`. **Deployment mode / installation method:** Authenticated, self-hosted source checkout. **Agent adapter(s) involved:** Native `paperclip_runner`, local Codex app-server. The shared physical-stop proof also covers ACPX. **Database mode:** Embedded PostgreSQL 18.1. **Access context:** Board task Retry. **Node.js version / operating system:** Incident: Node 24.20.0, Linux ARM64. Local verification: macOS ARM64. **Relevant logs or output:** Accepted Retry stays in `deferred_issue_execution`; no new run or provider launch occurs. **Relevant config:** Per-turn local native execution. The old tool contract differs from the current contract. **Additional context:** Related: #19311 prevents the oversized tool result that can lead to retained cleanup. #18217 covers broader cleanup isolation and retained-result maintenance. #19134 admits stopped Runner sources under the current run lease. This patch fixes explicit continuation admission on current master. ## What Changed - Resolve the retained journal's owner under the same company, task, agent, workspace, native session, and runner instance. - Require terminal ownership, settled controllers, released local leases, and exact inherited process birth. Refuse any launch evidence on the failed attempt. - Accept a current host's verified process absence from its exact launch receipt when an older server lacks a stop receipt. A later launch invalidates that observation. - Validate normalized receipts against their own IDs, sequences, and digests. Match ordered provider identities to the independent raw journal. - Keep tool compatibility at the session reuse boundary. Explicit continuation always uses a fresh session. - Bind the stop proof to its exact successor and canonical Retry receipt. Reuse the proven local workspace through normal preparation. Refuse changed bindings, closed or remote workspaces, pending operations, and other live leases. Projectless run-ID placeholders keep the existing fresh-workspace fallback. - Retain journals, results, files, and uncertain action outcomes. Add regression and opt-in live acceptance coverage. - Make the split-handoff browser test wait for both child-completion notices and all run settlement before teardown. Assert the final task set stays unchanged. ## Verification - Red: canonical Retry on the original code accepts one saved intent but creates zero successors and records `local_cleanup`. - Red: distinct raw and normalized event sequences reproduce that wait after owner resolution alone. After admission was repaired, live acceptance then exposed the empty-attempt workspace problem. A PostgreSQL regression also reproduced that wrong workspace selection. - Green at `6684df5e82`: **479 targeted cases**, including 26 new PostgreSQL cases for duplicate clicks, exact process and workspace ownership, controller/lease fences, changed state, and independent receipt integrity. The complete 1,150-test native executor suite passed earlier. - `pnpm -r typecheck` and `pnpm build` pass again at final head `432a52bea8`, including its build stamp. Token gates pass. Master now contains the unrelated directory-fixture type correction found during the first typecheck. - Real authenticated HTTP Retry with Codex `gpt-6.1-sol`, high effort, and per-turn execution passes again at final head `432a52bea8` (93.3 seconds). It preserves the first file line, appends the second once, starts a fresh session in the existing workspace, keeps the retained journal unchanged, commits finalization, and clears task locks. - The strengthened split-handoff browser scenario passes **three consecutive local runs**. Its original CI teardown and one same-head rerun failed when cleanup cancelled an active legacy completion-notification run and hit a PostgreSQL row-lock timeout. The scenario now waits for the complete handoff before cleanup. [Initial failure](https://github.com/paperclipai/paperclip/actions/runs/42106466518/job/127523693634), [same-head rerun](https://github.com/paperclipai/paperclip/actions/runs/42106466518/job/127531698321). These failures remain part of the verification record. - The local full `pnpm test:run` passed all six chat integration shards. Its obsolete pre-rebase attempt was stopped during the general server phase; no complete local-suite pass is claimed. Full CI, including both required gates, passed at `6684df5e82`. At final head `432a52bea8`, all initial CI groups passed except the unchanged status-card deadline test, whose post-wakeup clock assertion missed its bound by 10 ms. That exact case passed three unchanged local repetitions (16.8/27.0/18.3 seconds); the single isolated shard rerun passed all **2,218 tests** (123 files), with no source changes. [Initial timing failure](https://github.com/paperclipai/paperclip/actions/runs/42114473652/job/127550722187). - Greptile reviewed `6684df5e82` at **5/5**, with no actionable findings. A second reviewer identified the supported projectless placeholder case. A new real PostgreSQL regression reproduced its workspace-binding failure before the one-line fallback correction; all **45 focused cases** now pass, including default environment selection and changed binding/cwd refusal. Greptile reviewed final head `432a52bea8` at **5/5**, with no blocking findings. The second reviewer’s thread is resolved. - The patched Linux build verified the actual retained worker/provider ownership and process absence in a transaction that was rolled back. No retained state was retired or provider started during that pre-deployment check. ## Risks - This changes an execution admission proof. Incorrect ownership could permit a second executor. The proof retains process, controller, lease, scope, journal, and receipt fences, and duplicate admission is tested through the real queue. - Missing or conflicting evidence remains deferred. Windows, remote execution, automatic replacement, and session reuse retain their existing policies. - No database migration or UI change. The original affected task still needs deployment of the fix before recovery can be claimed. ## Model Used - OpenAI GPT-6 through Codex, with reasoning, repository tools, code execution, SSH diagnostics, and live provider testing. This session does not expose the exact backend model build or context-window size. The live test agent uses OpenAI `gpt-6.1-sol` at high effort. ## 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> |
||
|
|
15249a689d |
fix(server): preserve sessions across cosmetic sandbox updates (#19349)
## Thinking Path > - Paperclip manages agent work and saves provider sessions between turns. > - Session and workspace compatibility include the selected environment revision. > - The managed sandbox reconciler also updates names, descriptions, and stock metadata. > - Those cosmetic updates advanced the revision even when the execution configuration stayed the same. > - A later turn therefore discarded an otherwise compatible saved session. > - This pull request keeps that revision stable for cosmetic reconciliation and retains resets for real configuration changes. ## Linked Issues or Issue Description Related: #18943 and #18552. **What happened?** A managed sandbox rename or description update made a later task turn start a fresh provider session. The driver, provider, snapshot, and agent configuration could all remain unchanged. Both session and workspace freshness use the environment's `updatedAt` as its configuration revision. **Expected behavior** Cosmetic managed reconciliation preserves compatible task sessions. Actual sandbox configuration or availability changes retain the normal replacement checks. **Steps to reproduce** 1. Create a managed sandbox and save a task-session fingerprint for it. 2. Reconcile the same sandbox configuration with a new name, description, or managed metadata. 3. Evaluate the saved session against that environment. Before this fix, the decision resets the session and reports the environment category. **Paperclip version or commit** Reproduced on master `d3713d827f70ddba63a323e57c562384dbd648ba`. **Deployment mode** Managed sandbox server. The regression uses an isolated PostgreSQL database and the actual reconciler and session-freshness resolver. ## What Changed - Keep the existing revision for an active managed sandbox when its execution configuration stays equal. - Continue advancing the revision when configuration or availability changes. - Retain activity records and managed-stock binding updates for cosmetic reconciliation. - Add a regression that checks session reuse, audit history, and rejection after a real snapshot change. - Document the freshness contract. ## Verification - The new PostgreSQL regression failed before the production change: it reported `reset: true` and changed category `environment`. - All 53 environment-service cases pass after the fix. - All 190 session-freshness and assembled heartbeat session-preparation cases pass. - Full local typecheck passes. The full local Vitest run remains active, with its local build queued afterward. Initial typecheck setup failures were retained and resolved by repairing generated SDK links; no source workaround was added. - [Full current-head CI](https://github.com/paperclipai/paperclip/actions/runs/42114984839) passes, including the sharded test suites and build: 55 successful checks, 3 skipped, and 1 neutral. - Greptile and Ponytail both reviewed this commit at 5/5 with no findings. No review threads remain unresolved. - Deployed verification remains pending normal delivery. No pinned release or manual rollout is required. ## Risks - Cosmetic managed reconciliation keeps `environments.updatedAt` unchanged, so it does not reorder that environment by this timestamp. Activity records and managed-resource bindings still record the update. - The comparison uses canonical configuration hashes, so JSON property order cannot hide a real change or create false drift. - Existing fingerprint formats and saved session records remain compatible. Real configuration and availability changes still advance the revision. ## Model Used OpenAI GPT-6 through Codex desktop, with reasoning and terminal/browser tools. The host does not expose a more specific deployment suffix or context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
da3084a05d |
fix(recovery): do not race a just-accepted interaction's own continuation wake (#19332)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents ask humans for decisions through thread interactions, such as
a plan approval
> - When a human accepts an interaction, the accept route enqueues a
continuation wake for the agent
> - A recovery sweep also re-wakes the agent when an accepted
interaction appears to have no continuation
> - The sweep can read the issue in the short gap between the accept and
the accept-time wake, so it queues a second continuation for the same
approval
> - That redundant wake carries the interaction receipt, so the composer
queue shows an immutable "Accepted: <plan>" item that the user cannot
edit or discard
> - This pull request makes the sweep wait a short grace window after
resolution before it treats an accepted interaction as stranded
> - The benefit is that one approval produces one continuation, and the
queue state agrees with the approval
## Linked Issues or Issue Description
No public issue exists. This bug was found in a hands-on onboarding test
on a staging cloud workspace.
**What happened?**
After the user approved the plan, a queued item stayed in the composer
queue. The user could not edit or discard it. The same item stayed.
Approval did not create a new item each time.
Evidence from the staging workspace (wake diagnostics and run records):
- The plan approval resolved at 17:42:05.692.
- The accept-time wake and its run were created at 17:42:05.992. That
run delivered the approval and succeeded at 17:42:51.
- The recovery sweep (`source: issue.interaction_continuation_recovery`)
requested a second wake for the same approval at 17:42:06.201. Its
`retryOfRunId` fell back to the interaction's source run. This shows
that the sweep saw no run after the resolution, so it read the issue
before the accept-time wake existed.
- The redundant wake was deferred behind later runs and a busy shared
workspace. It ran only at 18:19. During that time, `GET
/issues/:id/queued-comments` returned it as an immutable interaction
entry ("Accepted: Approve storefront prototype plan").
**Expected behavior**
One accepted interaction produces one continuation. A delivered approval
does not stay in the composer queue.
**Steps to reproduce**
1. An agent asks for plan approval on a task.
2. A user accepts the plan.
3. The stranded-issue recovery sweep runs within the moment between the
accept and the accept-time wake enqueue.
4. The sweep queues a second continuation that carries the same
approval. The composer queue shows it as an uneditable item until that
duplicate run finishes.
## What Changed
- `server/src/services/recovery/service.ts`: the accepted-interaction
recovery branch now skips the requeue while the interaction resolved
less than `INTERACTION_CONTINUATION_RECOVERY_GRACE_MS` (2 minutes) ago.
The check applies only when no successful run has delivered the
interaction yet. All other recovery branches for the issue keep their
behavior.
- The skip reports the grace deadline through `onDeferredDeadline`. The
sleep-aware recovery scheduler then rechecks the issue at that deadline,
so a lost accept-time wake still recovers without another write.
- `server/src/__tests__/heartbeat-process-recovery.test.ts`: two
regression tests.
- `does not race a just-accepted plan approval before its accept-time
wake is visible` reproduces the staging race and is the red/green test.
It also asserts that the skip registers the deadline, and that a sweep
after the deadline requeues the approval.
- `does not requeue an accepted plan approval whose accept-time wake is
deferred behind another run` guards the deferred accept-time wake case.
This test already passed on `master`.
- `dispatches the onboarding first task once the user answered the
opening card` now seeds an answer 3 minutes old instead of "now". The
test checks that a lost continuation is requeued. An answer inside the
grace window is not yet lost, so the older seed keeps the test's intent.
Related: the Slack connector cancel bug from the same onboarding test (a
stuck steering item after the user declined the connector) is likely the
same redundant plan-approval wake. At 17:52:10, right after the decline,
the item still queued was a retry of this duplicate wake (it carried the
plan approval, not the Slack interaction). This PR removes the
duplicate, so it most likely covers that symptom too. That bug has its
own investigation.
## Verification
Red, on `master` code with only the new tests added:
```
$ cd server && npx vitest run src/__tests__/heartbeat-process-recovery.test.ts -t "plan approval"
× does not race a just-accepted plan approval before its accept-time wake is visible 1029ms
FAIL src/__tests__/heartbeat-process-recovery.test.ts > heartbeat orphaned process recovery > does not race a just-accepted plan approval before its accept-time wake is visible
AssertionError: expected 1 to be +0 // Object.is equality
Test Files 1 failed (1)
Tests 1 failed | 3 passed | 411 skipped (415)
```
Green, with the fix (the final commit also checks the registered
deadline and the requeue after it):
```
$ cd server && npx vitest run src/__tests__/heartbeat-process-recovery.test.ts -t "plan approval"
Test Files 1 passed (1)
Tests 4 passed | 411 skipped (415)
```
Neighbouring interaction-recovery tests on the final commit (`-t "plan
approval|accepted interaction|answered question|answered interaction"`):
9 passed and 1 failed. The failing test, `schedules bounded retries for
failed accepted interaction continuation wakes`, is a timing flake. It
uses a resolution time in March 2026, so the grace window does not apply
to it. It failed 3 of 3 runs with the original `service.ts` on the same
machine under load, and it passed 1 of 2 runs with the fix.
- `pnpm --filter @paperclipai/server... typecheck`: passes.
- `-t "onboarding first task|plan approval"` on the final commit: 8
passed.
- CI runs the full `heartbeat-process-recovery` suite in the serialized
server shards.
## Risks
- Low risk. If an accept-time wake is really lost, recovery now re-wakes
the agent up to 2 minutes later than before. The sweep already runs on
an interval, so this adds at most one sweep of delay.
- The grace window does not change behavior when a successful run
already delivered the interaction.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used in Claude Code
with tool use (shell, file edits, browser inspection of the staging
workspace API).
## 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>
|
||
|
|
2bd2e650fe |
fix(connections): let agents outside a grant request scoped access to large catalogs (#19327)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents use company app connections, such as GitHub, through
`connections_search` and `connection_request`.
> - A per-user OAuth connection is shared only with the agent that
requested it. Other agents must request their own scoped access.
> - In a hands-on onboarding test, the CEO agent connected GitHub. A
Frontend Engineer hired earlier did not get access. It asked for access
and got a refusal that it could not act on.
> - The engineer then reported "no GitHub access" while the CEO reported
"GitHub is connected". The user saw this as inconsistent and unreliable.
> - This pull request makes the refusal actionable. It also makes an
agent with existing access get `ready`.
> - The benefit is that each agent can request, and then accurately
report, its own access to a connection.
## Linked Issues or Issue Description
**What happened?**
An onboarding test on staging connected GitHub through the CEO agent's
connection request. The grant was delegated and installed only for the
CEO. The Frontend Engineer (hired two minutes earlier) called
`connection_request({ service: "github" })` and then
`connection_request({ service: "github", connectionId })`. Both calls
returned 422 `Specify the tools needed for this task (up to 20)`. The
GitHub catalog has 46 active tools. The refusal gave no tool names.
`connections_search` gave `connectionId: null` for that agent. The agent
told the user that it had no GitHub access. The CEO said GitHub was
connected.
**Expected behavior**
Each agent accurately reports, and can request and use, the connector
access granted to it.
**Steps to reproduce**
1. Connect a per-user GitHub connection (more than 20 indexed tools)
through agent A's connection request.
2. Assign a task that needs GitHub to agent B, which existed before the
grant.
3. Agent B calls `connection_request` with `service: "github"`.
4. The call fails with the bare "up to 20" message, and agent B cannot
find the tool names to request.
**Deployment mode**
Paperclip cloud (staging).
**Agent adapter(s) involved**
Not adapter-specific (core bug).
**Additional context**
Root cause from the staging evidence:
- Connection `GitHub` (oauth, `credentialPolicy: per_user`, 46 active
tools). One user grant. Delegations and installs: CEO at 17:45, Frontend
Engineer at 18:18.
- Frontend Engineer run, 17:53–18:10: git push logged `GitHub
capability_missing`. Two `connection_request` calls returned the 422
above.
- The engineer found the names through `call_api` later and filed a
scoped card at 18:10. The user approved it at 18:18. After that, the
engineer had access.
- The grant scope is intentional: a per-user grant is not shared
automatically with other agents. The defect is the dead-end refusal, not
the scope.
- Repository creation after the grant (a separate onboarding report) is
not in this PR.
## What Changed
- `server/src/services/connection-intents.ts`: when a scoped access
request needs more than 20 tools, the 422 now names the eligible
connection id and lists its indexed tool names (first 100). It tells the
agent to retry with only the needed names. No access is granted.
- `server/src/services/connection-intents.ts`: a request without
`toolNames` from an agent that already has allowed tools on the
connection now reports `ready`. Before, it demanded a grant for every
remaining catalog tool.
- The guessed-tool-name path keeps its existing 20-name bound.
- `server/src/__tests__/connection-intents-service.test.ts`: a
regression test that reproduces the staging setup. The setup is a
per-user GitHub grant shared with the CEO only, 46 tools, and an
engineer outside the grant.
## Verification
Red (test added first, service unchanged):
```
pnpm --dir server exec vitest run src/__tests__/connection-intents-service.test.ts -t "hired before a GitHub grant"
FAIL connectionIntentService > lets an agent hired before a GitHub grant request the access the grant left out
AssertionError: expected 'Specify the tools needed for this tas…' to contain '6c2d6faf-e059-4578-9ac4-8a5fbd81ecfa'
Expected: "6c2d6faf-e059-4578-9ac4-8a5fbd81ecfa"
Received: "Specify the tools needed for this task (up to 20)"
Tests 1 failed | 24 skipped (25)
```
Green (after the fix; the same test):
```
Test Files 1 passed (1)
Tests 1 passed | 24 skipped (25)
```
The test checks these steps:
1. The engineer's search gives `needs_user_action`.
2. The refusal names the connection and the needed tools
(`create_repository`, `push_files`).
3. The scoped retry creates a card.
4. The user approves the card.
5. A plain `connection_request("github")` gives `ready` for that
connection.
During development, an intermediate version of the fix failed step 5 of
the same test. That failure caused the second change.
Neighbouring suites:
- `connection-intents-service.test.ts`: 25 passed.
- `connection-aggregator-fallback.test.ts`: 45 passed. This includes the
bounded guessed-name recovery tests.
- `satisfied-connection-intents.test.ts`: 52 passed. Its `afterAll` hook
times out locally after 30 s. This also occurs on unchanged master code,
so it is not caused by this change.
`tsc --noEmit` for `server` gives no errors in the changed files. The
local workspace had other errors because some workspace packages were
not built. CI runs the full typecheck.
## Risks
- Low risk. The refusal message is longer, and it lists up to 100 tool
names. It lists names only for a connection that the requesting identity
is already eligible to use (same check as before).
- Behaviour shift: an agent with partial access that asks without
`toolNames` gets `ready` and no new access card. To get more tools, the
agent must name them. This matches the tool description ("pass
connectionId and the exact required toolNames").
- No schema, API shape, or permission change. No access is granted
without human approval.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, in Claude Code with
tool use (shell, browser inspection of the staging run history, 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>
|
||
|
|
9a79384958 |
Relay bounded Runner source refusal diagnostics (#19302)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - An environment plugin can submit a Runner continuation from a retained source. > - The host can refuse that continuation when its source is unverified or has an unresolved operation. > - The worker and Core currently discard that bounded reason. > - Raw provider errors can contain credentials, text or foreign identities. > - This pull request relays only a closed diagnostic and keeps the existing admission and redaction rules. ## Linked Issues or Issue Description Refs #19134 and #19157. This independent fix is based on master. A search found no duplicate relay. **What happened?** A source submit can receive HTTP 409 with `SESSION_STATE_UNAVAILABLE`. The worker drops its structured data. Core then replaces the error with a generic message. The caller cannot distinguish the two known diagnostic reasons safely. **Expected behavior** Carry only the fixed status, code and reason through SDK worker RPC and Core. Keep all provider text and identities out of the relayed error. A diagnostic must not authorize a retry or change state. **Steps to reproduce** Submit an authorized retained source through a task provider. Have the provider throw the recognized state-unavailable error. Read the error after worker RPC and Core dispatch. ## What Changed - Export `PluginEnvironmentTaskErrorDiagnostic`, `environmentTaskErrorData` and `readEnvironmentTaskErrorDiagnostic`. - Accept only `httpStatus: 409`, `errorCode: "SESSION_STATE_UNAVAILABLE"` and reason `"unresolved_operation"` or `"source_state_unverified"`. - Attach the versioned envelope only to an environment task `submit` with `resumeFrom`. Replace its provider message with a fixed message. - Revalidate and reconstruct the closed error in Core. Keep other failures under the existing generic redaction. - Document older SDK behavior and the diagnostic-only boundary. No grant, retry, discard, lifecycle or admission rule changes. ## Verification - Rebased head `7d02bb3e4c` includes master’s UI fixture fix from #19342. The sole conflict was in the worker integration test imports. It now imports both the physical-task result schema from #19300 and the diagnostic reader and RPC error type from this PR. The 39 SDK/worker cases and 57 Core/runtime cases passed, including PostgreSQL worker integration. The SDK build and Runner/server TypeScript checks passed. [Current-head CI](https://github.com/paperclipai/paperclip/actions/runs/42111889388) passed without retries. All 56 check-rollup entries were green, including Snyk and Socket; [standard Greptile review 2](https://github.com/paperclipai/paperclip/pull/19302#issuecomment-6665823815) scored this exact head **5/5**, with zero unresolved threads. The PR was marked ready on November 5 at 21:37 UTC. - Both new worker relay cases fail against the baseline worker and pass with the fix. - 46 SDK/worker cases passed, including both reasons, malformed/foreign envelopes, extra identities, unsafe getters and non-source operations. - 51 Core/runtime cases passed. The integration uses real SDK serialization and dispatch with PostgreSQL through the existing authorized retained-source submit. It verifies both reasons, malformed RPC rejection, extra-field removal, preserved source outcome and existing dispatch bookkeeping. A valid envelope on an ordinary submit or status stays generic. - Runner and SDK builds and server TypeScript check passed. An initial Runner build lacked its eval-kernel build prerequisite; it passed after that prerequisite was built. - The first expanded DB fixture used a shared environment name and failed its unique constraint. It now uses its unique fixture ID; both complete focused files pass. - Diff and added-line secret scans passed. [GitHub workflow 42083410387](https://github.com/paperclipai/paperclip/actions/runs/42083410387) passed on its second attempt. All CI checks completed, and Snyk passed. Standard Greptile scored exact head `38559ec4df8c` 5/5 with no unresolved threads. That result applies to the previous head. - The first GitHub attempt failed in the unchanged concurrent-budget case in `decision-models.test.ts` (one provider call observed, two expected). That exact case passed locally without code or assertion changes. The workspace shard also passed 4,960 tests before the unchanged CLI company import/export fixture exceeded its 30-second child-server startup wait. No port-collision cause is claimed; that fixture deliberately reserves the requested port. Both initial logs are retained. One failed-job-only retry passed without source changes. ## Risks Older SDKs keep the generic failure. The envelope is diagnostic evidence only. Provider operation and call identities are never accepted. Specific user-visible operation reporting must use Core's retained event or receipt under its existing authority. Master does not contain the new claimed predecessor orchestration. This PR tests the existing retained-source submission boundary. It does not claim the #19157 source recovery or old-journal end-to-end qualification. Runtime pins and deployments remain separate. ## Model Used OpenAI GPT-6.1 Sol (`gpt-6.1-sol`), high reasoning. Used repository tools, TypeScript, SDK RPC and real PostgreSQL tests. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used with version and capability details - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have described the issue in-PR using the bug template - [x] I have not referenced internal or instance-local Paperclip issues or links - [x] My branch name describes the change - [x] I have run the focused tests locally and they pass - [x] I have added tests for the new behavior and rejection paths - [x] I have updated relevant documentation - [x] I have considered and documented risks - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no unresolved findings - [x] I will address reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
d3713d827f |
fix(ui): add membershipRole to the agent-creator test fixture (#19342)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The UI typecheck gate runs on every pull request > - #15183 made `membershipRole` required on `CompanyUserDirectoryEntry` > - #16055 added `ui/src/lib/agent-creator.test.ts` with a fixture that does not set `membershipRole` > - The two pull requests merged separately, so master now fails `ci / Typecheck + Release Registry` > - This failure blocks every open pull request > - This pull request adds the missing field to the test fixture > - The benefit is that the typecheck gate is green again on master and on open pull requests ## Linked Issues or Issue Description Refs #15183 Refs #16055 **Describe the bug** Master fails typecheck. The error is `src/lib/agent-creator.test.ts(6,3): error TS2741: Property 'membershipRole' is missing ... in type 'CompanyUserDirectoryEntry'`. **Steps to reproduce** Run `pnpm --filter @paperclipai/ui typecheck` on current master. Or open any new pull request and look at `ci / Typecheck + Release Registry`. **Expected behavior** Typecheck passes on master. ## What Changed - Set `membershipRole: null` in the `member()` fixture in `ui/src/lib/agent-creator.test.ts`. ## Verification - Red: CI on #19327, #19328 and #19332 fails `ci / Typecheck + Release Registry` with the TS2741 error above. - Green: after this change, `tsc --noEmit -p ui/tsconfig.json` reports no `agent-creator` error. - `npx vitest run src/lib/agent-creator.test.ts` in `ui/`: 4 tests pass. ## Risks - Low risk. The change is in a test fixture only. Production code does not change. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Claude Code with tool use. ## 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.1105.0-canary.20 |
||
|
|
c3550cfb38 |
fix(daytona): send archive chunks in bounded windows (#19339)
## Thinking Path > - Paperclip manages agents and saves their work when execution stops. > - The Daytona plugin saves an archive through a terminal when disk capacity prevents the normal archive or download copy. > - That fallback sent one 40 KiB chunk and waited for its host write acknowledgement. > - Network delay then applied to every small chunk. A large archive could exceed the recovery deadline even while bytes were moving safely. > - This pull request sends a bounded window of chunks and still acknowledges every completed host write. > - The benefit is faster recovery over delayed connections, with bounded memory and the existing size, space and completion checks. ## Linked Issues or Issue Description Refs #19034, which added the capacity fallback. **What happened?** The archive fallback waits for one network round trip after each 40 KiB chunk. A saved workspace archive can be much larger than that chunk. Repeating a timed-out transfer does not remove this throughput limit. **Expected behavior** Allow a small amount of data to travel before waiting for acknowledgements. Keep memory bounded, write bytes in order, and stop after any write or protocol failure. **Steps to reproduce** 1. Run the real Bash archive sender against a host that delays each acknowledgement by 400 ms. 2. Transfer 1 MiB plus a partial final chunk with a four-second deadline. 3. The original implementation times out. The bounded-window implementation completes and preserves the exact source bytes. **Paperclip version or commit** The failing regression uses the original implementation from `6a66c09f7f7`. ## What Changed - Send at most sixteen 40 KiB chunks before collecting their acknowledgements. Drain the final partial window before the completion receipt. - Serialize host writes and reject more than sixteen pending frames. Stop queued writes on the first failure. - Keep per-frame size checks, total archive limits, the 1 GiB host space reserve, the transfer deadline and bounded teardown. - Add a real Bash regression with delayed acknowledgements, plus overflow and failed-write coverage. Update the plugin documentation. - Update the agent-creator test fixture for the required directory membership role introduced by #15183, which otherwise breaks merged CI typechecking. ## Verification - Failing first: the final real-Bash regression fails against the original source with `Daytona archive stream timed out`. It passes with the change. The empty-stream case passes too. - Standalone Daytona package `pnpm test`: 578 passed, 8 skipped. The skips are the existing opt-in live tests. - Standalone package `pnpm typecheck` and `pnpm build`: passed. - `pnpm test src/file-sync-capacity.live.test.ts` with a staging Daytona key: 2 passed on isolated test sandboxes. This covers actual terminal transport, stop/start, archive and file-copy capacity failures, exact saved hashes, safe links, cleanup and a second export. The tests removed their own sandboxes. - The production dependency assumption is bounded: at most 640 KiB of decoded payloads can wait for host writes. The parser also has a bounded encoded buffer. No background work or idle timer is added. - Initial hosted CI failed with TS2741 in `ui/src/lib/agent-creator.test.ts` after #15183 made `membershipRole` required. Rebased onto that change and added the nullable field to the fixture. UI `pnpm typecheck`, all 4 existing creator-label tests, and `pnpm check:token-gates` now pass locally. Hosted CI subsequently passed on the updated head. - `git diff --check` and a local secret and PII scan: passed. - Full repository checks were not repeated locally for this standalone plugin change. Hosted verification at `bf6b3cb6a622a9523c72b86fdfe616555c8f3ef8`: all 53 check runs passed, with the 2 opt-in Storybook checks skipped; both required gates (`ci / verify`, `ci / e2e`) passed. Greptile is 5/5 at this exact head, with no review threads or merge conflicts. The separate non-required `security/snyk (cryppadotta)` status remains pending; it is not claimed as passed. ## Risks - A burst can queue up to sixteen chunks instead of one. The receiver enforces this bound and acknowledges a chunk only after its ordered host write. - The timeout budget is unchanged. A connection that cannot finish within that budget can still fail. This change does not retry a provider turn or change file-recovery authority. - Source confinement, archive validation, successful producer completion and publication rules are unchanged. No schema migration. ## Model Used - OpenAI `gpt-6.1-sol` with xhigh reasoning, in Codex through T3 Code. Tool use included source edits, real Bash and filesystem tests, and isolated Daytona tests. The runtime does not expose 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 (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> |
||
|
|
fb2e07ebdd |
Wait for physical authority before activating hosted warm routes (#19277)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Core can give two consecutive runs, A and B, the same hosted Runner so B can continue A’s conversation. > - During that handoff, Core can record that B is next while the Runner still answers as A. > - If Core restarts at that point and tries to connect for B, the Runner rejects the connection. B’s next turn stalls. > - This PR makes Core finish the handoff over A’s connection and check which run the Runner now reports. > - Once the Runner reports B and authenticates B’s connection, B can continue on the same Runner. ## Linked Issues or Issue Description - Refs #17390 for hosted Runner reuse. - Refs #19157 for recovering a stopped Runner session before attaching the next run. ## What Changed - If the Runner still reports A, Core uses A’s connection to acknowledge the result of `run.attach`. This tells the Runner that Core saved the handoff and that it can switch to B. - Core reads the Runner’s saved state and checks it against the recorded handoff. If the Runner already reports B for that handoff, Core connects for B directly. Core waits for B’s connection to authenticate before sending its work. This applies both during an ordinary handoff and after Core restarts. ## Verification - Run `pnpm --filter @paperclipai/paperclip-runner exec vitest run src/live/runnerd-staged-handoff.test.ts` with the native test binaries built. The regression replaces Core at four points in the handoff. Before the fix, Core tried to connect for B while the Runner still reported A. - With the fix, B and a third run each start and finish one turn. The test observes one Runner process, one controlled Codex provider process, and one conversation thread throughout. - All seven handoff cases passed. GitHub CI passed after retrying an unrelated port collision and interrupted jobs. Standard Greptile scored the commit 5/5. [CI run](https://github.com/paperclipai/paperclip/actions/runs/42070818838) ## Risks - The host must be able to read the Runner’s state. If Core cannot verify the switch before the existing deadline, it leaves the handoff pending and sends no work for B. Live hosted verification is still separate. > 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 - OpenAI GPT-6.1 Sol (`gpt-6.1-sol`), high reasoning effort, through Codex with repository tools and code execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
00c7738513 |
fix(task-chat): keep messages sendable after a declined connection request (#19331)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The task chat composer shows a queue of saved input that waits for
the agent's next turn
> - When a person declines a connection request (for example Slack), the
server queues a read-only receipt so the agent learns the outcome
> - The composer showed that receipt with its raw JSON receipt in hover
text, and the receipt hid the person's next message
> - The next message could not be edited, discarded, or sent, so the
chat looked stuck after a simple "Decline"
> - This pull request shows a readable status for receipts and keeps the
person's own later messages visible and actionable
> - The benefit is that declining a connection resolves cleanly and the
conversation continues
## Linked Issues or Issue Description
**What happened?**
During an onboarding test on staging, a person declined the agent's
"Connect Slack" request. A queued steering item then stayed above the
composer. It could not be edited. Its hover text showed raw JSON.
Sending the next message produced an error.
**Steps to reproduce**
1. Give a task to a Paperclip runner agent whose run is active or waits
on a busy workspace.
2. Let the agent request a connection, then decline it.
3. See the queued item "Rejected: Connect Slack". Hover it: the title is
the full JSON receipt.
4. Send another message. The message never appears in the queue. Its row
controls stay disabled. Send or Interrupt on the receipt queue returns
`409 This queue does not support interruption`.
**Expected behavior**
A declined connection request resolves cleanly. It shows a readable
status. The next message can be sent.
**Evidence (staging wake diagnostics for the test task)**
- 17:52:09: the decline wake for the Slack request. It stayed deferred,
was never claimed, and was skipped at 18:21.
- 17:52:10: a later retry wake for the plan-approval continuation.
- `GET /issues/:id/queued-comments` projects the oldest matching wake.
From 17:52 to 18:21 the composer showed the Slack receipt.
Refs: related to the separate plan-approval queued-message fix, which
changes the recovery sweep. This PR only touches the queue projection
and the receipt label.
## What Changed
- `server/src/services/queued-interaction-response.ts`: add
`queuedInteractionSummary()`. A receipt now has one readable line, for
example `Declined: Slack connection`, `Connected: GitHub connection`, or
`Accepted: Build the app`. The entry `source.summary` carries it. The
steering body for the agent still includes the typed JSON receipt.
- `server/src/routes/issues.ts` (`findQueuedCommentWake`): a queued
user-message wake now takes precedence over a receipt wake. A receipt
shows only when it is the only queued input. The receipt wake itself is
unchanged and is still delivered to the agent.
- `server/src/modules/wake-queue/adapters/postgres.ts`
(`findExistingDeferredWake`): skip interaction-receipt wakes when a new
message picks the deferred wake it merges into. Before, the receipt was
the oldest deferred wake, the merge was skipped, and every later message
started its own wake. Only the first message then appeared in the queue.
Receipts still never absorb user input.
- `packages/shared/src/types/issue.ts`: add the optional `summary` field
to `IssueQueuedCommentEntry.source`.
- `ui/src/components/task-chat/TaskChatQueuedMessages.tsx`: add
`queuedMessageLabel()`. The row text, the hover title, and all
aria-labels use the summary. They never use the raw steering body.
## Verification
Red/green tests were written first and run against the unchanged code.
New tests:
- `server/src/__tests__/issue-queued-comments-routes.test.ts`
- `shows a declined connection receipt as a readable status, not its
JSON`
- `keeps the next message visible and actionable after a declined
connection (running run | stopped run)`: declines Slack, posts the next
message, and asserts that the queue lists that message with
`canEdit`/`canDiscard`, that the discard works, and that the receipt
wake is still pending for the agent.
- `queues every later message together after a declined connection
(legacy run | stopped run)`: two messages after a decline both appear in
one editable queue, and the receipt wake keeps no message ids.
- `ui/src/components/task-chat/TaskChatQueuedMessages.test.tsx`
- `shows a declined connection request as readable text, never its raw
JSON receipt (BUG-08)`
**Red (unchanged code):**
```
server: × shows a declined connection receipt as a readable status, not its JSON
AssertionError: expected { kind: 'interaction', …(3) } to match object { …, summary: 'Declined: Slack connection' }
server: × keeps the next message visible and actionable after a declined connection (running run)
server: × keeps the next message visible and actionable after a declined connection (stopped run)
AssertionError: ["Declined: Slack connection"]: expected undefined to match object { canEdit: true, canDiscard: true }
(the queue lists only the receipt; the new message is missing)
Tests 3 failed | 85 skipped (88)
ui: × shows a declined connection request as readable text, never its raw JSON receipt (BUG-08)
AssertionError: expected 'Reorder queued message: Rejected: Con…' not to contain '{'
Tests 1 failed | 23 skipped (24)
```
An exploratory probe on the unchanged code also showed the send error on
this path: `POST /queued-comments/interrupt` on the receipt queue with a
running native run returns `409 {"error":"This queue does not support
interruption"}`. The exact UI text that the tester saw was not
available.
Second commit (review follow-up), unchanged `findExistingDeferredWake`:
```
server: × queues every later message together after a declined connection (legacy run)
AssertionError: expected [ Array(1) ] to deeply equal [ …(2) ]
Tests 1 failed | 1 passed | 88 skipped (90)
```
With the change: `Tests 2 passed | 88 skipped (90)`.
**Green (with this change):**
```
pnpm --filter server exec vitest run src/__tests__/issue-queued-comments-routes.test.ts
Test Files 1 passed (1) Tests 90 passed (90)
server: heartbeat-comment-wake-batching + src/modules/wake-queue
Test Files 11 passed Tests 322 passed (322)
server: retry-scheduling, wake-diagnostics, question-response-delivery, comment-wake-batching, dot-runner, responsible-user-invariant, stale-queue-invalidation, queued-comments routes
Test Files 8 passed Tests 357 passed (357)
pnpm --filter ui exec vitest run src/components/task-chat/TaskChatQueuedMessages.test.tsx src/lib/issue-queued-comment-queue.test.ts
Test Files 2 passed (2) Tests 31 passed (31)
server: question-response-delivery, connection-intents-service, satisfied-connection-intents
Test Files 3 passed Tests 199 passed (199)
```
A first full run of the route file hit a 30s `beforeAll` timeout while
embedded Postgres started on a loaded machine. One new stopped-run case
was also flaky, because the agent could dispatch the new message before
the assertion. I fixed the flake: the test now occupies the agent on
another task (`maxConcurrentRuns: 1`), which is the existing pattern in
this file. I confirmed again that this test fails without the route fix.
Typecheck: `pnpm --filter @paperclipai/shared typecheck`, `pnpm --filter
@paperclipai/server typecheck`, and `pnpm --filter @paperclipai/ui
typecheck` pass. `pnpm check:token-gates`: all gates clean. I did not
run the full `pnpm test:run` or `pnpm build` locally. CI runs them.
## Risks
- Behavior change: when a receipt and a user message wait at the same
time, the composer now shows the message queue. The receipt is no longer
shown there until it is the only queued input. The receipt wake and its
delivery do not change. The resolved card in the thread still shows the
outcome.
- The first line of the steering body for connection receipts changes
from `Rejected: Connect Slack` to `Declined: Slack connection`. The JSON
receipt for the agent does not change.
- Out of scope (unchanged on master, reproduced without any receipt):
while a native run is running, each new message gets its own queued run,
and the queue shows only the first one. Messages that join a
`scheduled_retry` run's context also do not appear in the queue
projection.
- Overlap: the plan-approval queued-message fix (BUG-03) changes the
recovery sweep, not this code. Both fixes touch the same symptom area.
They do not edit the same functions.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, browser inspection of staging, 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>
|
||
|
|
cad3f8ae09 |
fix(task-chat): keep a yielded turn's streamed reply visible (#19324)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The task chat shows each native runner turn while it streams, while it settles, and after it settles. > - During the onboarding interview, the agent streams a reply, posts a question card, and ends its turn as `yielded`. > - The live tail hides that reply when the card's pending interaction arrives. The settled turn also hides it, because a yielded turn never fills the final-response slot. > - The user sees the reply appear and then disappear. The reply is still in run history, so this is a rendering regression, not data loss. > - This pull request hides only the control-plane wait stub of a yielded turn. It keeps prose that the agent wrote for the user. > - The benefit is a stable conversation while responses stream and finish. ## Linked Issues or Issue Description **Bug description** During a hands-on onboarding test on staging, interview messages in the first-task chat appeared and then disappeared. **Steps to reproduce** 1. Start onboarding and pick "Interview me and propose a plan". 2. Watch the agent's turn. It streams a reply that describes the question card, for example "I've posted a question card on this task that asks four things...". 3. The question card appears. The reply disappears and does not come back after the run settles or after a reload. **Expected behavior** The conversation stays stable while responses stream and finish. **Related** Refs #18977. That PR changes server-side final-response selection for native standing conversations and leaves yielded waits and cards in control. This PR changes only how the task chat renders a yielded turn's authored reply. The two PRs do not overlap. **Evidence and root cause** - Persisted history is complete. The run's events contain the `agentMessage` `item.completed` with `channel: "final"` and the full reply text. The `run.result.accepted` result has `reportedWorkDisposition: "yielded"` with a `response_wake` continuation. Its summary is different from the reply. - The rendered thread does not contain that reply, also after "More messages" expands the history. - Classification: rendering/replacement, not persistence. Three gates drop the reply. `TaskChatThread` sets `suppressFinal` on the live tail when a pending interaction for that run exists. `TaskChatRunnerTurn` then clears the latched final. `paperclipRunnerFinalResponse` and the settled-turn path (`!sourceYielded`) return no final response for yielded turns. - Those gates were added on purpose so the mechanical "Waiting for <card>." prose that some providers echo does not show as an answer. This change keeps that behavior. ## What Changed - `transcript-adapter.ts`: add `isYieldedWaitStub`. A wait stub is text that echoes the yielded result summary, or a single-line "Waiting for ..." text. For a yielded turn, `paperclipRunnerFinalResponse` now returns the last authored final-channel message. It still returns nothing for a wait stub. New `yielded` and `waitSummary` options let callers state the yield from run metadata. - `TaskChatRunnerTurn.tsx`: `suppressFinal` hides only a wait stub. It no longer hides the agent's authored reply. A new `waitSummary` prop carries the accepted summary. - `TaskChatThread.tsx`: a settled yielded turn renders the authored reply once, in the last populated segment. The reply also counts as a native response for the "no user-facing response" marker and for progress de-duplication. The live tail passes the accepted summary to the runner turn. - Review round 1 (commit |
||
|
|
b929ad43de |
fix: keep dependency waits neutral and explain GitHub access failures (#19315)
## Thinking Path > - Paperclip manages agent work and shows users what can move it forward. > - Task status and tool approval cards describe different parts of that work. > - A queued dependency wait has no provider attempt to retry. > - GitHub can reject an approved action while other connection tools still work. > - These cases showed a false Retry banner and an unhelpful provider error. > - This pull request keeps dependency waits neutral and gives GitHub access failures a direct repair path. ## Linked Issues or Issue Description Related: #19268 preserves pending Retry preparation and current failure causes. **What happened?** A queued wake cancelled by the dependency gate appeared as a failed attempt with Retry. A GitHub MCP permission denial appeared as raw HTTP error text after an approved repository action. **Expected behavior** Keep the task's dependency state. Show GitHub access failures with a clear explanation and a link to the selected connection's Permissions page. Preserve actual failures, provider details, and approval gates. **Steps to reproduce** 1. Queue a wake, then leave its task dependent on unfinished work before dispatch. 2. Open the task after the dependency gate cancels that wake. 3. Separately approve a GitHub action whose MCP result contains `403 Resource not accessible by integration`. 4. Reopen the saved tool approval. ## What Changed - Recognize both queues' unstarted dependency-wait receipts in cancellation provenance and task projections. No dispatch or recovery gate changes. - Classify GitHub's specific permission error after MCP result normalization. Keep result validation, the raw diagnostic, and working tools available. - Apply the same display code to both historical GitHub error codes. Identify the provider from company-scoped connection metadata, including renamed apps, without rewriting stored decisions. - Show a token-based inline access card with **Manage GitHub access** and collapsed **Error details**. - Give agents trusted recovery steps after an approved GitHub action fails. Keep provider text in the untrusted result and prevent automatic replay. - Add regressions, a production-component Storybook case, and execution/connector documentation. ## Verification - Red before the fix: cancellation provenance, dependency-wait projection, the real queue cancellation, approved MCP failure classification, saved interaction projection, and the error card assertions failed at the expected behavior. - Green focused tests: queue cancellation, one ready-task executor, approval-before-dispatch, one failed GitHub request, connection reuse, retained provider diagnostics, saved interaction reads, task list/detail projection and redaction. - UI: 106 component tests passed. Token gates and Storybook catalog checks passed. - `node scripts/typecheck-low-memory.mjs`: passed. - Local source instance: verified a saved dependency wait stays neutral after reload; a saved GitHub error shows repair guidance, expands its original error, and opens the correct connection Permissions page. PostgreSQL and HTTP APIs were real. The GitHub denial was a controlled MCP fixture; no account or provider permission changed. - `pnpm build`: passed for all workspace packages, the Runner binary, server, CLI and UI. The server build and all server typecheck layers also passed after the review repair. - Additional safety checks passed for realistic repository arguments, active recovery, failed Retry preparation, and bounded provider diagnostics. - Review repair: 45 pure tests and 7 PostgreSQL cases passed. These cover both historical error codes, renamed apps, and company/provider/application identity boundaries. A second local browser check verified the renamed app, retained error detail, and correct Permissions destination. - Final review regressions: the dispatcher and metadata cases failed before the repair. Afterward, 25 focused cases and 59 projection/identity cases passed, including connection-key precedence. The native wait and app-only GitHub source also passed a local browser check. - The historical read adds at most one connection query per interaction read batch. It selects IDs and the provider key only and runs only for candidate permission failures. It adds no provider requests. - Full local `pnpm test:run` stopped at the chat suite's `afterAll` cleanup timeout. All 268 assertions in that group and the three prior chat groups passed. The failure is retained here; it is not counted as a full-suite pass. CI passed on `e8a692e1e2` after one workspace-startup rerun. Fresh CI is pending for the combined head. Its first workspace-a attempt timed out in the CLI company import/export test's child-server startup wait; 4,968 UI tests and 287 CLI tests passed in that job. The same shard passed on the preceding commit. The workspace shard passed on its one rerun. All other test, build and browser jobs passed. The [first failure](https://github.com/paperclipai/paperclip/actions/runs/42093823928/job/127478869941) is retained in the Actions history. Greptile reviewed the preceding head at 5/5. Fresh review is pending for the combined head. ## Risks The GitHub classifier matches the documented permission-denial phrase, not every 403. Rate limits, missing resources and other providers retain their existing error behavior. Historical normalization affects display only. A dependency cancellation needs an unstarted run and a supported queue receipt; current user gates, Retry preparation and recovery remain authoritative. No schema migration or credential changes. ## Model Used OpenAI GPT-6, through the Codex agent. The exact runtime model ID and context-window size are not exposed in this session. Used reasoning, repository tools, TypeScript, PostgreSQL, MCP fixtures and browser verification. The parallel agent-recovery commit records Anthropic Claude Opus 5.5 as co-author. ## 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 described the issue in-PR using the bug template - [x] I have not referenced internal or instance-local Paperclip issues or links - [x] My branch name describes the change - [x] I have run the focused tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation - [x] I have considered and documented risks - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open findings - [x] I will address reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2506bcdaf4 |
Admit exact stopped Runner sources under the current run lease (#19134)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Native execution keeps provider state and a durable controller journal. > - A completed process can stop before the next run attaches. > - The current run must own recovery of that exact source. > - An old controller must lose permission to change the consumed source journal. > - This pull request adds explicit source admission and protected task dispatch. > - The benefit is a narrow recovery boundary that preserves the original successful result. ## Linked Issues or Issue Description Native prerequisites #19132 and #19153 are merged into master. This PR targets master and updates the TypeScript Runner driver contract alongside Core and SDK admission. This change adds the SDK and Core admission boundary. Executor selection and fresh source preparation are a separate change. Related #17916 supplies the workspace export holds preserved here. ## What Changed - Plugins advertise `codexSourceRecoveryVersion: 1` for Codex App Server and `acpxSourceRecoveryVersion: 1` for ACPX. Hosts can qualify either, both, or neither. Core selects the matching field, because a stopped-source check for one provider cannot establish the other provider’s recovery guarantees. ACPX orchestration is tracked separately in #19196. Use the existing exact source identity and `resumeFrom` under the authenticated current run. No public recovery mode is added. Require the claimed source, current account and prepared launch at protected dispatch. - Keep the first source binding pinned on the consumer lease, including after uncertain dispatch. A newly acquired coordinator can refresh only the same source. - Claim one stopped successful source under the current run's acquired native lease. Store the token-free consumer reference in existing lease metadata. Preserve the source status and result. - Check the exact six source identity fields, encrypted journal header, company, task, agent, environment, driver, workspace, account selection, and current coordinator generation. Keep workspace export and legacy settlement holds. - Serialize claims and checkpoint writers in the same coordinator lock order. Reject stale source writes after consumption. Check the current consumer before recovered source writes. - Check source admission before and after protected worker dispatch. Keep the request and lease owned by the current run while the native identity remains the source. - Allow the same owner to prepare B after a fresh source submission, using the exact consumed-source proof instead of a generic warm-reuse flag. Recheck that proof after dispatch. - Preserve ordinary cached adoption and repeated preparation after launch. The current authenticated owner can differ from the adopted PRP identity without requesting a new source launch. Keep ordinary suspended recovery and live task attachment checks. Extract the shared source resolver to avoid a module cycle, and make ordinary selection honor the same durable source consumer. ## Verification - Current head `cac9cf188a` includes merged #19300 and the symmetric capabilities introduced in `b7e25a64b4`. The task-owner and warm-rebind fixtures now identify their physical task. All 29 SDK schema cases, 95 Core ownership and source-admission cases, 25 native Codex and ACPX recovery cases, and the binary metadata check passed on this head. The SDK and Runner builds and server typecheck passed. Both native providers reject the other provider’s capability. GitHub CI is green and [standard Greptile scored this commit 5/5](https://github.com/paperclipai/paperclip/pull/19134#issuecomment-6645574114) with no open threads. - Previous head `bda49271a4ae362a15baf3fe6d6d297e25fd2000` is directly based on master after #19132 and #19153 landed. [GitHub workflow 42072192023](https://github.com/paperclipai/paperclip/actions/runs/42072192023) passed on its first attempt. All check runs completed successfully or as intentional workflow skips; Snyk passed. Standard Greptile scored this exact head 5/5. That review had no unresolved findings. - Runner and SDK TypeScript builds and server typecheck passed. The focused SDK, task-owner, task-runtime, PostgreSQL source and authority suites passed all 161 cases. The production transport-factory getter regression and 14 native-lane selection checks also passed. - The Linux native lane executed the new ordinary warm A→B process case. It kept one Runner/provider process and completed two turns on the same thread. The eager-getter mutation fails with `native_runner_source_attachment_pending`; preserving the live getter passes. The production factory test changes the current owner after transport creation and checks that same options object. - PostgreSQL cases cover both concurrent source-writer/claim orders, exact source submission under the current caller, source-switch denial after uncertain dispatch, same-source coordinator refresh, stale same-boot generation, corrupted journal headers, competing consumers, account changes and export holds. - The protected dispatch sequence submits A, prepares B and retries B preparation. It rejects undispatched sources, changed accounts and foreign target bindings. Source status and accepted result remain unchanged. - Ordinary adopted A preparation under current B, repeated ordinary preparation after launch and ordinary suspended continuation retain their existing path. Absent or false claims preserve ordinary identity denial; malformed claims fail closed. - Earlier development attempts exposed ordinary preparation regressions and the live-getter omission; the final regressions cover those fixes. The first embedded PostgreSQL attempt needed missing library aliases repaired in ignored dependencies. Those attempts are not passing evidence. - Historical, superseded CI heads had unchanged UI fake-timer and Playwright health-response-lifetime failures. Their isolated cases and scoped retries passed. The response-lifetime cause remains unproven. No UI/test timeout was changed. The final rebased-head workflow above passed without a retry. - Whitespace and added-line secret/PII scans passed. Full workspace checks ran on GitHub. No live vendor probe or deployment was performed. ## Risks - Any driver that used the earlier Codex TypeScript option must use `codexSourceRecoveryVersion`. The new plugin description rejects the generic spelling. The Launcher’s existing Codex-specific HTTP header can map to this property when the plugin adopts the new SDK. The change requires no journal migration or ROM change. - This is an inactive admission foundation. Executor orchestration is separate in #19157. Native restore integrity and provider stopped-source/account validation must be present before executor activation. The native prerequisite uses ordinary recovery followed by attachment. The composed executor proofs are in #19157. - The source claim is durable. A later controller must recover the same consumer or keep the hold. It cannot select another source after an ambiguous launch. - Admission adds bounded database checks around protected startup calls. Busy issue locks fail closed; other lock waits have a one-second bound. - No migration, snapshot format, proactive receipt, recurring worker, or deployment is included. Full workspace checks and platform coverage run in CI. These tests do not prove vendor history durability or a hosted rollout. ## Model Used OpenAI GPT-6.1 Sol (`gpt-6.1-sol`), high reasoning, through Codex with repository tools and code execution. The runtime does not expose a context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green on the current head - [x] Standard Greptile is 5/5 with no unresolved findings on the current head - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
22281d4b3a |
feat(task-chat): approve harness-proposed plans (Codex plan mode, Claude ExitPlanMode) (#19291)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents run through the native runner. Codex and Claude Code each have their own plan mode, and each produces a proposed-plan document. > - Paperclip already has a planning work mode, an issue `plan` document, and a `request_confirmation` approval gate for plans. > - These parts were not connected. The Codex proposed-plan item was transcript text only. The server path never put Codex into plan mode. Claude was never put into plan mode, and its ExitPlanMode plan was dropped. A planning run could close its task as done with no approval. > - This pull request turns a harness's own proposed plan into the issue's Plan document and the existing approval gate, for Codex and Claude. > - The benefit is that users approve or send back the agent's real plan from the task chat. Approve resumes the agent. Request changes sends the reason back to the agent. ## Linked Issues or Issue Description Refs #19252 **Subsystem affected** Native runner provider events (Codex and ACPX Claude), the runner Codex provider (Rust), server native plan synchronization, and the task chat UI. **Problem or motivation** Codex and Claude both produce a proposed-plan document in their native plan mode. Paperclip did not use it. In live tests on the server path, a Codex planning run was not in Codex plan mode, saved its own `implementation-plan` document, and marked the task done without approval. Claude planning runs were not in Claude plan mode, and the ExitPlanMode plan text never reached Paperclip. **Proposed solution** Add a canonical `plan.proposed` event for a harness's proposed plan. Put Codex and Claude into their native plan mode for planning runs. Save a completed proposal as the issue `plan` document with the existing `request_confirmation` gate. Agents can also propose a plan on a standard task; the work mode stays standard. **Alternatives considered** Reuse `plan.updated`. Its schema is the within-turn checklist and is not the durable Plan document, so a separate event keeps the two clear. Stronger prompt text without native plan mode. Live tests showed that the model kept its own document workflow. **Roadmap alignment** This reuses the issue plan document, planning work mode and approval gate that already exist. It adds one canonical provider event. ## What Changed - `packages/paperclip-runner/protocol/schemas/*`: add the `plan.proposed` event (`paperclip.plan.proposed.v1`: `proposalId`, `status`, `markdown` up to 60k characters, `source`). Regenerate the schema bundle, validators and manifest. - `packages/paperclip-runner/src/provider-events.ts`: map the Codex `plan` item and a `<proposed_plan>` block in a completed Codex message to `plan.proposed`. Add `claudePlanProposalEvent`. - `packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs`: send Codex's native `collaborationMode: plan` on `turn/start` for a read-only (planning) Codex run. Before this change the server path never sent it. - `runner-core/src/acpx_event_payload.rs`: admit `plan.proposed` as ACPX rich display activity. - `drivers/acpx/codex-runtime-adapter.ts`: set Claude Code to native plan mode for a read-only run. Capture Claude's ExitPlanMode plan from its permission request, record it, and decline the native "Ready to code?" prompt. - `drivers/acpx/codex-acpx-driver.ts`, `cli/acpx-runtime-sidecar.ts`: emit the captured proposal as a rich event. Complete that turn instead of reporting it as cancelled. - `backends/codex-native-backend.ts`: per-harness plan-mode instructions, and a short opt-in hint for Claude on standard tasks. - `server/src/services/native-runtime/native-session-executor.ts`: synchronize a completed `plan.proposed` into the `plan` document and a `request_confirmation` in planning and standard mode. Do not change the issue status during the run (that revoked the run's lease). The run finalizer turns the pending confirmation into the review wait. - `server/src/services/heartbeat-run-summary.ts`: keep a `<proposed_plan>` block out of the posted reply. - UI: new `plan_proposal` provider family. The task chat shows a live "Writing plan" card while a proposal streams and a one-line record when it completes. Resolved plan reviews keep a receipt ("Approved Plan v1", "Requested changes to Plan v1: reason"). Plan previews skip section headings. - Storybook: `Tasks and conversations/.../Task chat/Proposed plan approval` shows the journey. - `doc/SPEC-implementation.md`: describe harness-proposed plan approval. ## Verification - `cargo test -p paperclip-runner-core --lib` (new: `read_only_codex_turns_use_native_plan_collaboration_mode`) - `cd packages/paperclip-runner && npx vitest run src/provider-events.test.ts src/drivers/acpx/codex-runtime-adapter.test.ts` - `cd server && npx vitest run src/__tests__/native-plan-proposal-sync.test.ts src/__tests__/heartbeat-run-summary.test.ts` - `cd ui && npx vitest run src/components/transcript/native-run-events.test.ts src/components/task-chat` - Live test on a local worktree instance with real harnesses: - Codex (`gpt-5.6-sol`), planning task: Codex asked one question, then produced a native plan item. The task showed Plan v1 with Approve plan / Request changes and moved to in review. Approve changed the work mode to standard. Codex then implemented the plan and its tests passed. - Codex, Request changes with a reason: Codex woke in planning mode and produced Plan v2 that includes the change. - Claude (`claude-opus-5-5`), planning task: Claude was in plan mode. ExitPlanMode became Plan v1 and the turn completed. Approve resumed Claude in standard mode, and Claude implemented the plan. - Claude, standard task that asks for a plan first: Claude stopped before code changes and asked for plan approval. The work mode stayed standard. - Storybook: open `Proposed plan approval` and check each stage. ## Risks - Codex planning runs now really use Codex plan mode on the server path. Codex can ask a clarifying question before it proposes. This matches Codex plan mode. - Claude planning runs now use Claude plan mode. A declined ExitPlanMode ends the Claude turn; the runner reports it as completed. Claude's tool row for that request records a rejection from Claude's side. - Codex does not stream the proposed plan on the server path yet, so the live card shows a placeholder until the plan completes. - A `<proposed_plan>` block in any completed Codex message on a standard task now asks for approval. This is the documented opt-in path. > 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`), via Claude Code with tool use (shell, file edits, browser). Long context. No extended thinking mode was used. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] 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> |
||
|
|
1b0f78100d |
fix(access): return membershipRole in the company user directory (#15183)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents often check a human approval. The check must confirm the role
of the approving user in the company.
> - An agent can read the company user directory. The directory removes
the membership role from the server record.
> - The admin member list requires `users:manage_permissions`. An
approval check must not need that permission.
> - This pull request returns `membershipRole` in the existing user
directory and keeps all access limits.
> - The benefit is that agents can verify an approver's role without
admin permissions.
## Linked Issues or Issue Description
**What happened?**
`GET /api/companies/{companyId}/user-directory` returns active users
without `membershipRole`. An agent that checks the authority of an
approving user cannot tell an `owner` from other roles. `/members`
requires `users:manage_permissions`.
**Expected behavior**
The directory returns the exact `membershipRole` from
`companyMemberships`. A missing role stays `null`. Reading this field
does not require admin permissions. Member management keeps its current
limits.
**Steps to reproduce**
1. Use a company with an active `owner` user and an active user without
a role.
2. Call `/user-directory` with an agent key.
3. Before this change, neither record has `membershipRole`. After this
change, the field is `owner` and `null`.
4. The same agent still cannot read `/members` or change a member.
## What Changed
- Add `membershipRole` to the select and result of
`loadCompanyUserDirectory`.
- Test the exact `owner`/`null` values, agent read access, denied admin
read and change, denied other company, and denied unauthenticated
request.
- Keep the existing `assertCompanyAccess` and the company, user type,
and active filters.
- The client type includes a nullable `membershipRole`. The management
test rejects only `users:manage_permissions` and checks the exact calls.
The first review findings are fixed in
|
||
|
|
d807887534 |
fix(ui): use one status glyph mapping for every task view (#19329)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Task status is the main signal on every task surface: sidebar rows, the task header, the status menu, lists, the inbox, pills and chat cards. > - One task state rendered different status icons in different views. A blocked task that waits on an active sub-task showed the blue "in queue" glyph in the task header. The status menu, the chat chip and the sidebar rows showed the red "Blocked" glyph for the same task. > - The cause: each view passed its own subset of the presentation fields to `StatusIcon`. Many views dropped `blockerAttention` and some dropped `externalConversationState`. The status menu rendered its current row from the bare status. The summaries API did not return `blockerAttention`, so the recent-task sidebar could not know the state. > - This pull request puts the state-to-glyph/label mapping in one resolver and makes every task view pass all of its fields. > - The benefit is that the same state uses the same icon and label everywhere. ## Linked Issues or Issue Description No public issue. Found in a hands-on onboarding test on a staging cloud workspace. **What happened?** The sidebar and the task pane/status menu showed different status icons for the same state. On the test task (blocked, waiting on an active sub-task), the task header and the status button showed the blue "in queue" glyph. The selected "Blocked" row in the status menu showed the red blocked glyph. The coordinator sidebar "Recents" and project rows also showed the red blocked glyph, and the chat "Subtask created" chip showed a red "Blocked" chip. **Expected behavior** The same underlying state uses the same icon and label everywhere. **Steps to reproduce** 1. Create a task with a sub-task. Block the parent on the sub-task, and let the sub-task's agent run (the parent is "blocked, covered"). 2. Open the parent task. The header shows the blue "in queue" glyph. 3. Open the status menu. The current "Blocked" row shows the red glyph. 4. With the coordinator sidebar on, look at the task in Recents or in its expanded project. It shows the red glyph. **Paperclip version or commit** `master` at |
||
|
|
b4cc2d30d2 |
fix(e2e): grade only started runs and bound the clarification readback (#19335)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Product E2E journeys grade agent work in the QA staging smoke plan > - On the current QA deployment, two journeys failed after the product had already done the requested work > - protected-actions counted a wake that the control plane retired before it started as a bound run, and failed its runtime binding check > - question-answer-resume waited for its saved document in a view that no longer exists. The wait lasted 27 minutes, until the case deadline, and starved the run queue > - This pull request grades only runs that started provider work, and reads the document where the UI now shows it, with a bounded wait > - The benefit is that both journeys measure the product, and a UI readback failure costs about two minutes instead of a whole worker ## Linked Issues or Issue Description **What happened?** - protected-actions: the task succeeded with two runs. A third automation wake for the same agent and task was cancelled before it started, with the reason "Agent cannot start work while completed runs await accounting". Its `startedAt` was null, its `resultJson.cancellation` had `source: "control_plane"` and `expected: true`, and its `executionRecovery.providerWorkStarted` was false. That run has no runtime binding, so `validateAgentTaskState` failed with `agent_run_binding_mismatch`. - question-answer-resume: the agent saved the requested document and the task was done by 19:32. The journey then navigated to `#document-<key>` and waited until 19:59 for an element that never appeared. With the task chat shell and Artifacts V1, documents render in the properties pane's Artifacts tab. The run reported `journey_operation_failed` and `evidence_collection_timeout`, and the 60-minute job ended before four other cases could run. **Expected behavior** - A wake retired before admission is kept as evidence but is not graded as provider work. - The saved document is read in its current view, and a UI readback failure ends quickly. **Steps to reproduce** QA staging smoke launch 3074 on app |
||
|
|
131c0c4609 |
fix(server): redact the credentialValues envelope in HTTP logs (#13742)
> **Attribution:** this pull request was written and opened by the
Paperclip agent **Arthur Dent** (Platform & Reliability Engineer,
Tharsia) from an automated run under the host GitHub credential. GitHub
records the author as `stefanriegel`; Stefan Riegel did not write it. No
other edit was made to this description.
## Thinking Path
> - Paperclip is the open source app that people use to manage AI agents
for work.
> - The server writes request details to the user journal. A middleware
module names the fields that are secret.
> - The connect endpoint accepts a `credentialValues` object. The object
maps a provider config path to a secret string.
> - One provider config path is `credentials.authorization`. The caller
chooses the key names. Therefore the key is one flat string, and it is
not a nested path.
> - The redaction path list did not cover the `credentialValues`
envelope. The recursive redactor compares keys by exact match, so
`credentials` and `authorization` in the key set did not match the flat
key `credentials.authorization`.
> - A failed connect request therefore wrote a GitHub token to the
journal in cleartext.
> - This pull request redacts the `credentialValues` envelope in both
layers.
> - The benefit is that no provider secret reaches the journal, for any
key name that a caller chooses.
## Linked Issues or Issue Description
No public issue exists. The report follows the bug report template.
**What happened?**
A `POST /api/companies/{companyId}/tools/apps/connect` request returned
HTTP 502. The server logs request bodies for non-2xx responses. The
journal line contained a GitHub fine-grained personal access token in
cleartext. The token was 93 characters long and started with
`github_pat_`. The line contained no redaction marker.
The logged body had this shape:
```
"reqBody":{"galleryKey":"github","connectionMethodKey":"mcp-key","name":"GitHub",
"credentialValues":{"credentials.authorization":"<TOKEN>"}, ... }
```
**Expected behavior**
The journal line shows a redaction marker in place of the token. No
cleartext credential reaches the journal on any response status.
**Steps to reproduce**
1. Run a Paperclip server on version 2026.916.0.
2. Send a `POST` request to
`/api/companies/{companyId}/tools/apps/connect`.
3. Put a secret under `credentialValues` with the key
`credentials.authorization`.
4. Make the request fail, so that the server returns a non-2xx status.
5. Read the journal line for the request.
Result: the secret appears in cleartext. The two redaction layers both
miss it.
**Paperclip version or commit**
2026.916.0. Reproduced against `master` at
`c65fc9e3c81c41aafe421aa90a00514b84343285`.
The two middleware files are unchanged at `master`
`0f5fafe16b069b8f0c7f17eb9080d010aa6cd513`, so the defect is present at
the current head of `master` as well.
**Deployment mode**
Self-hosted.
**Installation method**
npm package.
**Access context**
Authenticated user, company-scoped route.
**Relevant logs or output**
Token value removed. The journal line has the shape above.
## What Changed
- Add `reqBody.credentialValues` to `HTTP_LOG_REDACT_PATHS` in
`server/src/middleware/http-log-redaction.ts`. This redacts the whole
envelope at the pino serialization boundary.
- Add `credentialvalues` to `SENSITIVE_KEYS` in
`server/src/middleware/redact-sensitive.ts`. This redacts the envelope
by name in the recursive pass.
- Add a test that sends a `credentialValues` envelope through a real
pino logger with the production path list. The test asserts that the
secret is absent.
- Add a test for the flat-dotted-key shape that leaked, and for the
nested shape as a regression.
- Add a test that the envelope is redacted below the top level.
The key-set change is the durable half. The path list only covers body
shapes that someone has already seen leak. The recursive pass matches
the envelope by name for any shape.
## Verification
Run these commands:
```
npx vitest run server/src/__tests__/http-log-redaction.test.ts server/src/__tests__/redact-sensitive.test.ts
```
Against the patched source, both files pass: **72 tests passed, 0
failed**. That is 54 tests in `http-log-redaction.test.ts` and 18 tests
in `redact-sensitive.test.ts`.
The same two files were then run against the **unpatched** source, which
is the parent commit of this branch. **Four tests fail there.** Those
four are exactly the tests this pull request adds:
- `http-log-redaction.test.ts` — "defines the HTTP auth and cookie
header paths that must be redacted". The path list does not contain
`reqBody.credentialValues`.
- `http-log-redaction.test.ts` — "redacts a credentialValues envelope at
the serialization boundary". This is the leak: a real pino logger writes
the token in cleartext.
- `redact-sensitive.test.ts` — "redacts the connector credentialValues
envelope on an app-connect body".
- `redact-sensitive.test.ts` — "redacts a credentialValues envelope
nested below the top level".
The four failures show that each half of the change is load-bearing, and
that both layers failed, not one. The control run also confirms that the
68 tests which pass on the unpatched source still pass on the patched
source, so the change regresses nothing that already held.
The tests assert that the secret string is absent, and they do not pin a
redaction marker. This is deliberate: the two layers write different
markers. `fast-redact` writes `[Redacted]`. `redactSensitive` writes
`[REDACTED]`. An assertion that requires `[REDACTED]` alone fails on a
correct path-list-only fix.
## Risks
Low risk. The change adds two redaction paths. It removes no existing
path.
One behavioral shift is intentional. The recursive pass now replaces the
whole `credentialValues` subtree with one marker, instead of replacing
individual leaves. Any non-secret sibling field placed inside
`credentialValues` is also redacted. This is correct, because the
envelope holds only provider secrets by definition.
One adjacent pull request, `#10784`, adds header paths to the same array
in `http-log-redaction.ts`. The two changes touch different regions of
the array. The second one to merge needs a trivial rebase at most.
## Model Used
- Provider and model: Anthropic Claude, via the Paperclip `claude_local`
agent adapter
- Capability details: tool use and code execution in a sandboxed
workspace
- The patch was authored and verified by automated agent 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
- [ ] 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
- [ ] 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
Left unchecked on purpose: the CI and Greptile items cannot be true
before the pull request exists.
"I have run tests locally and they pass" stays unchecked as a
whole-workspace statement. The two test files named under Verification
were run locally, against both the patched and the unpatched source, and
the numbers above are from those runs. The full workspace suite has not
run.
## Related pull requests
Refs `#10784`, `#6373`, `#8841`.
Co-authored-by: Arthur Dent <arthur.dent@paperclip.local>
|
||
|
|
1c165e9f1e |
fix(ui): bound the queued-message strip so it scrolls instead of covering the conversation (#19321)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - In task chat, a user can queue messages while an agent run is
active. The queued messages show in a strip above the composer, with
Steer, Interrupt, Discard and Edit actions.
> - An onboarding test on staging reported that the steering strip
clipped and covered nearby UI.
> - The strip adds one 44px row for each queued message and has no
height limit. With several queued messages it takes most of the thread.
In the Coordinator layout the dock is absolutely positioned at the
bottom of the thread, so the strip grows up over the conversation.
> - This pull request puts the rows in a bounded list that scrolls. The
status and error lines stay below the list.
> - The benefit is that the conversation stays readable at desktop and
narrow widths, and every queued message stays reachable.
## Linked Issues or Issue Description
**What happened?**
With several queued messages, the queued/steering strip above the task
composer grew without a limit. It covered the transcript and nearby
components. At a 1024x600 window, 8 queued messages made the strip 353px
tall in a 555px thread.
**Expected behavior**
The strip stays readable and inside the available space at normal and
narrow widths. Extra queued messages scroll inside the strip.
**Steps to reproduce**
1. Open a task with an active run in the Coordinator workspace.
2. Queue 6 or more messages.
3. See that the strip covers most of the conversation.
**Paperclip version or commit**
`master` at
|
||
|
|
5d9f6c56c2 |
fix(ui): stop calling setup-draft agents "paused manually" (#19322)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The task chat shows a notice when the assigned agent is paused
> - A new agent is stored as `status: "paused"` with `pauseReason:
"setup"` while its setup runs
> - The notice treated every pause reason except budget, import and
system as a manual pause
> - So a user who just created their first agent saw "It was paused
manually." and a "Resume agent" button
> - This pull request shows the real setup state and the correct
recovery action
> - The benefit is that new users do not think that they paused their
agent by mistake
## Linked Issues or Issue Description
**What happened?**
During an onboarding test on a staging cloud workspace, the first agent
showed a "paused manually" warning right after it was created. Work
continued later. The agent activity log shows `agent.identity.created`,
several `agent.updated` events while setup ran, then
`agent.hire_created`. It shows no pause or resume event. The agent was
never paused by an operator. It was a setup draft (`pauseReason:
"setup"`).
**Steps to reproduce**
1. Create a first agent in the onboarding flow.
2. Open the task that is assigned to the agent while the agent setup is
not finished.
3. See "<agent> is paused. New runs will not start until the agent is
resumed. It was paused manually." with a "Resume agent" button.
**Expected behavior**
The notice shows that the agent is still being set up. When the draft
waits for the user, the notice links to the agent page to finish setup.
A real pause keeps a clear "Resume agent" action.
## What Changed
- `IssueAssigneePausedNotice` now handles `pauseReason: "setup"`
separately. It shows "<agent> is still being set up." with an info tone.
- While the lifecycle state is `preparing`, `verifying` or `pausing`,
the notice says that runs start automatically when setup finishes. It
shows no action.
- When the setup draft is parked (`lifecycleState: "paused"`), the
notice shows a "Finish setup" link to the agent page, where the existing
"Finish setup" button lives.
- The notice never offers "Resume agent" for a setup draft.
- `company_archived` pauses now say "It was paused because its company
is archived." They offer no resume, because restoring the company
resumes these agents.
- Only `pauseReason: "manual"` says "It was paused manually." An unknown
or null reason shows no reason sentence.
- Added tests for the setup-draft states and the company-archive pause.
## Verification
Red, on current `master` code with the new tests (`cd ui && npx vitest
run src/components/IssueChatThread.test.tsx -t
IssueAssigneePausedNotice`):
```
× shows a setup-draft agent (preparing) as still being set up, not manually paused 35ms
× shows a setup-draft agent (paused) as still being set up, not manually paused 11ms
× explains a company-archive pause instead of calling it manual 12ms
AssertionError: expected 'CEO is paused.New runs will not start…' not to contain 'paused manually'
Tests 3 failed | 5 passed | 100 skipped (108)
```
Green, same command after the fix:
```
Tests 8 passed | 100 skipped (108)
```
Neighbouring suites (`npx vitest run
src/components/IssueChatThread.test.tsx
src/components/TaskChatThread.test.tsx`):
```
Test Files 2 passed (2)
Tests 471 passed | 1 skipped (472)
```
- `node scripts/check-token-gates.mjs`: all gates clean.
- `tsc --noEmit -p ui`: no errors in the changed files. The local run
shows errors only from unbuilt workspace packages
(`@paperclipai/plugin-sdk/ui` dist and the generated storybook feature
map). CI builds these packages.
- I did not run the full `pnpm -r typecheck` / `pnpm test:run` / `pnpm
build`. CI runs them.
## Risks
- Low risk. The change is UI-only and limited to one notice component.
- A null or unknown pause reason no longer says "manually". It still
says that the agent is paused and still offers "Resume agent".
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edits, browser inspection of the staging
workspace).
## 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>
|
||
|
|
1e98aa76e3 |
chore(lockfile): refresh pnpm-lock.yaml (#19337)
## 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> |
||
|
|
2575b941d0 |
feat(task-chat): show hook and auto-review decisions as governance rows (#19266)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents run on harnesses such as Codex and Claude, and the task chat shows what each run did > - Harnesses make automatic decisions: hooks block or allow tool calls, and Codex auto-review approves or denies actions > - Today Paperclip drops these decisions or shows them as generic rows. Codex `safety.review.*` always says `decision: "unknown"`, the native Rust runner drops `hook/*` and `item/autoApprovalReview/*`, and claude-agent-acp drops Claude hook messages > - An operator cannot see why an agent changed course or which action ran without a person's approval. This conflicts with Paperclip's approval-gate model > - This pull request records each hook and review decision with its reason and shows it as a small governance row in the task chat > - The benefit is that automatic decisions become auditable, and a blocked action explains itself without opening run logs ## Linked Issues or Issue Description Refs #19252 (precedent: canonical `plan.updated` events rendered as a task-chat card). Builds on #19259 (ACP `_meta` passthrough from ACPX), now merged. **Subsystem affected** Runner provider events (Rust native Codex normalizer, TS drivers, ACPX sidecar), the claude-agent-acp patch, the PRP provider-event schema, and the task chat activity UI. **Problem or motivation** Hook and auto-review outcomes reach Paperclip but are lost or flattened. Codex reviews always record an unknown decision. Native Codex runs drop hook and review notifications in the Rust normalizer. Claude hook messages never leave the adapter. Codex reuses one `run.id` for every call of a hook, and the id contains a host path. **Proposed solution** Map the real outcome, decision, action, risk and reason into additive optional fields on `paperclip.hook.v1` and `paperclip.safety.review.v1`. Forward Claude SDK hook messages. Render small grey rows: a stopped action is tinted and shows its reason, an approved review folds into the command it approved as an "auto-approved" mark, and routine hook passes stay out of activity summaries. **Alternatives considered** Separate rows for every approval. This duplicated each approved command, so approvals fold into the command row instead. ## What Changed - Runner (Rust): new `governance_events.rs`. `normalize_codex_notification` now maps `hook/started|completed` and `item/autoApprovalReview/started|completed`. Each hook call gets its own id (a digest of the run id and start time), so no host path is stored. - Runner (TS): new `governance-events.ts` with the same mapping for the TS Codex driver, and the Claude hook mapping. The ACPX sidecar publishes Claude hook updates as schema-validated `hook.*` rich events. - claude-agent-acp patch: turns on `includeHookEvents` and forwards `hook_started` / `hook_response` on `session_info_update._meta.claudeCode.hook`, with bounded output. - Protocol: additive optional fields. Hook records get `outcome`, `message`, `source` and `toolName`, and the `blocked` and `stopped` statuses. Review records get `actionKind`, `action`, `riskLevel`, `userConsent` (the reviewer's user-authorization level; keys that end in "authorization" are redacted as secrets), `rationale` and `decidedBy`, and the `timed_out` and `aborted` decisions. Regenerated the manifest and validators. - Fixed: the hook `blocking` flag read `executionMode === "blocking"`, but Codex sends `sync` / `async`. - UI: `native-run-events` keeps the full hook and review payloads. `governance-presentation.ts` writes the row labels. `fold-auto-reviews.ts` attaches an approved review to its command row. The rows show the tint, the reason line and the "auto-approved" mark. Activity summaries lead with a block or denial. - Storybook: "Task chat / Governance rows" shows every outcome, a Codex run, a Claude run and the live line. - Docs: added a governance activity section to `packages/paperclip-runner/docs/protocol-compatibility.md`. ## Verification - `pnpm --filter @paperclipai/paperclip-runner exec vitest run src/governance-events.test.ts src/provider-events.test.ts src/drivers/codex src/drivers/acpx src/cli`. 2305 tests pass. This includes a replay of real Codex 0.160 notifications from a session with hooks and auto-review (`test-fixtures/governance/codex-0.160-hooks-auto-review.json`, sanitized). - `cargo test --release -p paperclip-runner-core`. The full suite passes, and `governance_events::tests` replays the same fixture. - `cd ui && npx vitest run`. 11,047 tests pass. - `node scripts/typecheck-low-memory.mjs`, `pnpm check:token-gates`, and the runner contract checks (`check:protocol-manifest`, `check:protocol-types`, `check:capability-contract`, `check:protocol-coverage`, replay goldens, contract fingerprints) pass. - Live test on a local isolated instance. A Codex agent and a Claude agent each used a preconfigured home with a PreToolUse hook that blocks edits to `0001_init.sql`, plus UserPromptSubmit and PostToolUse hooks. Both runs showed "PreToolUse hook blocked a tool call" (Codex) or "PreToolUse hook blocked Bash" (Claude), with the hook's message. They also showed "UserPromptSubmit hook added context", and the group summary started with the block. - Auto-review cannot run in Paperclip today, because the native Codex runner only allows `approvalPolicy: never`. The review mapping and the approval fold are proven against real Codex 0.160 app-server output (fixture above) and in Storybook. ## Risks - Low risk to execution. These records are display-only and carry no authority. All new schema fields are optional, so older records stay valid and older UIs ignore the new fields. - The `auto-approved` fold depends on `targetItemId` matching the command's execution id. Real Codex 0.160 data confirms that it matches. When there is no match, the review stays as its own row. - In Paperclip's isolated homes, hooks do not run, because the runner never copies hooks into an isolated home. Rows appear only for agents with a preconfigured home, or with future managed hooks. - The claude-agent-acp patch changes. The ACPX profile check still passes, because the patch is Paperclip's ACPX layer and not the upstream release. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use (shell, browser and file editing) 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 - [ ] 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> |
||
|
|
b86f55f0ba |
test(server): remove two more server test races (#19330)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The server Vitest shards gate every pull request > - Two more server tests failed on pull requests that do not touch them (#19309, #19265) > - #19314 fixed four other flakes. These two have different causes > - Random CI failures slow every contributor and hide real regressions > - This pull request removes both root causes without retries or skips > - The benefit is fewer false CI failures ## Linked Issues or Issue Description Refs #19309, #19265, #19314 **What happened?** - #19309, job 127459915333: `workspace-recovery.test.ts` > "keeps ancestor Retry held when current proof changes: sibling_hold" received `requestId: null` from a second Retry request. - #19265, job 127481105563: `paperclip-control-plane-port.test.ts` > "retains a durably recorded governed terminal while its postcommit observer is pending: checkpoint timeout" failed with "native session checkpoint timed out after 100ms" before the terminal was recorded. **Root causes** 1. Workspace recovery (test race). `POST /issues/:id/retry` returns 202 and then starts `heartbeat.resumeTaskRetry` without waiting. The proof-change tests change state after the receipt. When the background nudge runs first, it admits the Retry and queues a run before the change. The new queued run is then the latest run, so the second Retry in `sibling_hold` is stale. I proved this locally: a 1.5 s pause after the receipt made every gate in the family fail with a queued run in the issue history. 2. Control-plane checkpoint timeout (budget too tight). `checkpointTimeoutMs: 100` applies to every checkpoint in the session, including the normal checkpoints before the terminal. On a loaded runner, one of those took more than 100 ms. The test expects only the checkpoint that the pending observer blocks to time out. ## What Changed - `server/src/services/workspace-recovery.test.ts`: add `receiptOnlyRecoveryApp`. It builds the same route app, but the route nudge does nothing. The two proof-change families ("keeps ancestor Retry held…" and "holds a failed-preparation HTTP Retry…") use it and call `resumeTaskRetry` explicitly, as before. - `server/src/services/native-runtime/paperclip-control-plane-port.test.ts`: the checkpoint budget is 1 s. The timeout case now waits for the run to fail with the timeout while the observer is still blocked, instead of a fixed 700 ms sleep. This also proves that the timeout fires while the observer is pending. ## Verification - With the forced 1.5 s pause and the fix applied: both workspace-recovery families pass (80 passed). - `npx vitest run src/services/workspace-recovery.test.ts -t "keeps ancestor Retry held when current proof changes|holds a failed-preparation HTTP Retry when current proof changes"`: 80 passed. - `npx vitest run src/services/native-runtime/paperclip-control-plane-port.test.ts`: 94 passed, run 3 times. ## Risks - Low risk. The changes are only in tests. The route nudge is a latency hint ("Maintenance redelivers it"). Other tests still cover it. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edit, and GitHub CLI). ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] 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> |
||
|
|
be5709a25c |
fix(runner): classify incompatible saved provider profiles (#19334)
Classify deterministic OpenCode and Cursor Cloud saved-profile mismatches with the existing permanent provider-state class. Preserve saved state and stop same-run retries after the runner refuses that state. Verified 37 native-provider backend tests, durable classified-result replay, the server policy test, rustfmt, workspace typecheck and build. Complete hosted CI passed with Greptile 5/5 and no unresolved threads. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
ffd9591ab4 |
fix(recovery): preserve cancelled files after a reusable sandbox stops (#19305)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - A native run writes its task files in a remote sandbox. The task
workspace records that run as its writer until the files are saved.
> - When a user stops or reassigns a run, the cancelled-workspace
preservation worker records the unsaved files as file recovery work.
> - A warm run (`keep_running`) that is not successful releases its
reusable sandbox through stop-and-retain. That release can finish before
the worker sees a terminal coordinator.
> - The worker accepted only an active lease or a released ephemeral
lease. It ignored the released reusable lease, so it never recorded the
file recovery.
> - The workspace kept the ended writer's hold. Each successor run
waited, then failed with "The workspace still has an unfinished writer
or save operation" after the one-hour cutoff.
> - This pull request accepts a released reusable lease that has a
confirmed stop-and-retain receipt as a preservation source.
> - The benefit is that the stopped run's files become recovery work,
and the task is not stranded behind a hold that nothing settles.
## Linked Issues or Issue Description
Refs #18820 (preserves files when Stop finds no live controller). Refs
#17677 (schedules cancelled workspace preservation). Refs #19285 (parks
a workspace wait behind an ended writer).
**What happened?**
A board user reassigned a task during a warm native run. Stop cancelled
the run with `issue_reassigned`, and the Stop acknowledgement was
recorded. The executor settled the coordinator to `terminal_failure`
with no owner. About eight seconds later, terminal release stopped and
retained the reusable Daytona sandbox. The lease became `released` with
`leasePolicy: reuse_by_environment`, a `stopped` termination receipt and
a stop-and-retain receipt. The task workspace stayed `dirty` with
`writerRunId` set to the cancelled run. No file recovery was recorded.
The new assignee's runs waited as `workspace_busy` for one hour. Then
each run failed `setup_failed`, and the task became blocked.
The heartbeat run row showed `nativePhase: "observed"`. That value is
stale: the checkpoint persist wrote it during teardown. The coordinator
itself was `terminal_failure`: the run's execution projection was
`recovery_needed` with no matching recovery action.
**Expected behavior**
The preservation worker records the stopped run's prepared files as file
recovery work. The lease keeps its release status and receipts. No
second stop and no new provider turn start.
**Steps to reproduce**
1. Cancel a native run with an acknowledged run-only Stop. Its remote
workspace reference is `prepared`, and the coordinator is
`terminal_failure` with no owner.
2. Release its reusable lease through stop-and-retain (`released`,
`reuse_by_environment`, confirmed stop receipt).
3. Run the cancelled-workspace preservation worker or the startup scan.
4. On master, the run has no `workspaceRestoreRecovery`, and no recovery
action exists.
## What Changed
-
`server/src/services/native-runtime/native-failed-workspace-recovery.ts`:
add one source check, `cancelledWorkspaceSource`, for cancelled
preservation. The worker check and the startup scan both use it. It
keeps the active and ephemeral cases. It adds a released reusable lease
only when the stop-and-retain receipt is confirmed
(`hasConfirmedSandboxStopAndRetain`) and `preparedRemoteWorkspaceSource`
proves the exact remote-workspace binding. This is the same proof the
restore recorder uses. Native-sync runs have no such proof and stay
ineligible. An ineligible source causes no run write or notification.
- The restore recorder (`preserveLegacyWorkspaceRestoreSources`) already
accepts this source. It checks the confirmed receipt and the exact
prepared workspace binding, and it keeps the lease status, policy and
receipts. This pull request does not change it.
- `native-cancelled-workspace-preservation.test.ts`: add the incident
shape as a regression for the worker and the startup scan, and add
safety cases.
- `doc/execution-semantics.md`: document the stopped reusable source and
the writer hold until recovery.
## Verification
From `server`:
```sh
npx vitest run src/services/native-runtime/native-cancelled-workspace-preservation.test.ts
```
- Red before the fix: the two new regression cases failed (`expected {
nativeCancellation: … } to match object { …, workspaceRestoreRecovery: …
}`). 30 other cases passed.
- Green after the fix: 36 passed.
- Review follow-up: the cases `invalid-receipt`, `expired-lease` and
`native-sync` failed on the previous head
|
||
|
|
dd889e6a0b |
feat: show the assignee's provider usage limit in task properties (#19270)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents run on the native Runner through the Codex app-server and the Claude ACP harness > - Both harnesses report the provider account's rate-limit windows: Codex sends `account/rateLimits/updated`, and Claude sends its SDK `rate_limit_event` as `usage_update._meta["_claude/rateLimit"]` > - Paperclip dropped both signals, so an operator could not see that an agent was near or past its provider limit > - This pull request normalizes both signals into one account-scoped snapshot, keeps the latest snapshot per agent, and shows it as one "Usage limit" line in the task properties pane > - The benefit is that an operator sees "5h 78% · resets 15:40" (amber above 80%, "Limited until …" when the provider refuses work, "using credits" when a used-up window falls back to credits) next to the assignee, and can click it for every window ## Linked Issues or Issue Description Refs #19246 Refs #19259 **Is your feature request related to a problem?** Codex and Claude report provider rate-limit windows during a run. Paperclip dropped them: the Codex notification fell through the Rust normalizer with no branch, and ACPX dropped Claude's `_meta`. Operators could not see how close an agent was to its provider limit. **Describe the solution you'd like** Store the latest reported windows per agent and show one compact line in the task properties pane, with a popover that lists every window. **Describe alternatives you've considered** A full gauge in the agent list and the run header. A design review chose one property line with a details popover instead. ## What Changed - Runner (Rust, the runnerd path): new `provider_rate_limits.rs`. Codex `account/rateLimits/updated` becomes a `provider.notice.recorded` event (category `provider.rate_limits`, scope `account`). `CodexProviderState` merges sparse updates and emits a notice only when the windows change. ACPX `usage_update` with a sanitized `rateLimit` field also emits the notice. - Runner (sidecar): `acpx-runtime-sidecar.ts` forwards only `status`, `rateLimitType`, `utilization` and `resetsAt` from `_meta["_claude/rateLimit"]`. This needs the ACPX `_meta` passthrough from #19259. Until that merges, Claude emits nothing. - Runner (TypeScript in-process drivers): the same mapping in `codex-session-notifications.ts` and `codex-acpx-driver.ts` through `drivers/provider-rate-limits.ts`. - Shared: `provider-rate-limits.ts` holds the snapshot type, the parser, the merge, and `summarizeProviderRateLimits` (fullest current window, expiry by reset time, limited versus using credits). - Server: `agent-provider-rate-limits.ts` merges each notice into `agent_runtime_state.state_json.providerRateLimits`, scoped by company and agent. A session reset keeps the snapshot. No migration. - UI: `AgentRateLimitPropertyValue` adds a "Usage limit" row under the assignee in `IssueProperties`. Transcripts treat the notice as run-log only. A run status event refreshes the assignee's runtime state. - Storybook: `Tasks and conversations/Task creation and lifecycle/Usage limit property`. - Docs: `doc/run-log-events.md` describes the notice and its storage. ## Verification - `cd packages/paperclip-runner/runner && cargo test -p paperclip-runner-core --lib rate_limit` and `--test acpx_provider_events`: sparse Codex merge, dedupe, reached window, checkpoint round trip, Claude utilization and rejection, notice shape. - `cd packages/paperclip-runner && npx vitest run src/drivers/provider-rate-limits.test.ts src/drivers/codex` - `cd packages/shared && npx vitest run src/provider-rate-limits.test.ts`: summary, expiry, limited, using credits, parse and merge. - `cd server && npx vitest run src/__tests__/agent-provider-rate-limits.test.ts`: create and merge, out-of-order snapshot, company isolation, unrelated notices, session reset keeps the snapshot. - `cd ui && npx vitest run src/components/transcript src/components/issue-properties src/context/LiveUpdatesProvider` - `pnpm -r typecheck` and `pnpm check:token-gates` pass. - Live test on a local worktree instance: a native Codex agent on a ChatGPT subscription login ran a real task. The runner recorded one `provider.rate_limits` notice. The server stored it. The task properties pane showed "7d 100% · using credits" (this account's weekly window was used up and had credits, and the run succeeded). The transcript showed no extra row. - Not verified live: Claude. The local Claude CLI was not signed in, and the Claude path also needs #19259. The Rust and sidecar paths have tests. - Not run locally: the full `pnpm test:run` and `pnpm build`. CI runs them. ## Risks - Low risk. The snapshot is display data. It does not hold or start work. The existing provider-limit wait (#19246) still reads typed run failures. - Codex can send a rate-limit update often. The runner emits a notice only when the windows change, so run-log growth stays small. - With API-key auth, the harnesses report no windows, so no row appears. This is the intended result. - `CodexProviderState` gains two optional serde fields with defaults, so older checkpoints still load. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, through Claude Code with tool use (shell, file edits, browser) 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 - [ ] 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> |
||
|
|
6a66c09f7f |
test(evals): pin secure confirmation and team warning repairs (#19333)
## Thinking Path > - Paperclip manages AI agents and human approval for work. > - The chat catalog measures real user journeys through TestOps. > - The last personal-secret journey reached a valid secure-save confirmation that its observer rejected. > - The team journey reported a missing named skill with a typographic apostrophe that its observer rejected. > - A company-secret request returned body validation before the required agent authorization denial. > - This PR pins one reviewed source that addresses these boundaries without changing the requests, models, or time limits. > - New native measurements must establish the result. Existing failures remain failures. ## Linked Issues or Issue Description Refs: #19084 Refs: #19112 Refs: #19325 Refs: #19261 This is the single successor pin for the reviewed personal confirmation, team warning, and company authorization changes. It does not merge those core or full-catalog branches into master. ## What Changed - Pin immutable source `ab0f5246f765058af702b93d9b6637f2ef5b83cd`, tree `6cf28980b0ab6d1dd8e54efda3996708c53aeb98`. - Bind the exact descriptor snapshot `0b57bba133a5c15860ca77b804f7282b5314d7feb8efbfd6f11fca42cdbdee49` and matching target test. - Advance personal-secret v9 to v10 and team-install v4 to v5. Record the personal confirmation helper and its existing contract/flow inputs in source provenance. - Disclose the unmerged company-secret authorization fix from #19325. It denies scoped agent creation before body validation and gives safe human entry guidance. Board creation remains unchanged. - Retain the original query history, including the single previously approved multipart-form clarification. Preserve all 244 identities, the 196/244 gate, model, clocks, native and human bounds, SDK registry, launcher, and private evidence protocol. ## Verification - Pin-local checks: all **38** manifest, adapter, and reconcile controls passed with `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`. - Exact source qualification: the combined domain run passed 328 controls and skipped one optional canary. Its two local listener failures were sandbox EPERM setup failures. The same two controls then passed with the authorized local listener, in a 73-control run. The eight new route authorization controls also passed. These local runs do not prove native completion. - Full E2E TypeScript passed on exact source ab0 after reuse of the healthy, same-source workspace graph. Earlier missing-alias graph failures are retained. A full local server build or repository-wide suite was not run; #19325 has its own core checks. - The source SDK guard verified all **506** registered input Git blobs unchanged, registry `efa70621e9fb42aa185f7ee616a1a726aa3eccb367e67e28817cedf26ef2ade8`. No SDK build or qualification renewal was needed. - Actual anchored controller dependency check: `prepare-report-lock.mjs` returned `already-reviewed`, then pnpm **9.15.4** completed the frozen, ignore-scripts install in 17 seconds. This exercised the current upstream Claude ACP patch and reporting output `dd5934060dd06b8e731e3beeed49daf918d1a0b2a6c32d3da7815f2e7f0b3691`. It made no tracked lock or policy change. Earlier source-lock and controller-lock evidence remain separate. - Existing failed personal/team attempts remain failed. The company initial noncompletion remains uncorrected as an outcome claim; the authorization fix addresses one proven error boundary only. Team repeat/link checks were not reached in the failed measurement and remain required. No uniform-ten PASS or new native feature PASS is claimed. - Next validation is a controlled TestOps measurement after normal pin merge and fresh actual-controller proof. Any later whole-ten regression is separate. All attempts remain recorded; there is no retry-until-green policy. - Exact head `58d4a9b2d3c90be725e8877873010295122fb9c0`: required CI aggregates `ci / e2e` and `ci / verify` passed in run 42099871883. Windows passed in run 42099871391. Genuine Greptile review is 5/5 on that exact head, with zero unresolved threads. The complete rollup has 56 successes, three configured skips, and one neutral check; no pending or adverse check remains. No native measurement ran as part of these checks. ## Risks - Personal v10 accepts two logical human inputs: the original chat request and a separately verified same-human secure-save confirmation. The raw chat count remains one. This is a disclosed fixture variant, not proof from a new native journey. - The new confirmation fields do not change the trusted publisher schema or its closed enums. Existing private helper and launcher bytes remain unchanged. - Team v5 only normalizes the apostrophe in its warning predicate. Saved state, named-skill truth, repeat idempotency, links, and contradictory-success guards still apply. - The authorization fix is present only in the pinned source. #19325 remains unmerged; its effect on the unfinished native turn is unproven. ## Model Used OpenAI `gpt-6.1-sol` through Codex, with reasoning, repository tools, and local code execution. The context window size is not reported by this runtime. No model inference was used for local tests. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes: #` / `Refs: #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [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.1105.0-canary.19 |
||
|
|
aaaad177eb |
fix(e2e): follow renamed project and Artifacts V1 download controls (#19323)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Product E2E journeys drive the real UI and run in the QA staging smoke plan > - Two UI changes landed after the previous QA deployment: the Projects toolbar action is now "Create project" (#18716), and Artifacts V1 is on for every instance (#19164), so attachment rows offer download as an icon link with an `aria-label` > - The journeys still clicked "Add Project" and required a visible "Download" text, so they timed out before reaching the server > - This pull request accepts both the old and the current controls > - The benefit is that project-task-context, build-revise and stop-redirect measure the product again on current deployments ## Linked Issues or Issue Description **What happened?** On the QA deployment of |
||
|
|
31bf6e4482 |
Pin plugin Runner cleanup to its physical task (#19300)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Plugin Runner owners manage a physical task through an environment lease. > - The existing replacement path can assign a new physical task to that same lease. > - A delayed old owner can then stop the replacement or mark it stopped. > - Cleanup must keep the original physical task identity across RPC and database writes. > - This pull request pins that identity and keeps cleanup available to the new owner. ## Linked Issues or Issue Description Refs #19134 and #19157. This fix is based on master and does not enable repeated stopped-source recovery. A search found no duplicate fix. **What happened?** The maintained `createOwnedSandbox` replacement path changes `providerLeaseId` on one lease. An old task owner uses that mutable lease for a later Stop. A stopped-state response already in flight can also update the new task's flags. **Expected behavior** The old owner must address its original task. Its late cleanup writes must not alter the replacement. The replacement's own owner must still stop and complete it. **Steps to reproduce** 1. Create a plugin task owner for a stopped task. 2. Replace that task through the existing describe/startup path on the same lease. 3. Resume an old checkpoint or a held old status response. 4. Observe a Stop for the replacement or an incorrect stopped flag before this fix. ## What Changed - Require each task owner to keep the physical ID returned by its description. Reject a rebind to a different task. - Carry the ID through the existing internal task context after startup ends. Reject a changed physical task before RPC dispatch. - Match the physical task in stopped-state, completion and credential-handoff writes. - Hold the exact lease row through the retained-session database update and local checkpoint save. - Cover the maintained replacement path with real PostgreSQL tests. Preserve positive cleanup for the replacement owner. Document this boundary. ## Verification - Both replacement regressions failed on unmodified master `10a7d039c8`: delayed old-owner Stop targeted the replacement, and a held old status response marked it stopped. - Six focused files passed 160 tests: task authority, task runtime, warm tasks, execution target, credential writeback and task owner. - Four selected executor factory cases passed. They cover task-only providers and the physical task binding after the startup context ends. - Runner TypeScript, plugin SDK and adapter-utils builds passed. Server TypeScript check passed. Diff and added-line secret scans passed. - All 15 setup-probe cases passed with the actual Runner and fake provider binaries. The first local attempt lacked cargo on PATH. A sandboxed native attempt timed out in seven process cases; the unchanged run with loopback permission passed all 15. A separate initial DB integration attempt was denied a loopback listener; its permission-correct run passed. - Review found three missed constructors in the existing SDK/Core lifecycle fixture. They now use the actual describe result. All four cases in that real PostgreSQL and worker RPC file passed. - The first GitHub attempt failed in the unchanged GitHub recovery pagination case in `chat-channels.integration.test.ts`: its zero-request assertion observed a configuration GET. That exact case passed locally without code or assertion changes. The one failed-job-only retry passed; the initial failure log is retained. - [GitHub workflow 42082587576](https://github.com/paperclipai/paperclip/actions/runs/42082587576) passed on its second attempt. All CI checks completed, and Snyk passed. Standard Greptile scored exact head `fb914d98b99e` 5/5 with no unresolved threads. The branch is mergeable and ready for review. ## Risks The task context change is internal. It adds no plugin wire field, schema or Runner mode. A stale owner now gets an error and cannot clean up a replacement. A new owner can still clean up that task. The retained checkpoint write holds one existing lease row during a bounded local file write. Repeated stopped-source incarnation admission, runtime pins and deployments are outside this change. ## Model Used OpenAI GPT-6.1 Sol (`gpt-6.1-sol`), high reasoning. Used repository tools, TypeScript and real PostgreSQL tests. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used with version and capability details - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have described the issue in-PR using the bug template - [x] I have not referenced internal or instance-local Paperclip issues or links - [x] My branch name describes the change - [x] I have run the focused tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation - [x] I have considered and documented risks - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no unresolved findings - [x] I will address reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
8aa28940bf |
fix(sentry): keep a live-heartbeat native capacity retry out of Sentry (#19320)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The server sends unexpected run failures to Sentry, and it keeps known, recovered failures local > - A native Codex "model at capacity" failure gets an automatic retry. That failure must stay out of Sentry while its retry owns recovery (#18295, #19051) > - The check reads the `nativeProviderFailure` diagnostic from the failed run's `resultJson`. On the live heartbeat path, the failed-status write replaces `resultJson` with the adapter's copy, and that copy has no diagnostic > - So every live-path capacity failure with a scheduled retry still goes to Sentry. Only the reconciliation path, which the tests cover, kept the failure local > - This pull request removes the diagnostic check. The scheduled retry run is the proof > - The benefit is that recovered capacity failures no longer create Sentry noise on the normal live path ## Linked Issues or Issue Description Refs #18295 Refs #19051 **What happened?** A native Codex run failed with `native_provider_overloaded`. The committed status decision scheduled the automatic capacity retry, and the retry run existed before the report. The run failure still went to Sentry. In a staging sample, every live-path capacity failure since #19051 was reported (1 of 1). **Expected behavior** When the `native_provider_overloaded` retry run exists for the same task, the failed run does not go to Sentry. The retry reports its own outcome. **Steps to reproduce** 1. Run a native Codex agent through the live heartbeat path (`finalizeNativeRun` without `projectRunStatus`). 2. Let the provider return `serverOverloaded`. The status decision schedules the capacity retry. 3. The heartbeat writes the terminal `failed` status with the adapter's `resultJson`, and `emitTerminalAgentTaskRun` calls `reportRunFailure` with that row. 4. The row has no `nativeProviderFailure`, so `isScheduledNativeCapacityRetry` returns false and the event goes to Sentry. **Relevant logs or output** The failed run row later shows `nativeProviderFailure: codex/server_overloaded`, because a later reconciliation write restores it. The row that the heartbeat write reports does not have the field. The same overwrite also explains the missing `finalizationReasonCode` that #19051 fixed. ## What Changed - `server/src/services/run-failure-report.ts`: `isScheduledNativeCapacityRetry` no longer requires `resultJson.nativeProviderFailure`. The run must still be native and failed, carry `native_provider_overloaded`, have a native task, have no signal, and not be in setup. The same-task successor with `scheduledRetryReason: native_provider_overloaded` is still required. - The successor is enough proof. Only the committed capacity status decision creates that successor, and only `forTerminal` in `native-provider-failure.ts` produces `native_provider_overloaded` together with the typed `codex`/`server_overloaded` diagnostic. - `run-failure-report.test.ts`: a new case keeps the failure local when the row has only the adapter's `resultJson` (no diagnostic, no finalization fields) and the successor exists. The "diagnostic is missing" case now reports only when no retry run exists. The case for a non-capacity diagnostic with a capacity successor is removed, because that state cannot occur. - `doc/observability.md`: the native capacity paragraph now says that the successor is the proof and that the run's own `resultJson` is not required. ## Verification - `pnpm --filter @paperclipai/server exec vitest run src/services/__tests__/run-failure-report.test.ts`: 284/284 pass with this change. - With the `master` version of `run-failure-report.ts`, the new case fails: 1 failed, 283 passed. So the test catches the bug. - `native-provider-failure.test.ts`: 57/57 pass. - `native-provider-overload.integration.test.ts` did not load in my sandbox, because `@paperclipai/plugin-sdk` was not built. CI must run it. ## Risks - Low risk. The change only removes one condition from a Sentry filter. - These failures still report: no retry run, exhausted or held retries, other retry lanes, a successor for a different task, legacy runs, signals, timeouts, setup failures, and unreadable retry evidence. - The change does not affect run status, retries, recovery, or the `all` reporting mode. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), agentic coding with tool use (shell, repository reads, test runs). ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
62a5aa7705 |
fix(runner): forward configured bindings into OpenCode (#19319)
Forward controller-selected task bindings into OpenCode shell commands. Keep OpenCode runtime configuration isolated. Redact raw and JSON-escaped binding values while preserving the non-secret environment manifest. Fixes #19257 Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
86a9d3ec6c |
feat(task-chat): tool rows that say what ran and how it went (#19265)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The task chat shows each tool call an agent makes as a short activity row. > - Real Claude runs showed Bash rows named "Terminal" with no command and Write rows stuck at "Preparing file…". No row showed an exit code or a duration, and a tool refused by policy looked like any other failure. > - Codex already reported exit codes, durations and parsed command actions, but the UI dropped them, and every Codex read looked like a shell command. > - This pull request gives both harnesses one row grammar: a verb icon, the real subject, an exit chip and a duration. > - The benefit is that a reviewer can see what ran and how it went without opening each row. ## Linked Issues or Issue Description Refs #19259 (this branch includes its `_meta` passthrough commit; merge #19259 first or together). **Problem or motivation** Claude tool rows hide what ran: the runner keeps the first ACP title ("Terminal", "Preparing file…") and drops the refined one. `exitCode` and `durationMs` are null on every Claude tool event. The UI ignores `tool.execution.progressed`, `exitCode` and `durationMs` for every harness. **Proposed solution** Read real tool facts in the runner (Claude `_meta.claudeCode`, raw input and result text; Codex `commandActions`), carry them in the canonical tool event, and render one row grammar in the task chat. **Alternatives considered** Enable Claude's `terminal_output` client capability to get `terminal_exit`. That changes how command output arrives, and Claude's own exit code is derived from the error flag anyway, so this PR reads the error flag and the "Exit code N" text instead. ## What Changed - **Runner (TS):** `createAcpxToolEventNormalizer` keeps the latest concrete title instead of the first placeholder. It merges tool facts across the lifecycle and measures the duration. New `acpxToolDetailFromRuntimeEvent` reads the native tool name, the command or pattern, the description, the exit code and a "Permission denied" reason. Exit 0 is inferred only for Claude `Bash`, where a non-zero exit is always a failed result. - **ACPX sidecar:** reads the facts before `rawInput` is dropped and sends a bounded `toolDetail`. A file path argument becomes an attested workspace-relative location. - **runner-core (Rust):** validates `toolDetail`. `normalize_acpx_tool_call` uses the native name, exit code, duration, subject, description and blocked reason. Codex `commandExecution` gets its operation, a single-file target and a subject from `commandActions`. The shell wrapper is removed, and a single search reads as `"TODO" in src`. - **Protocol:** `toolExecution` gains the optional fields `subject`, `description` and `blockedReason`. Generated validators and the manifest are regenerated. - **UI:** - `nativeRunEventsToTranscript` handles `tool.execution.progressed` and passes a canonical `detail` on `tool_call` entries. - The task-chat row shows `$ command`, a path or a pattern, a red `exit N` chip (exit 0 only for commands) and a duration (0 is hidden). - Three or more consecutive reads fold into "Read N files a, b, c +1". - A refused tool shows "Blocked" with its reason. - **Storybook:** `Tasks/Tool rows` compares the data recorded today with the new runner data for Claude and Codex. ## Verification - `cd packages/paperclip-runner && npx vitest run src/provider-events.test.ts src/cli src/drivers/acpx` (1,957 pass) - `cd packages/paperclip-runner/runner && cargo test -p paperclip-runner-core --lib provider_events` - `cd ui && npx vitest run src/components/transcript src/components/task-chat` (973 pass) - `pnpm check:token-gates`, `pnpm --filter paperclip-runner build` (includes the protocol coverage and replay-golden checks) - Live runs on a local instance with real harnesses: - Codex (gpt-5.6-sol): the rows read `$ node --test` with `exit 1` and then `exit 0`, plus real durations. A three-file `sed` shows as one command and does not claim a single file. - Claude (claude-sonnet-5-5 through ACPX): the rows show `Bash` and `Edit` instead of "Terminal" and "Preparing file…". Commands show their real text, `exit 0` and durations, and edits show `Edited temp.js`. - Not seen live: a real Claude `Write`, a folded read run and a Claude policy denial. Unit and Rust tests cover them. ## Risks - The tool-execution schema gains only optional fields, so older events stay valid. Older runs keep rendering with what they recorded. - Commands now cross the sidecar boundary as `subject` (bounded to 4,000 characters). Codex already recorded commands as the tool name, and server redaction still applies. - The tool lifecycle now keeps the latest concrete title. A provider that retitles a call to something less useful would show the newer title. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use, code execution and browser automation. ## 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> |
||
|
|
8464a8c9ae |
fix(native): settle a bootstrap retry that lost its task lock (#19307)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - A native run that fails before any provider work gets a bounded same-run bootstrap retry. Restart recovery decides whether that retry can run. > - The retry needs the task's issue lock. A user comment or another wake can move that lock to a new run before the retry is due. > - Restart recovery then returned `issue_execution_lock_changed` without a write. The same check ran on every recovery pass and never ended. The coordinator stayed `retryable_failure`. > - The orphaned-lease sweep waits for a terminal coordinator before it preserves the workspace. So the sandbox lease stayed active, and every later run for the task was cancelled as `workspace_busy`. > - This pull request gives a lock-lost bootstrap retry the same bounded admission that a superseded remote retry already has. > - The benefit is that the retry settles, the board gets a reconciliation action, and the sandbox lease is released instead of blocking the task. ## Linked Issues or Issue Description No public issue exists. Bug description: **What happened?** On a hosted instance, a native agent run failed at startup because the provider rejected `session.open` with a rate limit. Paperclip scheduled a same-run bootstrap retry. Seconds later, a new run for the task took its issue lock, most likely woken by a user comment. From then on, every restart recovery pass for the old run returned `blocked` / `issue_execution_lock_changed` and wrote nothing. The run recorded about 1,000 recovery events over 12 hours. Its coordinator stayed `retryable_failure` with `recoveryMode: bootstrap_retry`. The scoped orphaned-lease sweep found a pending workspace export for the lease. It could not preserve the workspace, because `preserveNativeFailedWorkspace` requires `terminal_failure`. So the lease stayed `active`, and 16 later runs for the task were cancelled as `workspace_busy`. Three more runs on the same instance were stuck in the same state. A board Stop on the failed run moved the coordinator to `terminal_failure`, and the sweep then released the lease within about a minute. **Expected behavior** A bootstrap retry that cannot get its task lock back settles within a bounded number of recovery passes. The coordinator reaches `terminal_failure`, the board gets a reconciliation action, and the existing orphaned-lease sweep can preserve the workspace and release the sandbox lease. **Steps to reproduce** 1. Fail a native remote run before provider work, so its coordinator is `retryable_failure` with `recoveryMode: bootstrap_retry`. 2. Move the task's `executionRunId` to a newer run for the same task, or clear it. 3. Run `claimNativeRestartRecoveries` for the failed run. 4. Before this change, it returns `blocked` with no `retryAt`, and the coordinator stays `retryable_failure` on every pass. The new tests in `native-remote-retry-admission.test.ts` reproduce this. **Paperclip version or commit** Observed on a hosted deployment. Reproduced in tests on `master` at `10a7d039c8e`. Related: #19190 (a `session.open` model rejection no longer schedules a doomed bootstrap retry), #19131 (preserve superseded observed workspaces) and #19281 (release the leases of a cancelled legacy run). None of them changes this admission path. ## What Changed - **Restart recovery (`server/src/services/native-runtime/native-restart-recovery.ts`)**: a new condition, `lockLostBootstrapRetry`, matches a failed run whose coordinator is `retryable_failure` with `recoveryMode: bootstrap_retry` and whose issue lock is not on the run. - A newer run for the task: the existing superseded check now covers this case too. It defers with `native_retry_source_superseded` as permanent, so admission is exhausted at once. - No newer run (the lock was released): recovery defers with `issue_execution_lock_changed` through the existing `deferRemoteRetry`. That path persists a deadline, backs off and exhausts after the system recovery budget. - When admission is exhausted, the existing hold path writes `terminal_failure` / `native_restart_recovery_blocked`, keeps the provider evidence and creates the board reconciliation action. This PR adds no new terminal path. - A bootstrap retry that still holds its own lock is unchanged. The exact-checkpoint remote retry path is unchanged. - **Docs (`doc/execution-semantics.md`)**: the bootstrap retry section now states the lock-lost rule. ## Verification - New test `settles a %s bootstrap retry that lost its task lock instead of re-checking forever` in `server/src/services/native-runtime/native-remote-retry-admission.test.ts`, for the `superseded` and `released` cases. The fixture is a genuine pre-provider failure: a `session.open` rejection with no session checkpoint, no runner checkpoint row and no provider evidence. - On `master` without the fix, both cases fail: recovery returns `blocked` without a deadline, and the coordinator stays `retryable_failure`. - With the fix, `superseded` settles on the first pass, and `released` settles after `SYSTEM_RECOVERY_MAX_RETRIES` deferred passes. Each deferred pass persists its `nextAttemptAt`. Both end in `terminal_failure` with one board recovery action, and later passes open no transaction. - `npx vitest run src/services/native-runtime/native-remote-retry-admission.test.ts` (from `server/`): 92 passed. - Related suites, each run on its own with exit code 0: `native-ingress-bootstrap-retry`, `native-startup-retry`, `native-scheduled-resume-lock`, `native-restart-recovery-isolation`, `native-runner-restart-recovery`, `native-runner-restart-preparation`, `native-runner-restart-diskless`, `native-warm-transition-restart`, `native-controller-recovery`, `native-failed-workspace-recovery`, `native-session-resumption`, `native-startup-cleanup-recovery`, `heartbeat-observed-workspace-preservation`, `heartbeat/recovery`, `runtime-selection`, `remote-workspace-migration` and `native-session-executor` (1,149 tests). Two other suites each had one local failure that this change does not cause: - `heartbeat-process-recovery`: "redacts opaque environment-bound credentials from Sentry diagnostics" fails the same way on this machine without the change. - `chat-channels.integration`: "settles a shared-endpoint accepted-response backlog without retrying its own locks" failed once under full-suite load. It passes alone, with and without the change. - `pnpm run typecheck` in `server/`: passed. ## Risks - Low to medium. The change only affects failed bootstrap retries whose lock is not on the run. Before this change, such a retry could never run, so no working path is removed. - A released lock that comes back to the same run during the backoff still resumes normally, because the condition reads the current lock on each pass. - When the retry settles, the task gets a board reconciliation action. Before this change, the task was silently stuck behind `workspace_busy`. - No migration and no API change. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use (shell, code search, test runs), extended thinking, and a read-only investigation subagent for the root cause. ## 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 --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
74b11014c6 |
fix(server): stop a queued answer from blocking saved-work recovery (#19318)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - A native run can accept its result before the workspace copy-back finishes. If a server restart interrupts the copy-back, the run keeps an owner receipt for the server that stopped. > - Another server takes over only after the cloud platform proves that the old deployment stopped. After that proof, recovery checks that the task has not moved on, so old files never overwrite newer work. > - That check counted any queued run on the task as newer work. > - The user's answer to the run's question queues the assignee's next wake. That wake cannot claim while the saved run holds the task's execution lock, so it waits for the copy-back. > - Recovery waited for the wake and the wake waited for recovery. The task stayed stuck, and nothing was logged. > - This pull request ignores a later wake for the same assignee that has never started, and logs waits that the worker stops retrying. > - The benefit is that answering a question during an interrupted copy-back no longer strands the task. Every other newer-work check still applies. ## Linked Issues or Issue Description Refs #15824 (added platform-verified takeover and these binding checks) and #19111 (worker concurrency and ordering for the same copy-backs). This pull request keeps the rest of #15824's fence unchanged. **What happened?** On a hosted instance, a server restart interrupted a copy-back after the worker had written its owner receipt. The run had asked the user a question, and the user answered. The answer's wake was queued. Every later server got a valid stop proof for the old deployment and then got `binding_changed` from the shared binding check: the only failing condition was the queued answer wake. The worker treats `binding_changed` as a final wait and returns without logging. The run stayed `running` and the wake stayed `queued` for about 19 hours, across many restarts. **Expected behavior** A wake that is waiting for the saved run does not stop the saved run's recovery. Runs that could change the workspace still prevent takeover, and the publication check still stops a copy-back if such a run appears. **Steps to reproduce** 1. Accept a native result and leave a copy-back owner receipt from a stopped deployment. 2. Queue the assignee's next wake on the same task, for example by answering the run's question. 3. Let the reconciler or the workspace worker revisit the saved result with a verified stop proof. On `master` the takeover returns `binding_changed` and nothing is copied or committed. The new database tests run these steps against PostgreSQL. **Paperclip version or commit** `master` at `03eb49053fe`. **Deployment mode** Cloud-managed instances with platform stop verification. The same binding check also guards operator owner recovery, so self-hosted operator recovery had the same deadlock. ## What Changed - `server/src/services/native-runtime/native-workspace-recovery-binding.ts`: the newer-run check ignores a run when all of these hold: - It is `queued` and has never started (`startedAt` is null). - It belongs to the run's agent, and that agent is the task's assignee. - It was created after the saved run. - Its wake reason is not `source_scoped_recovery_action`. The run claim blocks such a run while the saved run holds the issue's execution lock (`lockIssueExecutionClaim` in `heartbeat/queue.ts`). Runs that have started, other agents' runs, recovery-action wakes and older queued runs still prevent takeover. - `server/src/services/native-runtime/native-workspace-finalization-work.ts`: log `runId` and `reason` when the worker stops retrying a wait (`binding_changed`, `owner_unverified`). It does not log `owner_active`. - `doc/native-workspace-finalization-recovery.md`: describe the waiting-wake rule in the shared binding section. - `server/src/services/native-runtime/native-workspace-queued-wake-recovery.test.ts` (new): the queued-wake tests, with a small fixture of their own. They are in a separate file so `native-workspace-finalizer.test.ts` stays under 1,000 lines. ## Verification - Failing first. These tests in `native-workspace-queued-wake-recovery.test.ts` fail against the original check and pass with this change: - Operator owner recovery with an answer wake queued. - Automatic takeover with the wake still queued. - Automatic takeover where the wake starts during copy-back. On `master` takeover is refused before this point; with the change, publication is stopped. - The real reconciler, from `retryable_failure`, with a stopped-deployment owner and a queued answer wake. It now copies once, commits the saved result and leaves the wake queued with its claim no longer blocked. - The fence is kept: three new cases (a requeued wake that already started, a recovery-action wake, another agent's queued run) still get `binding_changed`. They pass both before and after the change. - Changed test: "does not reopen an old result after a newer task execution exists" now inserts a started run instead of a never-started queued run of the same assignee. The queued case is now the new operator-recovery test above. - `cd server && npx vitest run src/services/native-runtime/native-workspace-queued-wake-recovery.test.ts src/services/native-runtime/native-workspace-finalizer.test.ts`: 2 files, 72 passed. With the original binding file restored, the 4 positive queued-wake tests failed and the 3 fenced cases passed. - Related suites (finalization work, platform proof, export retry, finalizer failure, recovery actions, owner-recovery routes, status reassessment, scoped deadline work): 8 files, 499 passed. - After rebasing onto `03eb49053fe`: finalizer and finalization-work suites, 97 passed. - `cd server && npx tsc --noEmit -p tsconfig.json`: pass. - Not verified on a live instance yet. The diagnosis comes from that instance's run, finalization, workspace-operation and recovery-action rows, plus a direct stop-proof request that returned a valid receipt. ## Risks - A copy-back can now finish while the assignee's wake is queued. The same-process retry path already does this, because it does not run the binding check. The takeover path now matches it for wakes that cannot start yet. - If some path starts such a wake despite the execution lock, `assertHeld` sees a running newer run and stops before publication. The window between that check and the write is the same as for the other binding conditions. - No schema migration. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`) through Claude Code, with tool use (shell, file edits, test runs, read-only database and log queries). The runtime does not expose the context window or reasoning configuration. ## 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 --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
ee72132a44 |
fix(daytona): bound one-shot exec requests by their command deadline (#19298)
Bound the Daytona one-shot executeCommand request at its command deadline plus 5 s and return an attributed timed-out result, instead of waiting for the host RPC deadline. Bound the stdin cleanup the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7f5d0793bd |
fix(daytona): record a bounded errno when a sandbox command fails (#19297)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Daytona sandbox provider copies the agent's workspace back to
the host after each turn (syncOut)
> - When the sandbox `tar` fails in the `archive_create` step, the
restore diagnostic records only `exitCode: 2` and `errorCode: "unknown"`
> - The command output is not sent across the plugin RPC boundary, so an
operator cannot tell a full disk from a permission error or a path that
is too long
> - This pull request maps the system error text at the end of a failed
sandbox command's output line to the existing allowlisted `errorCode`
> - The benefit is that the next failure records the real errno, and the
output text stays private
## Linked Issues or Issue Description
Refs #18172
Refs #18212
**What happened?**
A hosted Daytona run failed its workspace restore. The `archive_create`
step exited 2 on all three attempts. The run log recorded
`{"step":"workspace_transfer","transferStep":"archive_create","transferFailureKind":"command_failed","exitCode":2,"errorCode":"unknown"}`.
Two causes are possible. The sandbox disk was full, or a path was longer
than PATH_MAX, for example from deeply nested GitHub helper config
directories before #18212. The diagnostic cannot tell these causes
apart.
**Expected behavior**
A failed sandbox command records a bounded errno such as `ENOSPC`,
`EACCES` or `ENAMETOOLONG` when its output shows one. The output text
itself must not be recorded.
**Steps to reproduce**
1. Run `performSyncOut` against a sandbox whose `tar -c` exits 2 with
`tar: ./x: Cannot open: Permission denied`.
2. Read the environment sync diagnostic from the thrown error.
3. See that `errorCode` is `unknown`.
**Paperclip version or commit**
`master` at
|
||
|
|
7644045bc5 |
fix(server): remove a settle deadlock and three server test races (#19314)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The server Vitest shards gate every pull request > - Three open pull requests (#19289, #19267, #19266) failed server tests in code that they do not change > - Each failure had a different cause. One cause is a real product deadlock, and three causes are test races > - Random CI failures slow every contributor and hide real regressions > - This pull request removes each root cause. It does not add retries or skips > - The benefit is fewer false CI failures and one less deadlock in external-agent settlement ## Linked Issues or Issue Description Refs #19289, #19267, #19266 **What happened?** Server tests failed at random on pull requests that did not touch them: - #19289, job 127412331924: `dot-runner.test.ts` > "signed readiness, native tools and finalization through Dot local Rust" failed with `PostgresError: deadlock detected` on the run-event append (`select ... from heartbeat_runs ... for update`). - #19267, job 127427384499: `tool-access-service.test.ts` > "keeps an organization audience fail-closed when empty replacement races with member archive" received a widened grant instead of the 409 error. - #19267, job 127410158948: `heartbeat-process-recovery.test.ts` > "does not reap active adapter executions started by another heartbeat service instance" failed with "Timed out waiting for adapter execution to start". - #19266, job 127404184242: `voice-session-store.integration.test.ts` > "schedules phone reply retries at the saved rate-limit deadline without early database scans" failed with a duplicate key on `companies_issue_prefix_idx`. **Root causes** 1. Dot deadlock (product bug). `settle` in `native-broker.ts` takes the external admission guard. Then it updates the same assignment row in two or more statements. PostgreSQL re-runs the foreign-key checks when a transaction updates a row version that it wrote earlier, so the second UPDATE takes `FOR KEY SHARE` on the heartbeat run. Execution admission locks the issue and the run `FOR UPDATE` before it takes the guard. Thus settle holds the guard and waits for the run, and admission holds the run and waits for the guard. The run-event append can queue behind them as a third participant, and the database can kill it. A local reproduction with `log_lock_waits` showed the RI check: `SELECT 1 FROM ONLY heartbeat_runs x WHERE id = $1 FOR KEY SHARE OF x`, during `update dot_runner_assignments set status='settled'`. 2. Tool-access race (test bug). The empty replacement and the member cleanup both waited for the first replacement to commit. Both waiters woke at the same time and raced for the membership row. A replacement that wins that race widens access before the cleanup, and the product code documents that outcome as valid ("Whichever transaction wins is then authoritative"). The test only expected the cleanup to win. 3. Heartbeat timeout (test bug). The run start includes real runtime skill preparation for a new company. I measured 3.3 s to 5.5 s locally before the adapter starts. The test gave this step a fixed 3 s budget. 4. Voice prefix collision (test bug). Each of about 110 fixture companies used `V` plus seven random hex digits as its unique issue prefix, so two companies could get the same prefix. **Master:** These tests do not fail on recent master runs (the master workflows do not run the server shards). The failures are timing-dependent and are not specific to a branch. ## What Changed - `server/src/modules/external-agents/native-broker.ts`: `settle` writes the assignment in one UPDATE. This applies to both Dot and Muse. The update sets the same columns under the same conditions as before. - `server/src/__tests__/dot-runner.test.ts`: added "settles accept/finish under the admission guard without waiting on a run that admission holds". The test holds the run `FOR UPDATE` like admission does, then runs settle, then takes the guard. Before the fix, settle waits and PostgreSQL reports a deadlock. After the fix, settle completes. - `server/src/__tests__/tool-access-service.test.ts`: start the empty replacement after the cleanup holds the membership row. Then wait until the replacement is blocked by the cleanup. The test still proves that the replacement re-checks the member status after the cleanup and fails closed. - `server/src/__tests__/heartbeat-process-recovery.test.ts`: wait for the adapter to start. Fail at once if the run settles before the adapter starts. The test timeout is now 30 s, because the start (3 s to 6 s) and the existing 5 s settle wait came close to the 15 s default. - `server/src/__tests__/voice-session-store.integration.test.ts`: each fixture prefix includes a per-run sequence, so prefixes in one run are always distinct and stay at 10 characters. ## Verification - Red/green for the deadlock: with the `native-broker.ts` change removed, both new tests fail with `deadlock detected`. With the change, both pass. - `npx vitest run src/__tests__/dot-runner.test.ts` (server): 57 passed. - Before the fix, I ran the Dot Rust test 8 times with `deadlock_timeout=50ms` and got the deadlock in about 1 of 8 runs. Two further runs of 16 iterations each passed. - `npx vitest run src/__tests__/tool-access-service.test.ts -t "fail-closed when empty replacement races"`: 3 passed, run 4 times. - `npx vitest run src/__tests__/heartbeat-process-recovery.test.ts -t "does not reap active adapter executions"`: this failed locally before the change (start took 3.3 s to 5.5 s). It passes after the change. - `npx vitest run src/__tests__/voice-session-store.integration.test.ts`: 132 passed. - `npx vitest run src/__tests__/muse-control.test.ts`: 256 passed. This file covers the Muse settle path. - Not run locally: the full `pnpm -r typecheck` / `pnpm test:run` / `pnpm build`. CI runs them. A server `tsc --noEmit` shows no errors in the changed lines. The existing errors come from the unbuilt runner package in this worktree. ## Risks - Low risk. The settle change keeps the same column writes and conditions, now in one statement. It removes a lock wait. It does not add a lock. - The other changes are only in tests. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edit, and GitHub CLI). ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] 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> |
||
|
|
55389554c2 |
fix(runner): paginate workspace discovery before journal persistence (#19311)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Native agents discover reusable workspaces through `list_workspaces`. > - The tool returned every authorized workspace in one result. > - A large inventory exceeded the durable command journal limit. The result stayed pending, and session cleanup blocked the next turn. > - This pull request limits discovery in SQL and returns explicit continuation metadata. > - Agents can inspect every authorized workspace without sending the full inventory in one command. ## Linked Issues or Issue Description Related: #15749 introduced the task workspace tools. No duplicate pagination fix was found. **What happened?** A native workspace discovery call returned a multi-megabyte inventory. Persisting its result failed with `command_payload_too_large`. The preceding answer could still be saved, but cleanup retained the unsettled tool call. A later turn failed before provider work with `native_session_cleanup_quarantined`. **Expected behavior** Workspace discovery must return a manageable page. Agents must be able to request the remaining authorized rows. Existing visibility checks and journal integrity checks must stay effective. **Steps to reproduce** 1. Create 2,000 task-selectable workspaces with ordinary repository paths. 2. Call `list_workspaces` from an admitted native run. 3. On the old code, the real API regression returns 869,781 bytes. The command journal allows about 510 KiB. 4. On the repaired code, follow `nextCursor` to inspect all rows. Pages contain at most 100 rows and fit within 128 KiB. **Paperclip version or commit** Reproduced on source commit `7351a9d74f` before the repair. **Deployment mode** Isolated source checkout with real PostgreSQL and authenticated native agent APIs. Hosted deployment and incident recovery remain separate. ## What Changed - Validate workspace list limits and UUID cursors with a JSON Schema pattern supported by strict validators. Return `workspaces`, `limit`, `returnedCount`, `hasMore`, and `nextCursor`. - Apply company, visibility, lifecycle, and cursor conditions before the SQL limit. Order pages by workspace ID. Fetch one extra row to detect continuation without a count query. - Fit encoded pages within 128 KiB, including JSON escaping and Unicode. If one summary cannot fit, return explicit string previews with `truncatedFields`. The authorized detail API retains its full record. - Keep existing unpaged summary callers on their array response and recent-use order. - Update the native tool descriptor, generated contracts, catalog fixture, and execution documentation. - Add regression coverage for large inventories, complete pagination, stable pages, private and foreign rows, invalid inputs, and legacy summary behavior. Add an opt-in two-turn live Codex acceptance check. ## Verification - Red before repair: `task-workspace-api.test.ts -t "pages a large authorized workspace inventory"` failed because the result was 869,781 bytes instead of below 128 KiB. The transport regression also failed at 2,284,671 bytes. - Green after repair and review changes: 120 tests passed across the workspace API, transport, routes, native authority, coordinator, historical-question, and Codex vertical-slice suites. They cover complete discovery, byte-sized pages, oversized individual summaries, full detail retrieval, private/foreign/lifecycle exclusions, and strict schema compatibility. - Journal safety: two focused durable control-plane tests passed. Oversized result rejection and lossless valid result delivery remain effective. - Tool catalog and reconciliation: 30 tests passed. - `pnpm -r typecheck`: passed. - `pnpm build`: passed again on final commit `bc10880da6`. Server typecheck also passed after the review changes. The original affected suites passed on the rebased first commit (31 tests), and the expanded review regression set passed afterward (120 tests). - Real provider acceptance passed again on final commit `bc10880da6`: local macOS arm64, rebuilt native Runner, `gpt-6.1-sol`, high effort, per-turn lifecycle, and 2,000 persisted workspaces. Both turns succeeded. Persisted call inputs and results prove that the second page uses the first result’s cursor, both calls succeed, their IDs do not overlap, and the encoded results stay within 128 KiB. The second turn kept the provider conversation and file. Both task locks were clear after completion, and no recovered execution failure was recorded. Duration: 166.92 seconds. - Full local `pnpm test:run` stopped in the chat shard: 330 tests passed and one Slack registration test failed with `socket hang up`. That exact test passed in isolation without changes, and the final-head CI chat shards passed. A broader local managed-Slack follow-up produced no result after about eight minutes and was interrupted; it is not counted as a pass. The original full-suite failure remains recorded. - The first CI head failed native tool-schema compilation because strict validators did not recognize `format: uuid`. The revised descriptor uses a UUID pattern. The previously failing coordinator, native vertical-slice, authority, and history suites now pass locally. The stronger live cursor-receipt acceptance check passed. Final-head CI completed on `bc10880da6`: 57 checks passed and five optional checks were skipped, with no failures or pending checks. Greptile reviewed that commit at 5/5; both prior findings are resolved. - Dependency cost: paged discovery runs the existing visibility-scoped summary query with an ID cursor and at most 101 returned rows. It adds no count query, filesystem scan, provider call, or background worker. ## Risks - Native `list_workspaces` now returns a page object instead of an array. Individual summaries can be explicit previews when a single record exceeds the byte budget. Its advertised descriptor and generated contracts change together. Agents must follow `nextCursor` until null for a complete inventory. - Each page rechecks current visibility. Concurrent insertions, deletions, or access changes require a fresh scan when a complete point-in-time inventory is needed. - No migration or journal-limit change. An existing cleanup quarantine still requires normal recovery after deployment; this PR does not bypass it. ## Model Used OpenAI Codex, GPT-6 family, with reasoning, shell tools, code editing, and test execution. The exact served model revision and context window are not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
a6f786479e |
Runner: verify provider eligibility before parking (#19133)
## Thinking Path > - Paperclip coordinates AI agents and their execution. > - Hosted Runners can retain a provider process between turns. > - The host must prove that process is idle before allowing its VM to sleep. > - A completed turn alone does not rule out an active goal or trailing provider work. > - This PR adds a conservative park-readiness hook for the existing Codex process. > - The later host-control consumer can use this proof with its identity and sequence fences. ## Linked Issues or Issue Description **Current behavior** There is no provider eligibility hook for host parking. The earlier implementation in #19060 also missed an active goal that arrived during its tail drain. **Proposed behavior** Default every executor to ineligible. Permit the surviving native Codex process only when its completed-turn authority still matches and it has no live or unsettled work. **Reason and benefit** Keep active goals and unresolved provider obligations awake. Review the provider proof separately from socket handling and the native park/reclaim loop. ## What Changed - Add `host_park_ready` with a default false result. - Refresh the configured credential observer before checking pending delivery. Refuse parking during credential delivery, without selecting or restoring a provider. - Verify the current Codex process and completed-turn identity; reject startup, active turns/goals/tools, and unsettled event queues. - Require completion actually observed by that process. Accepted-finish reconciliation still preserves ordinary result authority without qualifying a restored process to park. - Refuse known helper lineage, whose diagnostics do not prove that helper work has finished. - Drain bounded trailing output through the existing accounting path. Retain goal updates and clears in durable state and normal event delivery, then recheck live goal state. - Add process tests for normal eligibility and the late-goal race. Assert that a restored process is ineligible until it completes an authoritative turn in its new generation. This PR is based on master, which contains the merged Codex (#19132) and ACPX (#19153) recovery foundations. The private socket codec in #19129 is merged; #19140 consumes this proof in the shared native loop. This PR does not advertise parking support. A separate ACPX readiness change must prove its live sidecar/session has no active or unsettled work. Both Codex App Server and qualified ACPX are required in the remaining park/reclaim, Core restart, eviction, and live sleep/wake acceptance phases. ## Verification - `cargo test --offline --locked --manifest-path packages/paperclip-runner/runner/Cargo.toml -p paperclip-runner-core --test codex_provider hosted_park` — 5 passed. - `ordinary_recovery_restores_a_completed_idle_session` passes and asserts that the genuinely restored provider cannot park before it completes an authoritative turn in its new generation. The fixture proves two provider starts and only one admitted turn. - The initial live-goal recheck mutation failed as expected. Review then identified that goal changes also needed durable retention. The new activation and clear cases verify saved goal state, retained events, and eligibility after delivery. - Existing goal, active-goal attachment, warm-tail, and late-accounting process regressions passed after sharing the goal event path. - The initial late-goal fixture timed out because it omitted `--durable-turn-ids`, which enables its existing first-turn gate. Added that fixture switch; both park tests then passed. - The credential-observation regression fails before the fix and passes afterward. Three focused credential cases prove durable replay and acknowledgement, fresh ACPX startup, and readiness without provider selection. The three provider parking checks also pass after the fix. - Full regression testing is left to GitHub CI. - Changed lines scanned for secrets, personal data, and private deployment references before push. Current head `28d41b083f58ee93db4305b1217719796bf3171d` adds one scoped review-fix commit to readiness `e8ba82374e0f72c82ac3195d4b1b3798c9072bab`, based on master `3d0d178ff7051aa44e07cd873fd4281357e0d815` without duplicating either recovery foundation. The PR remains eight parking-specific files. Review identified quiet helper work and reconstructed accepted-finish authority that could incorrectly qualify. The provider now records its actually observed terminal authority separately in memory, and parking refuses known helper lineage in both the saved state and the live inventory after draining. No durable field, lifecycle, public API, or ordinary recovery policy changed. Both process regressions fail when the parking guards are removed and pass when restored. Seventeen focused checks pass, including ordinary recovery, helper requests, receipt replay, credentials, and ACPX ineligibility. Rustfmt, diff and added-line scans passed. All exact-head CI is green: [PR run](https://github.com/paperclipai/paperclip/actions/runs/42087773928), [Runner Windows](https://github.com/paperclipai/paperclip/actions/runs/42087773720), and [Docker Runner](https://github.com/paperclipai/paperclip/actions/runs/42087773712) passed; Storybook was skipped. Standard review 15 scored 5/5 before the authorized follow-up [Apex review](https://github.com/paperclipai/paperclip/runs/127471966630). Apex review 16 also scored [5/5 on this exact head](https://github.com/paperclipai/paperclip/pull/19133#issuecomment-6645371152), with zero new comments and zero unresolved threads. On this current head, the first server shard 11/12 [job 127458190155](https://github.com/paperclipai/paperclip/actions/runs/42087773928/job/127458190155) failed the unchanged decision-model concurrency test: after releasing the database lock, its `waitFor` observed one provider call instead of two. The identical file passed all 37 tests on the prior reviewed head. One targeted unchanged-head retry [job 127466020677](https://github.com/paperclipai/paperclip/actions/runs/42087773928/job/127466020677) passed all 1,779 tests, including those 37 decision-model cases; 13 tests were skipped. No code, test timeout, or CI configuration changed between attempts. The original failure's cause remains unestablished. On prior reviewed head `e8ba82374e0f72c82ac3195d4b1b3798c9072bab`, the first `General tests (workspaces-c)` job [127389771745](https://github.com/paperclipai/paperclip/actions/runs/42068371976/job/127389771745) failed the unchanged database SIGTERM test at `packages/db/src/test-postgres-command.test.ts:44`: `JSON.parse` read empty readiness-file content immediately after an existence check. The fixture writes directly to that path, leaving a create-before-content race. One targeted unchanged-head retry [127398676711](https://github.com/paperclipai/paperclip/actions/runs/42068371976/job/127398676711) passed all **313 tests in 72 files**, including SIGTERM and SIGINT. No source or timeout changed between attempts. On the superseded `ca088abd552b` head, one Cloud sign-in recovery case returned to the expected task URL but missed its heading within the five-second assertion window; the other 40 cases passed. One targeted unchanged-head retry (job 127136265280) passed all **41 cases**. No test timeout or application code changed between attempts, and the first failure's cause remains unestablished. The superseded c7 parent failed eight retained-cleanup settlement cases because prepared restoration started a provider during a drain-only epoch. The corrected parent preserves ordinary prepared behavior; the new completed-source path excludes prepared providers. The parent reports all 19 settlement rows passing. This PR adds no prepared-session proof or restoration policy. ## Risks Known helpers remain conservatively ineligible even after their progress diagnostics: this slice has no authoritative helper-completion ledger. The eligibility check drains the existing bounded provider tail and can persist late accounting. That accounting must settle before parking succeeds. This hook alone does not prove VM suspension or vendor goal behavior across a process restart. Live sleep/wake qualification remains a later rollout check. ## Model Used OpenAI GPT-6.1 Sol (`gpt-6.1-sol`), High reasoning, through Codex with tool use and code execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
377e98f1a2 |
test(runner): keep the production close budget for recovered prepared-descriptor closes (#19309)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Paperclip Runner closes a native session only after it proves
that the provider drained and the runner suspended
> - The test "runs an environment-prepared descriptor without Core
filesystem preparation (rotated-recovery)" closes recovered transports
with a 5s close grace
> - A 5s grace leaves only 2.5s for the provider drain, and a recovered
successor needs several drain round trips
> - On a loaded CI runner with the debug runnerd, the drain does not
finish in 2.5s, so close correctly fails closed and the test fails
> - This pull request makes those closes use the production close budget
> - The benefit is that the Runner Vitest lane stops failing on
unrelated PRs, and the close assertions stay the same
## Linked Issues or Issue Description
No issue exists. The problem, in the bug report format:
**What happened?**
The Runner Vitest lane failed on PR #19265 (run 42075789869, job "Verify
Paperclip Runner (vitest package)"). The change in that PR is not
related to the Runner:
```
FAIL src/live/runnerd-codex-transport.test.ts > runs an environment-prepared descriptor without Core filesystem preparation (rotated-recovery)
NativeSessionCloseUnrecoverableError: provider_transport_failed: runner did not durably suspend before checkpoint
❯ src/live/runnerd-codex-transport.test.ts:2768:21
Serialized Error: { settlement: { runnerSuspended: true, providerDrained: false, suspensionState: null, ... finalProviderState: null } }
```
**Expected behavior**
The test fails only when recovered close behavior is wrong. It does not
fail because the CI machine is slow.
**Steps to reproduce**
1. In the rotated loop of the test, set the close grace to 700ms instead
of 5_000.
2. Run `npx vitest run src/live/runnerd-codex-transport.test.ts -t
rotated-recovery`.
3. The second (recovered) close fails with the same error and the same
settlement (`runnerSuspended: true`, `providerDrained: false`,
`suspensionState: null`).
Root cause: `runnerCloseDeadlines` keeps half of the grace (max 2.5s)
for suspension. The rest is the preparation budget for the provider
drain. With `closeGraceMs: 5_000`, the drain gets 2.5s. In a local
measurement, the recovered close needed 4 `runner.drain` commands and
about 440ms on an idle machine. Each drain is a full runner round trip,
because a drain receipt is `true` only after the controller ACKs the
prefix that the previous drain moved. On a loaded CI runner with the
debug runnerd binary, those round trips use more than 2.5s. The
transport then fails closed, which is correct product behavior.
Production uses a 10s default grace (7.5s for preparation, with the
drain capped at 5s).
**Paperclip version or commit**
`master` at
canary/v2026.1105.0-canary.18
|