Commit Graph
65 Commits
Author SHA1 Message Date
DottaandClaude Opus 5.5 15ee6a25a6 fix(native): re-bootstrap a claimed Daytona run killed before runner launch (#19616)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Native runs on Daytona start a `paperclip-runnerd` process in a
sandbox. Restart recovery decides what to do with each live run after
the control plane restarts.
> - #19583 lets recovery start the same-run bootstrap again when the
Postgres controller checkpoint proves that the runner received only
bootstrap commands.
> - A live replay found an earlier window. The control plane died after
the executor claimed the run and staged the sandbox workspace, but
before it asked for a runner process. No runner checkpoint existed, so
#19583 did not apply.
> - Recovery classified that run as `ambiguous_remote_recovery_evidence`
and blocked the task. The task also showed "Workspace repair required",
which did not describe the real cause.
> - This pull request proves the "nothing reached a runner" case from
durable evidence and starts the bootstrap again. It also names the
restart in the settled hold note.
> - The benefit is that a restart at any point of Daytona startup before
runner launch continues automatically.

## Linked Issues or Issue Description

**What happened?**

A chaos replay killed the control plane about 2 s after a claimed ACPX
Claude run started on Daytona. The kill came after `stage.workspace` and
environment acquisition, before `native.process_start_requested`. The
restarted server took over because the previous controller process was
dead. It then returned `ambiguous_remote_recovery_evidence` with
disposition `blocked`. The task rested blocked as "Workspace repair
required".

Evidence in the saved database at that point: coordinator `attempt: 1`,
`controllerGeneration: 1`, previous controller identity recorded and
dead. Native workspace sync was `prepared`. There were no runner
checkpoint rows, no session checkpoint, no process PID or start time, no
provider session, and no execution-start or runner-sourced event. The
local session root was an empty directory.

**Expected behavior**

When durable evidence proves that no runner process was requested and no
controller checkpoint exists, recovery continues the run with a fresh
bootstrap. Any evidence that a runner may have started keeps the hold.

**Steps to reproduce**

1. Start a native run on a Daytona environment.
2. Kill the control plane after workspace staging and before the
executor requests the runner process.
3. Restart the control plane.
4. See that restart recovery blocks the task with
`ambiguous_remote_recovery_evidence`.

Root cause:
`server/src/services/native-runtime/native-restart-recovery.ts`,
function `classifyNativeRunnerRecoveryEvidence`, remote branch. The only
no-checkpoint remote rule required an unclaimed coordinator (`attempt:
0`, no controller identity). A claimed run with no launch evidence fell
through to "ambiguous".

Refs #19583, #16349

## What Changed

- `native-restart-recovery.ts`: add the `remote_runner_never_launched`
proof, which leads to `bootstrap_incomplete`. It applies only to a
classic sandbox run with a prepared workspace and the preparation
fencing version. The run must have a real executor claim (controller
identity and lease owner) whose controller is gone (takeover allowed).
It needs no durable checkpoint, no session checkpoint, no process
fields, no provider session, and no provider evidence. The run must also
have no `native.process_start_requested`, process identity,
`adapter.invoke`, `tool.execution.started`, or runner-sourced event. The
same three-attempt budget applies. The shared event query moved into
`hasNativeExecutionStartEvidence`.
- `native-session-executor.ts`:
`restoreRemoteBootstrapCheckpointForRecovery` removes an empty session
root that preparation created. Without that step, the fresh bootstrap
read it as `durable_identity_unreadable`.
- `execution-recovery-resolution.ts`: the new
`executionRecoverySettlementNote` names a settled
`native_restart_recovery_blocked` hold as a stop after a server restart.
It no longer shows "Workspace repair required" because a later workspace
export was retried. Other causes keep their notes.
- `doc/run-log-events.md`: document the new reason.

## Verification

- Red, then green: `cd server && npx vitest run
src/services/native-runtime/native-runner-restart-recovery.integration.test.ts
-t "unlaunched|never requested|Postgres checkpoint"`. Before the fix,
the proven case (lease live with a dead owner, and lease expired) and
the budget case returned `blocked` /
`ambiguous_remote_recovery_evidence`. The empty-root executor case
failed.
- Fail-closed cases: these keep the hold:
`native.process_start_requested`, a process identity event,
`adapter.invoke`, a runner-sourced event, a recorded PID, a recorded
provider session, and a spent budget. A live claiming controller returns
`awaiting_evidence` / `controller_still_alive`. The existing
"contradictory evidence" cases (`claimed` with no controller identity,
`launch`, `process`, and others) stay blocked.
- Wording: `server/src/services/execution-recovery-resolution.test.ts`.
- Regression set (10 files, including `workspace-recovery`,
`native-safe-replacement`, `heartbeat/recovery`,
`native-session-executor`, and `native-restart-recovery`): 2035 passed,
14 skipped.
- Live Daytona replay of the failing chaos task, 3 runs, sequential,
ACPX Claude, double SIGKILL, current master plus this change. Image:
content-matched `paperclip-daytona-runner@sha256:5a29a797…` built from
c6eecac1e0, with runner inputs pinned to it, because the image-only
build sat in the Actions queue. Result: **3/3 PASS** (R1 pass; R2, R4,
R5, R6 and T pass). Each run's faults landed after the runner launched.
Recovery waited on the live remote controller lease, then continued
without a Retry. Run 3 continued through `reattach_remote_runner`. The
saved logs for runs 1 and 2 do not name their claim kind. The earlier
pre-launch window (the subject of this PR) is not hit reliably by replay
timing. The integration tests cover that window with the same evidence
shape as the saved failing database.

## Risks

- The proof depends on the executor writing
`native.process_start_requested` before a backend may spawn a runner
(`onSessionAdmission` in `executeNativeSession`). A future change that
spawns a runner earlier would weaken this proof. The fail-closed tests
pin the event as the gate.
- A fresh bootstrap acquires a new sandbox. The staged sandbox from the
dead attempt keeps its lease until run settlement releases all run
leases. It never ran a runner.
- The note change affects only settled holds with the cause
`native_restart_recovery_blocked`.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edits, GitHub CLI) 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>
2026-11-06 17:17:14 -06:00
DottaandClaude Opus 5.5 d728be8fbf fix(native): re-bootstrap a Daytona runner restarted before session.open (#19583)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Native runs on Daytona start a `paperclip-runnerd` process in a
sandbox. The local control plane keeps the runner's controller state in
an encrypted Postgres checkpoint.
> - When the control plane restarts, restart recovery classifies each
live native run and decides to reattach, resume, bootstrap again, or
hold the task.
> - A restart chaos wave killed the control plane twice while a Daytona
runner was connecting. The run was claimed and the sandbox runner was
launched, but `session.open` never completed. No provider evidence
existed.
> - Recovery waited correctly for the old 60 s controller lease to
expire. Then the classifier had no rule for a claimed remote bootstrap.
It returned `ambiguous_remote_recovery_evidence` and blocked the task as
"Workspace repair required".
> - The controller writes each command to the Postgres checkpoint before
it sends it. After the lease expires, that checkpoint is the complete
record of what reached the sandbox runner.
> - This pull request lets recovery prove from that checkpoint that no
provider work exists, and then start the same-run bootstrap again.
> - The benefit is that a restart during Daytona runner startup
continues automatically instead of stopping the task.

## Linked Issues or Issue Description

**What happened?**

A restart during Daytona remote runner startup (native ACPX Claude, two
SIGKILLs about 20 s apart) left the task blocked. The run history showed
`remote_controller_lease_live` twice, then
`ambiguous_remote_recovery_evidence` with disposition `blocked`. The
task showed "Workspace repair required"
(`native_restart_recovery_blocked`) and did not continue.

**Expected behavior**

When the durable controller record proves that the runner received only
bootstrap commands, recovery continues the run with a fresh bootstrap.
Any evidence of provider work keeps the hold.

**Steps to reproduce**

1. Start a native run on a Daytona environment.
2. Kill the control plane after the sandbox runner connects and before
`session.open` completes.
3. Restart the control plane and wait for the old controller lease to
expire.
4. See that restart recovery blocks the task with
`ambiguous_remote_recovery_evidence`.

Root cause:
`server/src/services/native-runtime/native-restart-recovery.ts`
`classifyNativeRunnerRecoveryEvidence`, remote branch. It admitted only
an unclaimed bootstrap (`remote_execution_never_claimed`, #16349) or a
run with a provider checkpoint. A claimed bootstrap with no provider
evidence fell through to `ambiguous_remote_recovery_evidence`.

The decrypted checkpoint of the failed run held `connectionCount: 1`,
`run.prepare` delivered once and pending, `session.open` pending and
never delivered, no committed events, and `ackedSourceSeq: 0`.

Refs #16349, #19524

## What Changed

- `native-bootstrap-proof.ts`: move the incomplete-bootstrap predicates
from the executor into a small pure module. Restart recovery and the
executor now use the same proof. No behavior change.
- `native-runner-checkpoint.ts`: add
`nativeRunnerCheckpointProvesIncompleteBootstrap`. It reads only a
checkpoint with the bootstrap parts (header, cursor, `command:1`,
`command:2`), checks the run, runner instance, and session identity, and
applies the shared proof. A remote runner journal is not on this host,
so the proof uses the controller record alone.
- `native-restart-recovery.ts`: for a classic sandbox run whose owner
lease has expired, with a durable checkpoint, no session checkpoint, no
provider evidence, and no staged attachment, claim
`bootstrap_incomplete` with the reason
`remote_bootstrap_proven_incomplete` when the checkpoint proof holds. At
three provider attempts, block with
`execution_recovery_budget_exhausted`.
- `native-session-executor.ts`: add
`restoreRemoteBootstrapCheckpointForRecovery`. For a
`bootstrap_incomplete` recovery on a sandbox target, it restores the
Postgres checkpoint over the local external-state marker. The existing
incomplete-bootstrap proof then checks the restored state before it
quarantines the root and bootstraps again.
- `doc/run-log-events.md`: document the new disposition reason.

## Verification

- Red, then green: `cd server && npx vitest run
src/services/native-runtime/native-runner-restart-recovery.integration.test.ts
-t "remote bootstrap"`. Before the fix, the proven case and the budget
case returned `blocked` / `ambiguous_remote_recovery_evidence`.
- New cases: the proven claimed bootstrap gets `bootstrap_incomplete`
(attempt kept, generation +1, run still `running`, no recovery action).
The hold stays for a queued `turn.start`, an acknowledged event, adopted
state, no checkpoint, and a spent budget. The executor helper restores
the checkpoint only for a bootstrap recovery on a classic sandbox and
only over a marker for the same run.
- Regression set: `native-runner-restart-recovery.integration`,
`native-session-executor`, `native-restart-recovery`,
`native-bootstrap-retry.integration`, `heartbeat/recovery`: 1383 passed,
14 skipped.
- Live Daytona replay of the failing chaos task (double SIGKILL during
remote runner startup), 3 runs: REPLAY_RESULTS

## Risks

- A fresh bootstrap acquires a new sandbox. The old sandbox keeps its
lease until run settlement releases all run leases. The old runner gets
no turn command, because only the controller issues one and the new
controller never connects to it.
- If `session.open` reached the old runner, the old provider session can
exist without a turn. No provider work or tool call can happen in it.
- Any checkpoint shape other than the bootstrap-only shape keeps the
previous fail-closed hold.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edits, GitHub CLI) 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>
2026-11-06 14:43:22 -06:00
DottaandClaude Opus 5.5 a0cba70f18 fix(runner): continue restart-interrupted ACPX and OpenCode turns (#19501)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Native runs keep their provider conversation when the control plane
restarts
> - A local Codex turn lost in a restart already continues once in the
same conversation (#15297)
> - ACPX (Claude and the other ACPX profiles) and OpenCode took a
different path: the runner closed the lost turn as a provider failure
> - The task then blocked with "Provider turn failed", and the user's
queued follow-up stayed unread
> - A restart must not need a user action when continuing is safe
> - This pull request gives ACPX and OpenCode the same recoverable
restart-loss cause and the same single continuation as Codex
> - The benefit is that work interrupted by a controller restart
finishes on its own

## Linked Issues or Issue Description

Refs #15297 (Codex continuation), #19399 (restart-interrupted turn
hook), #19471 (harness fix used for the live reruns).

**What happened?**

After a control-plane restart during an active native ACPX Claude turn,
the task ended `blocked` with `native_provider_terminal_failed: Provider
turn failed`. The queued follow-up comment was not consumed. No
continuation started. The next run then failed with
`native_session_cleanup_quarantined`. A native OpenCode run killed
mid-turn ended the same way (`turn.failed` with `error: null`).

**Expected behavior**

When the old provider process is gone and the conversation can be
restored, continue the task once in the same conversation. When that is
not safe, record an honest restart cause instead of a generic provider
failure.

**Steps to reproduce**

1. Start a native ACPX Claude task on a local runner.
2. Submit a follow-up comment while the turn is active.
3. Restart the control plane while the turn is still running.
4. Run `pnpm test:e2e:runner -- --max-automatic-retries 0 --id
everyday-workflows.runner-acpx-claude.local.recover-controller`.

**Root cause**

- `runner-core/src/acpx_provider_backend.rs`
`restore_session_if_needed`: a saved `turn_active` state always closed
the session. It recorded `turn.failed` with only a top-level `code:
acpx_active_turn_recovery_closed` and no `error` object. It also emitted
`run.terminal`.
- The runnerd transport forwards only `error`, so the controller saw
`error: null` and reported generic `provider_turn_failed`.
- `runner-core/src/provider_backend.rs` gave the recoverable
`provider_turn_lost_on_restore` cause only to `provider_label ==
"codex"`. OpenCode shares that backend.
- `nativeRestartInterruptedTurnId` accepted only `codex_app_server`. The
runtime continuation gate accepted only `provider.kind === "codex"`.
- Two follow-on defects appeared once the continuation ran live:
- The restored ACPX provider rejected the controller's result for the
lost turn's open semantic call. That call never settled.
- The restored provider's MCP request ids restart. The controller keys
semantic calls by `hash(runId, callId)`. The continuation's first
Paperclip tool call reused an earlier id, was treated as already
answered, and hung. After the result was accepted,
`runner_settlement_incomplete` failed the run.

## What Changed

- ACPX runner: for an active turn lost to a runner restart, prove that
the original provider lifetime lost its fence quorum. Then restore the
same conversation and record `turn.failed` with `error: { code:
"provider_turn_lost_on_restore", recoverable: true }`. Keep the run open
for the controller's continuation. An accepted result, an unanswered
runtime request, or an active goal keeps the old close behavior. So do
an unproven lifetime fence and a failed restore.
- ACPX runner fallback close: add a structured `error`
(`acpx_active_turn_recovery_closed`, not recoverable, with an honest
restart message), so the cause survives the transport.
- ACPX runner: track open semantic calls durably. On restore, retire the
lost turn's open calls. Acknowledge their controller results as
`retired` and do not send them to the new provider.
- ACPX sidecar: when the sidecar restores an existing session, prefix
forwarded semantic `callId`s with a per-process namespace
(`r<hex>:<id>`). Fresh sessions keep raw ids.
- OpenCode runner: give OpenCode the same recoverable cause and open run
as Codex (`restart_lost_turn_is_continuable`). Cursor Cloud stays
excluded because its agent runs remotely.
- Runner TS: `nativeRestartInterruptedTurnId` accepts `acpx_runtime` and
`opencode_server`. The runtime continuation gate accepts `acpx` and
`opencode`. All other Codex safety rules are unchanged: one
continuation, admitted dead-runner restart only, no compaction, goal,
semantic result, or pending runtime request.
- Docs: `doc/execution-semantics.md` and `doc/run-log-events.md`.

## Verification

Red, then green, at each layer:

- Rust `tests/acpx_warm_attachment.rs`
`restores_the_conversation_after_losing_an_active_turn_to_a_runner_restart`.
Red: status `closed`. Then red on the lost call's result: "tool result
does not match a pending provider call". Green after the fix.
- Rust `tests/native_provider_backend.rs`
`restart_lost_opencode_turn_records_a_recoverable_cause`, with the
qualified OpenCode launch fixture. Red: `error` was `null`. Green after
the fix.
- Rust unit test
`restart_lost_turns_continue_only_for_locally_restored_providers`
(Codex, OpenCode yes; Cursor Cloud no).
- TS `native-session-runtime.test.ts` "continues a restart-lost
acpx_runtime / opencode_server turn once". Red:
`NativeProviderTerminalFailure provider_turn_lost_on_restore`. Green:
exactly one continuation, which does not resend the task.
- TS `acpx-runtime-sidecar.test.ts`: a restored provider's call ids are
namespaced and resolve.
- Server `native-runner-restart-recovery.integration.test.ts`: a
checkpointed `acpx_runtime` or `opencode_server` restart interruption is
claimed as `resume_dead_runner`. Red without the driver gate. Green with
it.
- Two `native_provider_backend.rs` tests asserted the old
close-on-restore result. They now assert the new contract. Their real
invariant still holds: the delivered response is never replayed.

Suites run locally on the final revision:

- runner-core: `cargo test --no-fail-fast acpx`, `--test
native_provider_backend` (38), `--test acpx_warm_attachment`, `--test
codex_provider restart_lost`
- runner TS: `native-session-runtime`, `runnerd-restart-continuation`,
Codex driver recovery, contracts, and `acpx-runtime-sidecar` (all pass)
- server: `native-runner-restart-recovery.integration.test.ts` (80
passed)
- `pnpm --filter "@paperclipai/paperclip-runner..." build` (all
generated-contract checks pass)

Live runner E2E (local, real Claude via ACPX, macOS arm64):

- `everyday-workflows.runner-acpx-claude.local.recover-controller`: PASS
twice in a row on the final runner code. The restart crossed an active
turn. The task finished `done`, the queued follow-up was consumed, the
ZIP download was verified, and there were no failure codes.
- `stock-harness.runner-acpx-claude.local.continuity-restart`
regression: PASS.
- Earlier failing reruns are kept as evidence of the follow-on defects:
one harness `kill EPERM` while stopping the server (unrelated to this
change), one hang on the colliding call id, and one
`runner_settlement_incomplete`.

## Risks

- A continuation can run more work in the restored conversation. Its
prompt requires inspecting state before repeating an uncertain action,
as for Codex.
- The ACPX restore waits up to 5 s for the original provider lifetime
fence before it falls back to the old close.
- The ACPX provider state file gains two fields with defaults. Older
saved states still load. A downgraded runner cannot read state that a
newer runner wrote with these fields.
- Restored ACPX sessions now forward prefixed call ids. Only fresh
sessions keep raw ids. Live proof covers Claude. Codex-ACP, Pi, Cursor,
Copilot, Grok, Kimi, and Antigravity share this path, but this PR proves
them only by unit tests and code review.
- OpenCode is covered by the runner fixture and gate tests. The live
OpenCode restart-chaos cell was not rerun here.
- `runner-core`
`unconfirmed_suspension_state_becomes_recoverable_only_after_cleanup_and_suspend`
is flaky when run in parallel. It also fails with the new path disabled,
so it predates this change.

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with
tool use, code execution, and extended reasoning.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:39:29 -06:00
DottaandPaperclip 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>
2026-11-05 16:04:38 -06:00
DottaandClaude Opus 5.5 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>
2026-11-05 20:26:51 +00:00
DottaandClaude Opus 5.5 d361f92071 feat(task-chat): show harness status notices as chips, out of the answer (#19267)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run through the native runner. The runner turns harness
events from Codex and Claude into canonical provider events, and the
task chat shows them.
> - Harnesses report their own lifecycle: context compaction, retries,
model fallback, and warnings. Paperclip lost or mangled these reports.
> - Claude (claude-agent-acp 0.73) sends compaction, model-fallback and
warning banners as ordinary message text, so "Compacting..." ended up
inside the agent's answer and the issue comment.
> - Codex sends `error` with the text in `error.message` and a
`willRetry` flag. Paperclip read `params.message`, so every Codex error
said "Provider notice", and every retry looked fatal.
> - This pull request keeps these reports out of the answer and shows
them as small status chips between the turn's activity.
> - The benefit is that users can see why an agent paused, retried or
forgot earlier context, and the answer text stays clean.

## Linked Issues or Issue Description

**Subsystem affected**
Native runner provider-event mapping (TypeScript driver, ACPX sidecar
and Rust runner-core) and the task chat transcript.

**Problem or motivation**
Claude status banners are mixed into assistant answer text. Codex error
notices lose their message and their retry state. The task chat hid all
of these notices, and settled turns stripped every provider notice.

**Proposed solution**
Classify Claude banners as provider notices at the ACPX boundary. Map
Codex `TurnError` fields and `willRetry` correctly. Render status
notices as inline chips. A retry chip fades after later provider work. A
final error shows the real provider message.

**Alternatives considered**
Patch claude-agent-acp to tag banners in `_meta`. ACPX keeps only
`origin`, `kind` and `source` from `_meta`, and other sessions own the
ACPX `_meta` passthrough work. Banner chunks already have no
`messageId`, and model output always has one, so the classification
needs no adapter patch.

**Roadmap alignment**
This makes existing harness signals visible. It adds no new protocol
schema. It uses the existing `paperclip.provider.notice.v1` `category`
and `details` fields.

## What Changed

- Codex `error` notifications (TypeScript `provider-events.ts` and Rust
`runner-core/src/provider_events.rs`): read `error.message`, map
`willRetry: true` to a recoverable `provider_retry` notice, keep
category `error` for a final error, and add the `CodexErrorInfo` kind,
HTTP status, and the "Reconnecting... n/m" attempt count as notice
details. Retries in one turn share one notice id.
- New `claude-status-banners.ts`: classifies the claude-agent-acp
compaction, model-fallback and SDK informational banners. A chunk must
have no `messageId` and match a known banner exactly.
- ACPX sidecar and the TypeScript ACPX driver emit these banners as
`provider_notice` events instead of answer text. Compaction start and
end share one notice key and carry a `Phase` detail.
- Rust runner-core accepts an optional `noticeKey` and bounded `details`
on ACPX `provider_notice` events.
- UI: new `TaskChatStatusNotice` chip and
`status-notice-presentation.ts`. Status notices become top-level
timeline rows, render as chips wherever protocol cards render, and stay
visible in settled (historical) turns. Generic warnings stay hidden as
before.
- New tokens `--tc-status-notice-warning-*` in `ui/src/index.css`.
- Storybook story `Tasks and conversations/.../Task chat/Status notices`
with eight states.
- `doc/run-log-events.md`: describes the status notice categories.

## Verification

- `pnpm --filter @paperclipai/ui exec vitest run
src/components/task-chat src/components/transcript`
- `pnpm --filter @paperclipai/paperclip-runner exec vitest run
src/provider-events.test.ts
src/drivers/acpx/claude-status-banners.test.ts
src/drivers/acpx/codex-acpx-driver.test.ts
src/cli/acpx-runtime-sidecar.test.ts`
- `cargo test -p paperclip-runner-core --lib provider_events` and
`--test acpx_provider_events`
- `pnpm check:token-gates`
- Storybook: open
`product-task-chat-status-notices--all-states-gallery`.
- Live test-drive on an isolated worktree instance:
- Codex (gpt-5.6-sol, app-server 0.160): a local pass-through proxy in
front of the real OpenAI API cut the first two response streams. Codex
ran its own retries. The chat showed an amber "Reconnecting" chip live,
then "Recovered after retry" once work continued. With every stream cut,
Codex made five retries and then sent a final error with the real
message.
- Claude (claude-haiku-5-5 through claude-agent-acp 0.73): a long
reading task triggered Claude's own auto-compaction twice. The chat
showed "Compacting context…" live and "Context compacted" afterwards.
The final answer and the issue comment contained no compaction text.
- Not verified live: model fallback and Claude informational banners.
They need a model refusal or a blocking hook. Unit tests cover them.

## Risks

- Low to medium. A Claude model message that is exactly a banner text
and has no `messageId` would become a notice. Model output from
claude-agent-acp always carries a `messageId`.
- Older stored Codex `error` notices (category `error`, not recoverable)
now render as a red row instead of being hidden.
- Claude retries (`api_retry`) are still not visible. claude-agent-acp
reports them only to clients that negotiate session-failure records, and
ACPX drops `_meta`. This is a follow-up that depends on the ACPX `_meta`
passthrough work.
- On a failed Codex turn the thread collapses to a summary, so the red
row does not show there. The existing task banner already shows the real
provider message.
- Some local suites failed on this machine. They do not touch the
changed code: Cursor Cloud staging-custody tests, the ACPX Claude
dependency-graph pin test (a timeout), and the Rust `task_memory`
monitor tests. CI is the reference for these.

> 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`) in Claude Code, with tool use,
code execution and browser automation. Default context window. Extended
reasoning was not enabled.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] 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

Refs paperclipai/paperclip#19252 (plan card precedent).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:38:57 +00:00
DottaandClaude Opus 5.5 c6996799c4 feat(task-chat): show an inline diff for every file edit (#19269)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The task chat shows what an agent did during a run, including each
tool call
> - Codex and Claude both report the patch for every file edit, but
Paperclip dropped it. Claude's ACP `diff` content was never read. The
Codex `fileChange` item became a generic item with no patch. Codex
`turn/diff/updated` had no native-runner mapping
> - A reviewer could not see what an agent changed without opening the
workspace
> - This pull request carries a bounded patch on each edit call and
shows it in the edit row. Each turn that changed files ends with a
"Changed in this turn" card
> - The benefit is that a reviewer can check every change in the
conversation, with the same view for Codex and Claude

## Linked Issues or Issue Description

Refs #19252 (precedent: canonical `plan.updated` events as a docked plan
card).

**Subsystem affected**
Paperclip runner (Rust and TypeScript canonical event mapping), PRP
provider-event schema, and the task-chat UI.

**Problem or motivation**
Edit rows say "Edited a file" with no content. Claude Edit/Write `diff`
content is lost after the ACPX sidecar. The Codex native runner drops
`fileChange.changes` and `turn/diff/updated`. The existing
workspace-changes card showed a raw text patch in a dialog, and only on
the in-process Codex path.

**Proposed solution**
Add an optional, bounded `fileChanges` list to
`paperclip.tool.execution.v1`. Fill it from Codex `fileChange` items and
Claude ACP `diff` content. Map Codex `turn/diff/updated` to
`workspace.change.updated` on the native runner. In the UI, show `+N −M`
on each edit row, open a compact colored diff, and end the turn with the
change set.

**Alternatives considered**
A runner-side git diff at turn end. It needs workspace access and extra
cost on every turn. The harness patches are already available. For
Claude, the UI builds the turn card from the edit rows instead of new
per-turn runner state.

## What Changed

- Protocol: optional `fileChanges[]` on `toolExecution` in
`provider-event.schema.json`, with the regenerated schema bundle,
validators and manifest. Paths are workspace-relative. A patch is at
most 32 KiB, and a call has at most 32 files.
- Runner (Rust): new `tool_file_changes.rs`. A Codex `fileChange` item
now also emits `tool.execution.*` with `fileChanges`; the native item
event stays for the recovery and progress projections.
`turn/diff/updated` becomes `workspace.change.updated`.
`normalize_acpx_tool_call` re-validates the sidecar's `fileChanges`.
`normalize_codex_notification_in` resolves absolute paths against the
session cwd.
- Runner (Rust): `has_bounded_rich_display_shape` accepts schema-valid
workspace diffs. Without this change, durable storage cut each patch
string to a 4 KiB preview. Patches use the uncapped secret redactor.
- Runner (TS): new `tool-file-changes.ts`. The ACPX sidecar computes
`fileChanges` from ACP `diff` content with the working directory. The
in-process Codex and ACPX mappers do the same, with an optional
`context` argument.
- UI: new `TaskChatDiffView` (colored rows, hunk labels, a height cap,
"Show N more lines"). Edit rows show counts and the diff. Collapsed
activity groups show edit totals. The workspace card is now "Changed in
this turn", with in-place per-file diffs, and it sits at the end of the
turn. When a harness sends no whole-turn diff (Claude), the card is
built from the edit rows. A change set is marked complete when its run
ends.
- UI: `native-run-events.ts` maps `workspace.change.updated` and
attaches each edit's latest patch to its row (the `diff` transcript
entries now carry `toolUseId`).
- Design tokens: `--diff-add-*`, `--diff-remove-*` and
`--sz-edit-diff-max-h` come from the status icon hues.
- Storybook: `Tasks/Edit diffs` story. Docs: `doc/run-log-events.md`.

## Verification

- Real runs on a local instance (`test-drive`, native runner):
- Codex (`gpt-5.6-sol`): the "Applied a patch" rows show `+9` and `+35
−2` with their diffs. The turn ends with "Changed in this turn", 2
files, `+42`, from `turn/diff/updated`.
- Claude (`claude-opus-5-5` via ACPX): the Write and Edit rows show
`+5`, `+14 −1` and `+9` with correct hunks. The turn card is built from
the edit rows.
- Codex model note: with `gpt-5.4`, pinned Codex 0.160 reports "model
metadata not found". The model then wrote files through the shell, so no
`fileChange` items occurred.
- `pnpm check:token-gates`: all gates clean.
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/task-chat src/components/transcript src/adapters`: 1073
passed.
- `packages/paperclip-runner`: `vitest run src/provider-events.test.ts
src/tool-file-changes.test.ts src/cli/acpx-runtime-sidecar.test.ts`
passed. `check:protocol-types` and `check:protocol-manifest` match.
- `cargo test -p paperclip-runner-core --lib provider_events
tool_file_changes`: passed. `cargo fmt --check`: clean.
- Storybook: open `Tasks and conversations/…/Tasks/Edit diffs`.

## Risks

- Larger payloads: an edit call can carry up to 96 KiB of patch in Rust,
and the sidecar keeps 48 KiB under its 128 KiB frame. Above the budget,
a file keeps its counts and drops its patch (`diffTruncated`).
- Redaction: each patch goes through the shared secret redactor. Before
this change, a 4 KiB preview cap also applied.
- Claude turn card: it joins each edit's patch in order. It is not a net
diff. For a file the turn created, the counts are net and exact. For an
existing file, the counts are summed over its edits.
- The workspace card moves to the end of the turn. One adapter test
changed to match.
- Local only: four Linux cgroup `task_memory` Rust tests fail on macOS.
They do not touch these modules.

## Model Used

Claude Opus 5.5 (`claude-opus-5-5`), via Claude Code with tool use
(shell, browser and file editing) and a long 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
- [ ] 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>
2026-11-05 17:39:07 +00:00
DottaandPaperclip 818d2bb661 fix(native): preserve closed post-result failure diagnostics (#19126)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Native execution persists a result before it completes cleanup and
workspace export.
> - A later execution error enters the failure-write path.
> - That write must reject an already selected result. The existing code
reports lease loss for every rejected write.
> - This can hide the category of the first error even when the same
controller still owns an unexpired lease.
> - This change adds closed diagnostic labels after exact ownership and
accepted-result checks. It keeps the existing control error and recovery
policy.

## Linked Issues or Issue Description

**What happened?**

A local real-PostgreSQL regression accepts a result through the
production control plane, then injects a harmless filesystem failure.
The production executor reports `native_session_lease_lost`. It loses
the original failure category because the failure transition requires a
null result ID.

**Expected behavior**

Keep the result guard and the existing lifecycle classification. Record
a closed diagnostic category when the selected result is accepted and
the controller still has exact, unexpired authority. Keep ordinary
lease-loss fencing when that proof is absent or its diagnostic lock is
busy.

**Steps to reproduce**

Run the new post-result cases in
`server/src/services/heartbeat/runtime-execution.test.ts`. They use the
real executor, control-plane result commit, heartbeat recovery path and
PostgreSQL. The provider boundary is controlled. No live provider is
used.

**Paperclip version or commit**

The regression fails on base `7d1e9b3477`. Related open PR #16917
changes legacy lease recovery. Open #19063 adds terminal finalizer
reasons, and #15310 preserves database finalization errors. They do not
change this executor diagnostic branch. This change only adds native
diagnostic evidence and does not reclaim a lease.

## What Changed

- Add a typed diagnostic with a closed stage, category and
accepted-result flag. Do not retain the source message, path, arbitrary
code or lease token.
- After a rejected failure write, check the selected accepted result and
the exact company, task, run, owner, attempt, generation, process
identity and expiry. Use a coordinator-only `NOWAIT` lock. Fall back to
the original control error if the diagnostic read fails.
- Report the closed event after rollback. Keep
`native_session_lease_lost` as the error message and leave every
failure-write predicate unchanged.
- Add real-PostgreSQL safety cases for stale and expired authority,
owner replacement, Stop state, result selection, pending accounting, one
provider invocation, closed labels and diagnostic sink failure.
- Document the closed event, unchanged authority, and possible late
diagnostic sink completion.
- Add a competing-row-lock barrier. It verifies actual PostgreSQL lock
refusal and control settlement before the competing lock is released.

## Verification

- Permanent regression on unchanged base: RED because the closed
diagnostic is absent.
- NOWAIT mutation: RED when only NOWAIT is removed. PostgreSQL reports
lock timeout instead of immediate lock refusal. The test uses barriers
and the actual database error, with no elapsed-time assertion.
- Final related suites: 56 tests passed across runtime execution and
provider failure integration tests.
- Full server source and test typecheck: passed.
- Full `pnpm -r typecheck`: passed in an independent locked-dependency
checkout.
- Full `pnpm build`: passed.
- Full `pnpm test:run`: running in the independent immutable checkout.
All six chat shards passed; the remaining suites are still pending.
- The first current-head CI attempt failed in two cases: workspace
runtime initial fixture HTTP readiness and instructions-bundle
error-message rendering. Both exact cases passed in isolation on
unchanged PR head and base. The original causes remain unknown after
bounded test-only observation. One controlled same-head rerun of each
failed job passed. Current required CI and all other completed checks
passed. Original failures remain recorded; no infrastructure or
flaky-test claim is made.
- Module boundary check and `git diff --check`: passed.
- Exact-head Greptile: 5/5 with no actionable findings or open review
threads. Automatic native review remains queued. That advisory check is
not required by current master rules; no current-head native assessment
is claimed.
- Earlier fixture setup failures remain recorded. These included missing
fixture fields and observing the Drizzle wrapper instead of its
PostgreSQL cause. They are not the regression RED. An earlier broad
typecheck was interrupted for the required NOWAIT revision; the final
full typecheck passed.
- No hosted export recovery or original hosted failure cause is claimed.

## Risks

The extra read is diagnostic only. It uses NOWAIT and cannot wait for a
competing coordinator lock. Its failure preserves the existing control
classification. The event sink uses the existing 60-second
execution-control bound. A slow sink can finish later and emit a late
event. Result selection, Stop, accounting, retry eligibility and
provider work do not depend on event success. No schema or lifecycle
contract changes are required.

## Model Used

OpenAI GPT-6.1, model ID `gpt-6.1-sol`, with reasoning, tool use and
code execution. The host did not expose the context-window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-04 17:45:53 -06:00
Devin FoleyandClaude Opus 5.5 6a45ea479e fix(heartbeat): retry failed legacy lease renewals until the deadline (#17851)
<!-- entire-trail-link-start -->
https://entire.com/gh/paperclipai/-/trails/52/changes/paperclip/52
<!-- entire-trail-link-end -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A legacy agent run keeps a durable controller lease. The server
renews the lease every 10 s. The lease lasts 60 s.
> - The lease watchdog stops the run when it loses the lease, because
another controller can then take the run.
> - #17032 moved renewal to a private pool. It also retries a renewal
that hits its statement timeout. But a renewal that fails with any other
error still stops the run at once.
> - Staging still shows this failure after #17032. A legacy run made 186
good renewals over 31 minutes. Then one renewal failed with an error
23.9 s after the last good renewal, and the watchdog stopped the run
immediately (`controllerLeaseLossCause=renewal_error`). The lease still
had about 36 s left.
> - This pull request retries a failed renewal until the lease deadline,
the same as a timed-out renewal.
> - The benefit is that one dropped database connection no longer
cancels a healthy run. The 60 s lease deadline still bounds the retries,
so the ownership guarantee does not change.

## Linked Issues or Issue Description

Refs #17032
Refs #16084

**What happened?**
A legacy agent run on staging was cancelled mid-turn. The run was
healthy. The cancellation evidence was
`cancellationSource=controller_lease` and
`controllerLeaseLossCause=renewal_error`, with
`controllerLeaseRenewalCount=186` and
`controllerLeaseSinceRenewalMs=23913`. The release already contained
#17032.

**Expected behavior**
One failed lease renewal must not stop a run while the lease is still
valid. The watchdog must try the renewal again. It must stop the run
only when the lease deadline passes or when the database rejects the
renewal.

**Steps to reproduce**
1. Start a legacy run so that `watchLegacyControllerLease` watches it.
2. Make one renewal query fail with a connection error, for example
terminate its Postgres backend with `pg_terminate_backend`.
3. See that the watchdog aborts the run immediately with
`renewal_error`, although the lease has not expired.

## What Changed

- `watchLegacyControllerLease` retries a renewal that fails with an
error. It waits 1 s between attempts
(`LEGACY_CONTROLLER_RENEWAL_RETRY_DELAY_MS`). The existing lease
deadline stops the retries.
- A retried renewal is safe. The UPDATE is idempotent, and it still
requires this controller's boot ID and an unexpired lease. A rejected
renewal (no row updated) still stops the run at once with
`renewal_rejected`.
- If the deadline passes while the attempts still fail with an error,
the cause stays `renewal_error`. A statement timeout counts as a stall,
so it stays `renewal_deadline`, as before. A good renewal clears the
error state.
- `deadlineLateMs` is now recorded whenever the deadline ends the lease,
also for `renewal_error`.
- Tests:
- New embedded-Postgres test: it terminates the renewal's own backend
while the renewal waits on the run row. The renewal retries on a new
connection and keeps the lease. Without the fix, `assertOwned` rejects.
- New unit tests: one failed attempt is retried and the lease is kept; a
stall after a recovered error is still `renewal_deadline`.
- The existing `renewal_error` test now checks that a renewal that
always fails is retried and is reported as `renewal_error` at the
deadline, with its timings.
- `doc/run-log-events.md` describes the retry and the updated
`renewal_error` and `deadlineLateMs` meaning.

## Verification

- `server/src/services/legacy-controller-lease.test.ts` with embedded
Postgres: 23 tests pass.
- With the original `legacy-controller-lease.ts`, 4 of these tests fail.
One of them is the real-Postgres test that terminates the renewal
connection.
- `server/src/services/heartbeat/run-completion.test.ts` and the
`run-cancellation` tests: 35 tests pass.
- A typecheck of the changed files reports 0 errors. CI runs the full
typecheck.

## Risks

- Low risk. The change is limited to the legacy lease watchdog.
- A run with a permanently failing renewal now stops at the lease
deadline (at most 60 s after the last good renewal), not at the first
error. Another controller cannot take the run before the lease expires
in the database, so ownership stays safe.
- A non-transient error (for example a code bug in the query) is retried
once per second until the deadline. The cause label is still
`renewal_error`.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, code search, 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>
2026-11-04 10:15:08 -08:00
DottaandPaperclip d8c1b9d4b5 fix(runtime): reclaim failed remote checkpoint retries (#18877)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native recovery must preserve the original execution and provider
state.
> - A failed remote run can retain its provider PID after heartbeat
releases the task execution lock.
> - Normal resumption excludes that PID, while restart recovery rejects
the released lock.
> - Recovery must reclaim that exact execution only after current
admission and durable ownership checks pass.
> - Failed retries and admission holds need separate finite budgets.
Neither can prove a provider stopped.

## Linked Issues or Issue Description

**What happened?**

A failed remote native run remains in retryable failure without a task
execution owner. Normal resumption and restart recovery both reject it
before inspecting the preserved remote provider. The current remote
workspace reference format also misses the due-retry routing path.

**Expected behavior**

A due exact-checkpoint retry reclaims its original task execution only
when current ownership and safety gates permit it. Newer task input
prevents an old claim. Lost or ambiguous provider state still prevents
replay or replacement. Ineligible recovery eventually becomes a quiet
manual reconciliation obligation.

**Steps to reproduce**

1. Start a remote native run and persist its provider checkpoint and
encrypted Runner authority.
2. Fail with exact-checkpoint recovery and a retained remote PID.
3. Release the task execution lock at heartbeat completion.
4. Run ordinary resumption and restart recovery after the retry
deadline.
5. Observe that both reject the same eligible run before remote
inspection.

**Paperclip version or commit**

Reproduced from source at `d59791828482`; integrated with upstream
`db9be4c5a6`.

**Deployment mode**

Remote workspaces with PostgreSQL. Local regressions use disposable
PostgreSQL and encrypted checkpoint persistence.

Related public repairs #18846, #18854, #18860 and #18841 cover finished
per-turn owners, pre-provider failures, unstarted follow-ups and storage
migration. They do not reclaim this failed run's released execution
lock. Merged #18879 also adds scheduled-resume lock retention and
reclaim. This PR keeps that compatibility and requires encrypted Runner
authority, canonical source ordering and a charged failed retry before
reclaim. Its scheduled-resume regression now uses a real encrypted
authority and retains a snapshot-only refusal control.

## What Changed

- Share normal admission checks and one guarded execution-binding
restore/event helper between normal and remote resumption.
- Keep agent-name normalization in a module with no imports. Preserve
heartbeat's existing export. Native admission no longer loads heartbeat
orchestration for a string key.
- Recheck company, assignment, agent eligibility, Stop, pause, approval,
dependencies, execution and checkout ownership before reclaim.
- Reject newer task input using PostgreSQL creation-time and ID
ordering, preserving timestamp precision.
- Require the recorded remote target, encrypted Runner authority, exact
checkpoint identity and existing controller fence.
- Commit task binding, controller lease, run status and recovery event
atomically. Recognize both remote workspace reference formats.
- Charge failed reattachments against the existing
three-provider-attempt budget, including retained-lock crash windows.
Active controller adoption preserves its charged attempt.
- Use the existing five-retry/30-minute system-recovery policy for
admission holds. Ended tasks and newer sources exhaust it immediately.
Preserve PID, checkpoint and task bindings, suppress resource
settlement, and assign manual reconciliation with executor stop/state
explicitly unverified.
- Register recovery intent before deadline writes; preserve live/future
deadlines quietly and roll back zero-row writes. Record changed hold
reasons once and terminal dispositions separately.
- Keep historical provider-budget exhaustion behind current
admission/source/controller/authority checks. Make an existing
orphan-cleanup fixture explicitly due.
- Include exact plugin task leases that have no workspace reference.
Reuse every reclaim and retry-budget guard, with a bounded
company/task/run lease projection.
- Share strict manual ownership proof across stale-lock cleanup,
adoption, queue release and wake admission. Require termination receipts
for every exact remote lease before normal cleanup can release either
binding.
- Preserve governing Stop through the existing audited cancellation
path. Its acknowledgement does not release an unverified provider.
Ordinary Retry can wait for exact Stop and file preservation, then queue
one continuation.

## Verification

- Current head is `5d7baf74aa89d00d7ac6e1493d38a6ce2ae7b7b5`. Its
[current CI
matrix](https://github.com/paperclipai/paperclip/actions/runs/41782198858)
is terminal and clean: 54 successful, three skipped and two neutral
contexts. Required `ci / verify` and `ci / e2e` pass. Greptile is 5/5 on
this exact head with no open findings. No fresh Paperclip 5/5 assessment
is claimed.
- The [prior complete CI
matrix](https://github.com/paperclipai/paperclip/actions/runs/41754365388)
passed on `3e641466d3edcaa6dd5021d979627082d388eb05`, with Greptile and
Paperclip both 5/5 and no open threads.
- All 233 tests across the six affected heartbeat, restart-isolation,
remote-admission, scheduled-resume and session-binding families pass on
production-identical `a5104fa89f9cb493091e000bf21ed3858871ae46` under
Node 24.22.2 and pnpm 9.15.4. They cover both workspace formats and
plugin task leases, competing controllers, exact rollback, Stop,
assignment/company boundaries, attempts, work-intent ordering, finite
backoff and quiet held state.
- The merge preserves #18931's per-run audit-failure isolation and this
PR's unchanged-transition hold. Two initial composition cases failed
because their setup or expected result described the older contract. The
rollback test now expects the exact per-run deferral while retaining
full coordinator equality and no work intent. The routine fixture writes
a real encrypted checkpoint through the normal owner before reaching the
actual routine unique constraint. No production admission guard or
rollback assertion was weakened. A separate agent reviewed this exact
merge and fixture delta without finding a blocker.
- Full recursive `pnpm -r typecheck` and `pnpm build` pass on production
merge `89c616fe45ceff75db450f67fd34224b4df7441d`. Subsequent commits
change only three test files. Both the two-file `a5104fa` and final
`5d7baf` server test-layer typechecks pass.
- Four production-identical `a5104fa` probes pass with the rebuilt
actual Rust Runner, a scripted ACPX subprocess and encrypted PostgreSQL
checkpoints under Node 24.22.2. Workspace-reference and plugin-lease
admission each cover surviving and lost providers. Replacement preserves
one provider launch, one original side effect, the charged attempt and
restored task execution binding. Lost providers reject snapshot recovery
without replacement. This is protocol-boundary evidence, not a live
Claude or hosted provisioning claim.
- Initial `a5104fa` CI found one additional routine integration fixture
missing the persisted `exact_checkpoint_resume` mode. Its encrypted
checkpoint was already present. The exact case fails locally before
repair and passes after recording that mode and expecting the charged
provider attempt. Every exact routine conflict, rollback, checkpoint,
task binding, successor-count and sibling-startup assertion remains.
Final head changes only that fixture. The correct native-server group
passes all nine files: 175 executed cases and 14 container-only skips,
matching the now-successful native-server CI gate.
- The earlier canonical HTTP Retry acceptance passed on the prior
reviewed head with real PostgreSQL, the recovery worker and real files.
Pending Stop protects both bindings and starts no successor. Exact
termination and copyback queue one preserved-workspace continuation. No
browser or live-model outcome is inferred from that test.
- The predecessor's full local `pnpm test:run` failed in an unchanged
Discord Gateway fixture with a 200 ms lease and a 422 initial ownership
error. Focused predecessor and untouched-base controls pass; that
full-run cause remains unproved. The required command was also started
on production-identical `a5104fa`. All six local chat shards passed,
covering 1,850 cases. The duplicate local invocation was interrupted
with SIGINT while the general-server group was still running after this
head's complete CI and the combined repair qualification had passed. The
wrapper exited 1 on cancellation, no process retained its test log, and
no full local pass is claimed. An earlier attempt to select the native
lane through the CI wrapper instead selected local package sharding:
2,631 cases passed and 13 failed. Those separate failures are retained
without causal attribution or unrelated source changes; that run is not
counted as native-server qualification. The direct native group and
current complete CI matrix pass.
- Live Claude Opus 5.5 continuation, hosted plugin provisioning,
deployment and recovery of historical tasks remain unverified. No
original task was retried or mutated for this verification.

## Risks

- Restoring a task binding grants remote inspection; it does not prove
that the provider survived.
- Unknown effects, lost providers and missing semantic results retain
their existing reconciliation requirements.
- Exhausted admission holds preserve execution bindings and resource
evidence for manual reconciliation. They do not authorize a fresh
executor or provider/resource release.
- The source guard is conservative: newer task input prevents recovery
of an older failed source.
- No database migration or provider profile change is required.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, tool use, code editing and
shell execution. The runtime does not expose the exact model snapshot 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>
2026-11-04 07:18:07 -06:00
DottaandPaperclip c39e076638 fix(runner): retain the cause of closed ACPX sessions (#18872)
## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - Runner records provider failures so the task can show their cause.
> - An ACPX control request can fail and close its session before the
event pump runs.
> - The next poll then reports only that the session is closed.
> - A second cleanup error can also drop a valid startup cause.
> - This pull request retains each failure at its existing reporting
boundary.
> - Operators can inspect the cause while the failed session stays
closed.

## Linked Issues or Issue Description

Related: Refs #18622, #18847, #18854. Those changes preserve provider
causes and handle proven startup failures. This change covers a control
failure after provider execution starts.

**What happened?**

A rejected sidecar control closes the ACPX session. A later poll
replaces its error with `ACPX provider session is closed`. The native
terminal loses the control classification. Separately, cleanup can
discard an existing validated startup cause.

**Expected behavior**

Retain the first control classification in the failed terminal. Do not
invent a provider explanation for that code. Keep a validated startup
cause when cleanup also fails. Keep the session closed, uncertain tool
outcomes pending, and the existing recovery checks effective.

**Steps to reproduce**

1. Start an ACPX turn and receive an authorized tool call.
2. Return its result after the provider has retired that call. The
sidecar rejects `tool.resolve`.
3. Poll the native backend. Before this change, the terminal reports
only the closed-session guard.
4. For the separate startup case, combine a rejected `session.open`
cause with a cleanup error. Before this change, the combined error drops
its validated provider cause.

**Paperclip version or commit**

The two subprocess regressions and the cleanup-composition regression
fail against unchanged production code at `683fa4725e`.

**Deployment mode**

Native Runner with ACPX. The process regressions use the real Rust
subprocess transport and durable native backend. Their controlled
stale-tool rejection matches the production sidecar response shape. The
separate cleanup unit injects a cleanup error at the existing
error-composition boundary.

## What Changed

- Retain the first failure in the ACPX session and return it from later
closed-state checks.
- Keep the validated provider cause when cleanup also fails.
- Match the stale-tool fixture to production. It carries the closed code
and no invented provider cause.
- Cover a rejected tool result, normal shutdown, raw sidecar-text
exclusion, pending tool preservation, refusal of another turn, and
terminal replay after controller restart.
- Document the existing failure and recovery boundary.

## Verification

- Red: both process regressions fail on the base code. Polling replaces
the named control failure with the generic closed-session error. The
cleanup unit also fails because the combined error loses its existing
startup cause.
- Green: all 12 ACPX session tests, all 36 native backend tests, and all
four session unit tests pass. The stale-tool cases require the
classification and absence of an invented provider cause.
- Commands: `cargo test --manifest-path
packages/paperclip-runner/runner/Cargo.toml --locked -p
paperclip-runner-core --test acpx_provider_session` and the same command
with `--test native_provider_backend`.
- Map structure and whitespace checks pass.
- At head `981faa4`, the full Runner core Rust suite passes: 847 tests
passed and three existing tests are ignored. Workspace typecheck and
build pass. The existing TypeScript provider-failure and
durable-settlement boundary suites pass all 30 tests.
- An earlier concurrent local Rust attempt missed an existing Codex
diagnostic deadline. That case passes alone on baseline and head, and
the final full suite passes with the local socket support its tests
require.
- Complete current-head CI passes: 55 successful checks, five
intentional skips and two neutral checks; no failed or pending checks.
Both `ci / verify` and `ci / e2e` pass.
- Failed CI attempts were investigated before rerunning affected jobs.
Logs showed runner shutdowns/lost communication, an unchanged Windows
WebSocket authentication deadline, and an unchanged Cursor Cloud
Stop-fixture assertion. The Cursor Cloud case passes on baseline and
head. No test assertion or CI guard was disabled.
- The local full `pnpm test:run` invocation passed all six chat
partitions, covering 1,838 expanded cases. It was stopped as a duplicate
while still in general-server after complete sharded CI passed. A
completed local full Vitest run is not claimed.
- Greptile and Paperclip reviewed the corrected head at 5/5 with no
actionable findings. There are no unresolved review threads.
- The process test exercises control rejection, process cleanup, durable
terminal persistence, and controller restart. The provider boundary is a
deterministic sidecar fixture. It is not a live Claude or hosted
recovery test.
- A genuine provider refusal or uncertain external effect remains
unresolved. This change preserves its evidence and does not make it safe
to replay.

## Risks

- The first control classification becomes visible where a generic
closed-session error appeared before. Raw sidecar error text stays
excluded. Startup details use the existing validated and redacted
`ProviderFailure` contract.
- A stored failure must not grant execution authority. The failed
session still rejects calls and new turns. Normal shutdown has no
fabricated failure cause.
- No provider profile, digest, retry budget, process ownership rule,
migration, credential, or permission change is required.

## Model Used

OpenAI Codex, based on GPT-6, with source inspection, code editing,
command execution and regression tests. The exact runtime snapshot and
context-window capacity 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 and available 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 this PR with the bug template fields
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal
ticket ID
- [x] I have run the local Rust, typecheck, build and boundary checks;
complete sharded CI passes and the partial local Vitest result is
reported above
- [x] I have added behavioral regression tests
- [x] I have updated the relevant execution and run-log documentation
- [x] I have considered and documented the risks
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open findings
- [x] I will address all review findings before requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-04 07:17:58 -06:00
commitperclip[bot] a00109aa35 fix: Preserve sanitized ACP JSON-RPC error details and numeric codes in run errors, summaries, and acpx.error records (#18087)
> [!NOTE]
> Opened by Prospector's automated issue-fix pipeline. A maintainer
reviews and decides; nothing here merges on its own.

> [!WARNING]
> This change crosses a trust boundary. See Risks.

## Thinking Path

- Issue #16034 reports a defect in paperclipai/paperclip.
- Root cause, as the fixing agent read it: emitAcpxFailure in
packages/adapter-utils/src/acpx-engine/execute.ts built the run error,
summary, and acpx.error message from Error.message alone. It ignored the
JSON-RPC error's 'data' explanation and numeric 'code', so every ACP
phase failure (create runtime, ensure session, configure session, turn)
showed only 'Internal error'.
- This pull request: Preserve sanitized ACP JSON-RPC error details and
numeric codes in run errors, summaries, and acpx.error records

## What Changed

- `packages/adapter-utils/src/acpx-engine/execute.ts`: This change is
kept from the earlier attempt. The shared failure handler reads the
explanation from a string 'data', 'data.details', or 'data.message'. It
appends that text to the message unless the message already contains it,
and runs the result through sanitizeTerminalSessionFailure. A numeric
JSON-RPC code is recorded as errorMeta.rpcCode. Errors without these
fields keep their old message.
- `packages/adapter-utils/src/acpx-engine/execute.test.ts`: The existing
harness keeps the earlier it.each case, which covers all four phases,
all three detail shapes, redaction, and rpcCode in the acpx.error
record. A new case in the same harness covers the checks that only the
separate file had: detail text already in the message is not repeated,
and a plain Error stays unchanged with no rpcCode.
- `doc/run-log-events.md`: Documents the change to the acpx.error
record: which detail formats are read and in what order, the rule
against repeating detail text, the redaction and 4,096-character title
limit applied through the existing sanitizer, and the optional integer
'rpcCode' field.

## Verification

- Reproducing test(s), added in this PR:
`packages/adapter-utils/src/acpx-engine/execute.test.ts`
- They fail on the base commit and pass with the fix, each twice in a
fresh sandbox.
- The repository's compile command passes with the fix applied.
- 9 existing test file(s) near the change passed.
- The full test suite passes except for the 11 file(s) that already fail
on the base commit.

## Risks

- Written by an automated pipeline, not a person: review it as you would
an outside contribution.
- Risk tier of the touched paths: 2.
- The scope reviewer found it also changes, beyond the report: Plain
Error, non-object throw, string or Node errors with string codes
(ENOENT), and data that is empty, missing, non-string or malformed
- The scope reviewer found it also changes, beyond the report:
messageOverride callers (turn timeout, handshake guard)
- It crosses a trust boundary. Check each crossing against what the
issue asked for, and against who asked:
- data-exposure
(packages/adapter-utils/src/acpx-engine/execute.ts:3626): Text the ACP
child process controls (JSON-RPC error 'data', 'data.details' or
'data.message') is now added to the run error message and summary that
users see; it passes through sanitizeTerminalSessionFailure redaction
first.
- logging (packages/adapter-utils/src/acpx-engine/execute.ts:3651): The
'acpx.error' run-log record now carries the provider's explanation in
'message' and a new integer 'rpcCode' field, both supplied by the child,
on every failure route that calls emitAcpxFailure without a
messageOverride, including the handshake-guard route that suppresses
stderr.
- data-exposure
(packages/adapter-utils/src/acpx-engine/execute.ts:3639): The numeric
JSON-RPC error code from the child is now copied into
'classified.errorMeta.rpcCode', which reaches the adapter result's
errorMeta and the places that store or show it.

## Model Used

- opus (Anthropic Claude, via Claude Code), writing and checking the
change in an isolated sandbox.
- opus (Anthropic Claude, via Claude Code), reviewing it independently.

## Linked Issues

Fixes #16034

A search of paperclipai/paperclip's open and closed pull requests found
no other pull request that names #16034.

## 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)
- [ ] 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)
- [ ] 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

<!-- prospector:issue-fix v1 issue=16034 base=5cfa2bf26d3e
report=1b07160d9492357f -->

Co-authored-by: commitperclip[bot] <284812584+commitperclip[bot]@users.noreply.github.com>
2026-11-03 14:43:24 -08:00
Devin FoleyandPaperclip 9fb06afbfa fix(runner): explain ACPX session-start failures and name typed causes (#18847)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native Runner starts ACP providers (Claude, Codex, Pi and others)
through the ACPX sidecar. `session.open` starts the provider session.
> - When the provider session does not start, Runner reports
`classification=session_ensure_failed`. If the provider failed with a
plain error and not with an ACP `RequestError`, no provider cause
crosses the sidecar. Then the run error, the run events and the run log
do not say why the session did not start.
> - A provider can also type the failure, for example with ACP's
authentication-required code. Runner reported that failure with the same
generic classification. The server could not tell a limit or a sign-in
failure at session start from other startup failures.
> - On one instance, a Claude agent hit its provider usage limit. The
first run failed in the turn with a typed limit failure. Each later run
for many hours failed at `session.open` with only
`session_ensure_failed`. Operators could only guess the cause from the
timing.
> - This pull request keeps a redacted, bounded cause for every session
ensure rejection. It names a typed provider category in the closed
classification.
> - The benefit: an operator can read why a session did not start, and a
later recovery change can treat a typed startup failure by its category.

## Linked Issues or Issue Description

Refs #18622, which added the `ProviderFailure` cause for startup and
turn failures. This change closes the remaining startup gaps. Refs
#17543, which is a related open change to the same classifier. It keeps
the JSON-RPC code range as a closed code. The two changes edit the same
lines, so the second one to merge needs a small rebase.

**What happened?**

A native ACPX run failed before its first prompt with `ACPX sidecar
command session.open was rejected (retryable=false,
classification=session_ensure_failed)`. When the ensure rejection was
not an ACP `RequestError`, the run had no other cause data. When the
provider typed the failure (for example ACP `-32000` "Authentication
required"), the classification stayed the same generic value.

**Expected behavior**

Every session ensure rejection keeps a redacted provider cause that an
operator can read on the run. A typed provider failure keeps its
category in the cause and in the closed classification. Provider text
never goes into the closed error text.

**Steps to reproduce**

1. Make `runtime.ensureSession` reject with a plain `Error`, or with an
ACP error that has code `-32000`.
2. Open a qualified ACPX runtime through the sidecar.
3. Before this change, the plain error has no `providerFailure`, and the
`-32000` error has category `unknown` and
`classification=session_ensure_failed`.

**Paperclip version or commit**

`master` at `e0276e0166c`.

## What Changed

- `acpxProviderFailure` reads ACP's reserved authentication-required
code (`-32000`) as category `authentication`. It also reads the
`errorKind` that Claude ACP puts in ACP error data (`rate_limit`,
`billing_error`, `authentication_failed`, `overloaded` and others). The
category table is now a `Map`, so an `errorKind` such as `constructor`
cannot select an inherited object key.
- The session ensure path in `codex-runtime-adapter.ts` keeps a cause
for every rejection. A rejection without an ACP error keeps its own
message, redacted with the launch environment and the existing
credential patterns, with category `unknown`. An abort reason is Runner
cancellation, so it does not become a provider cause. A non-`Error`
rejection now keeps its cause on the wrapper error.
- `acpx_sidecar_transport.rs`: for `ACPX_SESSION_ENSURE_FAILED`, a
schema-valid cause with a typed category gives
`classification=provider_<category>` (for example
`provider_quota_exhausted`, `provider_rate_limit`,
`provider_authentication`). An untyped or invalid cause keeps
`session_ensure_failed`. Only the validated enum value is used. The
cause's text never goes into the error string.
- `runnerd-codex-transport.ts`: when a command result has a provider
cause, the reported error now keeps Runner's closed ACPX rejection text,
so the classification stays in the run error. Before, it changed that
text to "Provider request failed". Any other message (for example a
Codex `RPC failed: ...` message) still gets the generic text. Provider
text still travels only in `providerFailure`.
- The credential-free Pi probe (`pi-closed-startup.test.mjs`,
`scripts/pi-public-install-probe.mjs`) now expects
`provider_authentication`. Pi reports its missing credential with ACP's
authentication-required error.
- Docs: `doc/run-log-events.md` and
`doc/architecture/runner-pi-capabilities.md`.

**What the Claude ACP agent returns at session start**
(`@agentclientprotocol/claude-agent-acp` 0.73.0 with the repository
patch, and the ACPX 0.13.1 patch):

- `newSession` and `loadSession` call `createSession`. It throws
`RequestError.authRequired` (`-32000`) only for a claude.ai subscription
when gateway auth is hidden. It throws `invalidParams` for a bad `cwd`
and `resourceNotFound` for a missing resumed session.
- Any other error, for example a failed SDK `initializationResult()`,
becomes a generic `-32603 Internal error`. The ACP SDK puts the raw
message in `data.details`, or the parsed JSON body in `data` when the
message is JSON.
- The typed AIR `sessionFailure` (category `limit`, `access` and others)
is sent only on prompt responses and on `session_info_update`
notifications. `loadSession` history replay also sends one for each
earlier usage-limit message, but that notification does not show whether
the limit is still active. The `errorKind` data is attached only by the
prompt loop.
- Thus a usage limit at session start has no typed signal on the ACP
error. The cause keeps the provider's message, and the existing `API
error: NNN` status parse can type it. This change does not add more
free-text matching.

**Shape for a recovery consumer:** the server reads the cause with
`providerFailureFromError(error)`, as it does now. A startup cause has
`phase: "startup"`, a `category` from the `ProviderFailure` schema and
its `retryability`. The run error keeps
`classification=provider_<category>` for a typed ensure failure, or
`session_ensure_failed` when the category is `unknown`.

## Verification

- `npx vitest run src/drivers/acpx src/cli
src/drivers/provider-failure.test.ts
src/live/runnerd-codex-transport.test.ts
src/live/runnerd-codex-transport-settlement.test.ts` in
`packages/paperclip-runner`: 103 of 104 files passed, 2214 tests. The 2
failures are in `installation-integrity.test.ts`. They fail the same way
on unmodified `origin/master` in this checkout (a temporary `server.js`
cannot resolve a module).
- New tests: a plain ensure error keeps its message with the
launch-environment secret and an `sk-ant-` value redacted; a `-32000`
ensure error gives category `authentication`; a non-`Error` rejection
with `errorKind: "billing_error"` gives `quota_exhausted`; an aborted
handshake gets no cause; typed and untyped ACP errors; a closed ACPX
rejection keeps its classification and a changed one gets the generic
text.
- `npx vitest run src/control-plane/acpx-controller-recovery.test.ts`: 8
passed.
- `cargo test -p paperclip-runner-core --no-fail-fast`: all passed. New
unit test: typed categories give `provider_<category>`; `unknown`, no
cause, a category outside the schema and an extra field each keep
`session_ensure_failed`; other closed codes keep their own
classification. New pipe test through the fake sidecar: the cause
survives the rejection, the classification is typed or generic, and the
cause text is not in the error string. In the first full run, two
`stdio_mcp` relay tests timed out. They passed in the second full run.
They do not use the changed code.
- `node --test test/acpx-sidecar-contract.test.mjs
test/protocol-contract.test.mjs test/pi-closed-startup.test.mjs`: 29
passed. The Pi probe is skipped without the Pi distribution.
- `pnpm --filter @paperclipai/paperclip-runner typecheck` (TypeScript,
`cargo fmt --check`, `cargo check`): passed. `pnpm --filter
@paperclipai/paperclip-runner build`: passed.
- Server, after the runner build: native provider failure, run
completion, task interruption presentation, Antigravity probe and the
related native session executor tests: 639 passed.
- Not verified: a live Claude account at its usage limit. I could not
run the credential-free Pi probe, because this checkout has no Pi
distribution. Its new expectation follows from the Pi patch, which
raises `RequestError.authRequired` for the missing credential.

## Risks

- The run error for an ACPX startup rejection with a cause now shows
Runner's closed rejection text, not "Provider request failed". This text
has only closed codes and is the same text that the run error showed
before #18622. Provider text stays in `providerFailure`.
- A plain ensure error message now becomes a displayed provider cause.
Redaction applies before the 4,000-character bound. A new credential
format that the patterns do not know can still need a new pattern. The
same risk applies to all provider causes.
- The classification of a typed ensure failure changes from
`session_ensure_failed` to `provider_<category>`. No server code matches
`session_ensure_failed`. The Pi probe expectation is updated.
- The category and retryability are evidence only. This change does not
change retry policy.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) through Claude Code, with tool use
(shell, file edits, test runs). 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
- [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>
2026-11-03 13:24:36 -08:00
DottaandPaperclip b22ef22d56 fix(runner): show provider failure details on tasks and runs (#18622)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native Runner starts providers and records their run outcomes.
> - Provider failures can contain useful details below a generic
protocol error.
> - Those details were lost at the sidecar, command receipt, and run
projection boundaries.
> - A blocked task therefore showed a generic failure even when the
provider gave a clear reason.
> - This pull request carries a bounded, sanitized provider failure
through those boundaries.
> - Operators can read the actual cause on the task and run pages.
Existing recovery policy still controls retries.

## Linked Issues or Issue Description

Refs #17543 for the related closed JSON-RPC classification work. Refs
#17272 for provider recovery in the legacy OpenCode adapter. This change
adds display evidence for native Runner providers and keeps recovery
policy separate.

**What happened?**

A native Claude session returned an HTTP 429 message below an ACP
`Internal error`. The task and run showed only a generic startup
failure. Operators had to inspect the provider response to learn that
the account was rate limited.

**Expected behavior**

The task and run show the sanitized provider message, provider name,
startup or turn phase, and available HTTP status. Unknown categories
keep the message. Display evidence does not grant a retry or replace a
recovery decision.

**Steps to reproduce**

1. Start a native Claude run whose account is rate limited during model
selection, or return a structured provider failure from a Codex or
OpenCode turn.
2. Let the run fail and open its task page.
3. Before this change, the page shows a generic failure. With this
change, the page shows the provider explanation. The run page shows the
same explanation after reload.

**Paperclip version or commit**

Source checkout based on `f8a888a072`.

## What Changed

- Add one JSON Schema for `ProviderFailure` and generate matching Runner
and shared TypeScript readers. Keep the Runner package independent of
the app workspace at runtime.
- Normalize known ACPX, Codex, and OpenCode error fields. Preserve
unknown messages. Redact known credential values and credential patterns
before applying the 4,000-character limit. Do not copy response bodies,
headers, stacks, or prompts.
- Carry startup failures through the sidecar, Rust Runner, durable
command receipts, and private transport error details. Keep provider
text out of generic transport error strings and Sentry failure messages.
- Keep turn failures in authenticated run events. Recover the same
evidence from persisted events during server finalization. Store the
cause in the company-scoped run result.
- Show one shared notice on failed tasks and run details. Keep existing
retry permissions, scheduled retry state, and resolved-task behavior.
- Reapply registered run-secret and current-user redaction to task
display data. Batch secret lookups for only the visible provider causes.
- Document the contract and update the capability maps. Add regressions
for nested Claude 429 errors, OpenAI quota and rate limits, OpenCode
errors, redaction, durable receipt replay, restart finalization, and UI
rendering.

## Verification

- `pnpm -r typecheck` passed. `pnpm build` passed. All GitHub Actions
build, test, package, and validation jobs passed on `d49c79ab4a`. The
security checks passed. Greptile gave this head 5/5 with no open review
threads. Paperclip Review remains pending. Its review worker exhausted
six workspace recovery attempts with export_interrupted. A fresh review
request was accepted and is waiting for that saved workspace. The
ordinary Retry action rejected reuse of the stopped request. This is an
external review-worker blocker; no check was overridden. The duplicate
local `pnpm test:run` was stopped after the CI test shards passed.
- Focused server settlement, provider evidence, and restart tests: 93
passed. Runner admission and normalization tests: 103 passed. Focused
Runner wire and terminal tests: 29 passed. The real Rust/sidecar command
receipt replay test passed.
- The new projection test failed before implementation because the task
had no provider cause. The startup producer regression also failed
before extraction moved ahead of the generic error-code replacement.
Both pass with this change.
- Live check: a rebuilt local sidecar received an actual Claude HTTP 429
during startup. The new cause kept the full provider message. A
synthetic task in an isolated local app then reached the same provider
error through the native Runner path. The dedicated run result, task
API, task banner, and run detail all showed the same message after
reload. The later reporting fix keeps run.error generic; a regression
proves the display cause still survives restart and stays out of Sentry
capture.
- The first isolated app attempt failed earlier in
`admission_credential`. An ordinary manual Retry reached the provider
and produced the 429 above. This PR does not claim to fix that separate
admission failure. An initial parallel local database test setup also
failed; the serial restart suite passed.
- Security review found that copying display text into run.error could
forward it to Sentry. A regression failed with that behavior. The fix
keeps the cause in its display field. All 38 settlement/restart tests
and the server typecheck pass.
- All 29 protocol contract tests pass, including bounds and extra-field
rejection. An earlier full Rust core run passed. The later full run hit
one 5-second receipt-limit timing assertion; its isolated rerun passed.
The full Linux and Windows Rust CI jobs passed on this head.
- Review regressions pass for explicit Codex driver identity, native
Rust startup and resume receipt propagation, oversized OpenCode/ACPX
errors, task read-time registered-secret redaction, and the standalone
source closure. The focused Runner run passed 103 tests;
projection/restart tests passed 16.
- Read-time redaction edge cases passed: a missing decryption key omits
only the unsafe optional explanation; expanded redacted messages remain
visible within the Unicode character limit. All 14 task projection tests
and server typecheck pass.
- The package Build check found an old Pi assertion against
Error.message. The probe now checks the exact closed classification in
the authenticated receipt and requires the new provider cause. Both
polling transport cases pass. A local credential-free Pi probe stopped
at admission_installation because this checkout lacks the Pi
distribution; the installed Linux package probe passed in CI on this
head.
- UI coverage includes startup and turn text, unknown categories,
literal rendering of provider text, read-only retry behavior, and
resolved-task suppression. Token gates and map checks passed.
- The live check covers Claude startup on macOS. Codex, OpenCode,
terminal failures, and restart paths have deterministic regression
coverage. The change is not deployed.

## Risks

- Provider messages are untrusted text. The boundary selects specific
fields, redacts credential patterns and known environment secret values,
bounds the message, and renders plain text. New provider-specific secret
formats may need added redaction patterns.
- Optional fields preserve compatibility with older producers and stored
runs. Old runs without the field keep their current fallback text. No
database migration is required.
- The cause stays in company-scoped run data. This change does not send
provider text to first-party Telemetry or add it to OpenTelemetry span
attributes.
- Normalized categories and retryability are display evidence. Existing
recovery classifiers, retry limits, ownership checks, and approval gates
keep their authority.

## Model Used

- OpenAI GPT-6 through Codex, with reasoning, repository tools, code
execution, and browser verification. The host does not expose the exact
serving variant 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 and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-03 06:13:26 -06:00
DottaandPaperclip dbff3747b8 feat(server): persist run logs and provider traces in S3 (#18327)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators use run logs and restricted provider traces to inspect
agent work.
> - Local log and trace files depend on the web server's persistent
volume.
> - That dependency prevents overlapping volume-backed Railway
deployments.
> - This PR reuses the instance S3 store for batched log and trace
payloads with conditional publication.
> - Existing Postgres rows retain their contracts; native export
recovery reuses events already committed before Runner acknowledgement.
> - S3-configured servers automatically copy and verify retained history
before a later deployment cutover.

## Linked Issues or Issue Description

**Subsystem affected**

Server run logs, restricted provider traces, storage providers, and
migration tooling.

**Problem or motivation**

Replacing the web server and its disk must preserve committed log output
and retained traces. The current S3 mirror can repeatedly upload a
growing file. Moving those payloads into Postgres would add database
writes, WAL, replication, and backup work.

**Proposed solution**

Reuse the existing instance storage configuration, S3-compatible
provider and credential chain. Store new run logs under run-logs/ and
opted-in raw traces under provider-traces/, below the existing prefix.
Cloud keeps its existing bucket and bucket-scoped credentials per stack.
Add no new configuration switches. Store immutable payload segments and
bounded binary range indexes directly in S3. Publish each batch with an
ETag-conditional head. Keep existing database ownership and lifecycle
records. Recover canonical native exports from their existing validated
control event source, checkpointing export progress in S3. Add no new
payload writes to Postgres. Preserve local defaults and migrate settled
history with verified byte counts and digests.

**Alternatives considered**

Postgres payload chunks add control-plane load. Repeated whole-file
mirrors amplify transfer work. The first index prototype left superseded
index nodes; the final binary index keeps all normally published nodes
reachable.

**Roadmap alignment**

This is a storage prerequisite for replacing the main web/API server. It
does not enable deployment overlap, detach a volume, or change native
Runner protocol acknowledgements. The maintainer approved this
implementation scope. ROADMAP.md has no duplicate implementation item.

Related PR: #16770 improves the existing in-flight mirror. This PR
preserves that legacy backend and uses the existing instance S3 profile
for new journals.

## What Changed

- Automatically select S3 segments for new logs and opted-in traces when
instance file storage uses S3. Reuse bucket, endpoint, region, prefix,
path style and credentials. Remove separate backend switches. Keep
local-disk instances local.
- Share the existing storage configuration resolver with product files.
Preserve config-file settings, environment precedence and the existing
root prefix. Keep pre-existing run-log mirror settings for legacy
history.
- Add bounded buffering, conditional ownership fencing, immutable range
reads, stable byte offsets, and completion hashing.
- Dispatch all existing log consumers through the recorded backend.
Coalesce output progress writes and publish live log messages after
storage receipts.
- Recover interrupted canonical native exports from their existing
durable source. Exclude separate credential, cleanup, and reprojection
records. Replay missing callback ranges and deduplicate late delivery.
Repair settled archives after controller lease expiry. Bound shared
repair work and stop it when all readers cancel. Serve the published
prefix when best-effort repair fails.
- Add encrypted optional trace channels, replacement reads, durable
deletion tombstones, and deadline-driven local/external expiry cleanup
independently of heartbeat scheduling. Stream historical source and
remote verification. Propagate worker cancellation through trace open,
conditional-write probing, upload, restore and reads. Fence deletion
even before the first object head exists; abort and drain owned
captures.
- Retry transient storage errors, distinguish 409 from failed
preconditions, and backpressure bounded log admission. Track interrupted
trace imports explicitly so ordinary local deletion stays independent of
S3.
- Automatically migrate retained history through the existing delivery
coordinator. Process one log and one trace per batch. Retry unresolved
history with backoff and stop scans after completion. Keep the
inventory/import command as optional diagnosis and repair. Verify source
and remote bytes before switching references. Verify finalized native
size and digest before setting its export cursor. Require ordered source
coverage when completeness metadata is missing. Preserve original trace
expiry. Block active, missing, corrupt, and unproven sources.
- Preserve identity and run-secret redaction in live and recovered
native logs. Register runtime log redaction secrets once through the
existing encrypted registry. Keep agent private keys in their existing
owner.
- Add storage, migration, retention, UTF-8, and route-privacy coverage,
plus operating documentation and qualification tooling.
- Add no schema, payload table, per-record SQL, production dependency,
or lockfile change.

## Verification

- Final fixes at `b3a44c898e` passed all 116 selected startup,
retention, attribution and cancellation controls. The disabled-scheduler
cleanup and concurrent-repair defects reproduced red before the fixes. A
timed-out repair still prevents later storage reads.
- The merged source passed 124 storage and real-Postgres Cloud
configuration tests. These include automatic history migration, existing
environment/file vault keys, source-directory removal, five real-SDK
stalled-request cancellation cases, shared capability-probe cancellation
and multi-chunk trace ordering. Saved Cloud S3 settings work after the
legacy config file is gone.
- Workspace typecheck and full build passed at the merged parent
`19f702d780`. Server typecheck and build passed again at `b3a44c898e`.
- Actual app acceptance: finish a process-agent run through the normal
API, stage its retained local-file input, then restart. Automatic
migration reports one migrated item and zero blockers. Remove the
original web home and restart at `b3a44c898e` with an empty home, the
existing S3 profile and the existing vault key file. The normal log API
preserves all five producer lines and exactly 788 bytes, with SHA-256
`74d9ae469da28bfe89b9b50a0e132aec24fe9e6d686496ed5e4ffa6167151036`. No
log-specific storage settings are present.
- Actual expiry acceptance: with heartbeat scheduling disabled, a trace
remains unexpired after startup and later reaches its fixture deadline.
The worker marks the row deleted and tombstones both S3 channel heads.
The retained run log stays byte-identical. The process fixture emits no
provider protocol frames, so nonempty encrypted payloads are qualified
separately through the real SDK round trips.
- Isolation tests use identical run identities in two stack buckets.
Cleanup in one existing prefix leaves another prefix, another company
and product files intact. Read-only Cloud source inspection confirms a
bucket and bucket-scoped credentials per stack. Four existing Cloud
storage environment-contract tests passed. No Cloud provisioning changed
and no new live vendor permission probe was performed.
- Earlier qualification passed 1,014 affected storage/native tests and
27 privacy-route tests with isolated Docker Postgres. An abrupt process
test commits 1,000 records, receives receipts, dies by SIGKILL and loses
its directory; another process reads every byte and continues. The real
Rust Runner experiment acknowledges 32 records while the export cursor
is zero, then continues the same Runner PID after controller loss. All
64 records export once with exactly 64 existing control rows. Repeated
snapshot/Stop commands completed in 162/81 ms.
- Emulator workload: 1,000 burst records used two batch PUTs. Eight
steady streams produced 240 records with 104 conditional PUTs and 72
GETs over 6.9 seconds. These are synthetic operation counts, not fleet
sizing or provider prices.
- Failure evidence is retained. Compact native events over 64 KiB,
missing file-key support, disabled-scheduler retention and repair
failure handling all reproduced before their fixes. Fixture repairs
addressed cold module setup, localhost/IPC permissions, the S3
bucket-name limit and a mode-0700 crash-test directory. Full local `pnpm
test:run` was stopped after host PostgreSQL semaphore exhaustion;
affected suites used isolated Docker databases instead. Native package
library symlinks were restored with the package's standard hydration
helper.
- The `19f702d780` browser session-recovery case timed out while saving
a task title. All eight browser shards passed on `b3a44c898e`, including
that case, with no UI behavior or assertion change. Fresh-head CI passed
at `b3a44c898e`: 55 successful checks, three skipped checks and one
neutral check. The branch is conflict-free.
- Map and module-boundary checks passed. Current-head Greptile is 5/5
with zero unresolved review threads.

Reproduce the opt-in storage suites with
`PAPERCLIP_LOG_TEST_S3_ENDPOINT` and a dedicated
`PAPERCLIP_RUN_EVIDENCE_MIGRATION_DATABASE_URL` ending in `_test`. See
`doc/run-evidence-storage.md` for operating limits, automatic migration,
optional diagnostics and rollout qualification.

## Risks

- Keep the existing per-stack bucket, prefix and vault key stable across
replacements. Relocating that storage is a separate whole-instance
operation.
- This release automatically changes new log storage for existing
S3-configured instances. Local-disk instances remain local. Canonical
native source replay is qualified with a deterministic real Runner;
actual remote execution and production-provider qualification remain
rollout gates. Generic server-only diagnostics and optional raw trace
tails without a surviving source can be incomplete after a crash. This
does not establish full zero-downtime deployment.
- Before deploying this release, qualify the actual S3 provider. It must
enforce conditional writes and pass read, version, deletion, workload,
and latency qualification. A capability probe rejects providers which
ignore preconditions. Moto is emulator evidence only.
- Dirty output is bounded and unconfirmed until S3 publication. Storage
failure leaves persistence incomplete while task results and usage can
settle.
- Trace expiry is not yet represented by Cloud's external wake contract.
Outstanding trace deadlines conservatively block sleep.
- Automatic history copying requires the original source volume. Keep it
until all retained resources pass verified import and clean-filesystem
reads. Stop old binaries from creating new local-file runs. Missing or
corrupt history remains blocked and visible in server logs. Rollback
requires a compatible reader against the same external state. Ordinary
object deletion does not erase retained historical object versions or
backups.

## Model Used

OpenAI Codex, `gpt-6.1-sol`, with reasoning, tool use, code execution,
and browser verification. The runtime did 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
- [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>
2026-10-30 15:17:35 -05:00
Nicky Leach a8e07500db fix(runner): preserve runtime activity through the native bridge (#18239) 2026-10-29 18:36:04 -07:00
DottaandPaperclip fe3e0f2d5f Preserve rejected native completion reports as failures (#18227)
Keep rejected declared completion reports on the existing permanent native failure path. Preserve rejection across restoration and order permanent failure ahead of terminal goal facts, goal results, and continuation while retaining the original provider terminal.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 18:49:01 -05:00
Nicky LeachandPaperclip 35ab90ad0f Map runtime activity to authorized task plugin panels (#18131)
## Thinking Path

> - Paperclip manages agents and tasks.
> - Plugins contribute views to the task sidebar.
> - A ready view can belong to an earlier task.
> - Runner records activity under its authenticated run.
> - This PR maps that activity to installed task-panel declarations.
> - The sidebar checks readiness after current-run use and restores use
after reconnect.

## Linked Issues or Issue Description

Builds on merged contract #18129 and Runner publisher #18050. This PR
targets
master and is ready for review. Earlier combined verification is
retained in
[the verification
archive](https://github.com/paperclipai/paperclip/pull/18050#issuecomment-6489369676).

**Subsystem affected**

Run-event reads, plugin task contributions and the task sidebar.

**Problem or motivation**

Readiness polling can show a view left ready by another task. Reload and
reconnect must recover current-run use without losing concurrent live
activity.

**Proposed solution**

Map recorded activity to installed task-panel declarations. Merge
authorized
history and live activity, then perform bounded readiness checks for the
running agent. Preserve the existing read-only view.

## What Changed

- Add optional `{ namespace, activityKey }` activation mappings to
task-panel
declarations. Legacy declarations remain valid but contribute no task
panel.
- Add `view=plugin-panels` to the existing run-events route. Preserve
run-read and
telemetry-read authorization, secret redaction and private-run denial.
Map
activity to installed contributions and return at most 128 panel
identities.
- Scope activation caches to current panel versions and activity
declarations.
Preserve unchanged panels during unrelated plugin updates and history
reads.
- Merge history and live hints without losing concurrent activations.
Filter by
  company, task, run and agent. Restore history on mount and reconnect.
- Replace periodic availability checks with up to three readiness
attempts after
activity. Fresh activity restarts settled transient failures. Pending
checks,
  successful readiness and HTTP 4xx failures are left alone.
- Reuse live run updates with the panel cache key. Recover only panel
history
after a pending subscription read. Mark expected not-ready query results
so
  the global error reporter skips them; real failures remain reportable.
- Preserve current-run dismissal and the read-only plugin view. Update
manifest
  comments and producer/consumer documentation.

## Verification

- Current head: `5e04d757c`, based on master `09c05c550`. Range
comparison
confirmed all six feature commits were unchanged by the rebase. The
final
follow-up keys readiness by the plugin installation ID and availability
path.
- `cd ui && node ../node_modules/vitest/vitest.mjs run
src/hooks/useTaskPluginPanels.test.tsx`
— all 45 cases passed. `cd ui && node ../node_modules/typescript/bin/tsc
-b` passed.
- The shared query-client and live-update recovery selection passed 96
cases on
  `49c544cd2`. Token gates passed before the final hook-only changes.
- Earlier focused verification passed 57 manifest cases, 69
database/history cases,
event-registry cases and route authorization cases, including
private-run denial.
- Regression coverage includes subscription/history races, task changes
during
recovery, unfocused run completion, bounded readiness retries, denied
reads,
expected not-ready responses, changed activity mappings, unchanged panel
preservation, old cache scopes, and same-version route or installation
changes.
- [Current-head
CI](https://github.com/paperclipai/paperclip/actions/runs/40942001707)
passed: 54 successful checks, two skips, no failures or pending checks.
- [Standard and final
review](https://github.com/paperclipai/paperclip/pull/18131#issuecomment-6489842168)
both scored 5/5 on `5e04d757c`. The final review used the Apex tier,
added no
comments, and left no unresolved findings. The PR is conflict-free and
ready for review.
- Local checks stay targeted. GitHub CI provides the broad build, test,
typecheck,
browser and Runner coverage. No deployment or live-provider acceptance
is claimed.

<details>
<summary>Retained failures and recovery evidence</summary>

- `ac9329548` twice failed a browser reload assertion while conversation
history
was concealed. Master fixed its readiness wait in #17923. The approve
journey
then passed locally on `fea63a418` with installed Chrome (1 passed, 39
seconds).
All eight browser shards passed in later CI. The browser test is outside
this diff.
- A canary checkout failed with Git HTTP 408; its unchanged-head retry
passed.
- `de07d09fa` failed the mobile Cases card click after a ten-second
stability wait.
The same read-only journey passed locally on that exact head (1 passed,
26 seconds).
Its unchanged-head CI retry passed 22 cases, with one skip. The original
artifact
  is retained; the cause of that click timeout is not established.
- [CI on
`49c544cd2`](https://github.com/paperclipai/paperclip/actions/runs/40903679075)
passed 54 checks with two skips. CI also passed on `de07d09fa` after the
recorded retry.
- The `1e59ab441` merge checkout passed all browser and server suites.
Runner
shard 2 passed 2,252 cases and timed out waiting for a
controller-recovery command.
The test had landed on master in #18161 after the former branch base.
Rebasing
aligned the local checkout with that code; Runner shard 2 then passed on
`fcf7380d3`.
The original timeout cause remains unproven; no unrelated timeout was
changed.
- [CI on
`fcf7380d3`](https://github.com/paperclipai/paperclip/actions/runs/40940235193)
recorded runner shutdown signals in typecheck and a workspace shard. A
chat
recovery test observed a null wake deadline, and one Runner transport
fixture
failed during close. Those separate assertion failures remain
unexplained.
All of those jobs passed on the final readiness-key head. Those later
passes
establish recovery; they do not prove the cause of the earlier assertion
failures.
- Local browser setup first stopped at pnpm version-download
verification, then a
sandbox IPC restriction, then a missing bundled browser. Installed pnpm
9.15.4
  and Chrome completed the journeys. No dependency files were changed.
- Earlier optional-property TypeScript errors were corrected. Embedded
PostgreSQL
needed its install hook run from its package directory. Older CI also
recorded
fixture-cleanup lock timeouts, attention-fixture timeouts, and canceled
server
shards after runner shutdown. The parent fixed its FIFO snapshot-copy
failure
before merge. The linked verification archive retains earlier combined
evidence.

</details>

## Risks

Activity is a hint and grants no access. Missing plugins, denied reads
and
unavailable views contribute no panel. Older runs without activity
records show
no task plugin tab. A used view stays available for the running task;
reconnect
reconciles readiness. Matching producer and consumer builds are
required. No
migration, dependency, image publication or deployment is included.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools and code
execution.
The exact serving snapshot is not exposed.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-29 13:56:48 -07:00
DottaandPaperclip 9e09d8f51f fix(runner): clarify native approval argument recovery (#18205)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - First-task onboarding requires a saved human proposal decision
before child work can start.
> - Native proposal tools reject interaction metadata placed outside
their advertised schema.
> - The existing rejection text focuses on question forms and does not
explain the misplaced approval metadata.
> - This pull request explains the field placement and keeps approval
recovery in its original confirmation or checkbox kind.
> - It also retains safe schema guidance in the run log so operators can
identify the rejected field.

## Linked Issues or Issue Description

Refs: #18025 for native first-task proposal recipes and #16889 for safe
native schema guidance. Related open PR #18105 changes separate server
API-client guidance.

**What happened?**

A native first-task confirmation copied `version` and
`supersedeOnUserComment` to the tool root. Both belong inside `payload`.
The runner correctly rejected the call before execution, but its
feedback described question forms. Later calls kept the misplaced field
and changed the approval into a question. No proposal card was created.
The run log retained only a generic schema error.

**Expected behavior**

Keep strict validation. Explain the nested field locations. Keep a
confirmation or checkbox approval in that kind while correcting its
arguments. Save the normal pending card and wait for an actual human
decision. Retain bounded schema guidance without storing rejected input.

**Steps to reproduce**

1. Send an otherwise valid native confirmation with root-level `version:
1` and `supersedeOnUserComment: false`.
2. Observe the pre-dispatch `invalid_tool_call` rejection.
3. Move both fields into `payload` and keep `interactionKind:
"confirmation"`.
4. Verify that the corrected call creates one pending confirmation,
exact replay creates no duplicate, and child creation still needs saved
human acceptance.

**Paperclip version or commit**

The observed argument shape and initial regressions came from public
source base `c32e1ba6`. The repair is based on maintained master
`4a6f6a65`. Public tests use synthetic prompts and identifiers.

## What Changed

- Give rejected confirmations and checkboxes approval-specific guidance.
It names `payload.version` and `payload.supersedeOnUserComment` without
suggesting a question-form recipe. Other human-input errors retain
complete question-form guidance and stable question/option ID
instructions.
- State the allowed native root fields in the first-task skill and put
the metadata inside both executable proposal recipes.
- Retain the existing safe guidance in `semantic_tool_denied`
diagnostics. Keep the original rejection identity and
`paperclipExecuted: false`.
- Preserve only the exact bounded scalar diagnostic payload after
verified event admission. The rest of the envelope and all API
request/result bodies keep their existing redaction.
- Add a real native Codex transport regression and strengthen
advertised-schema, card replay, human-admission, persistence, and
privacy controls. Update the execution contract and onboarding maps.

## Verification

- Red: the native transport reproduced the malformed confirmation and
showed that the old hint omitted the metadata correction. A second red
confirmed that approval guidance still included the question-form
recipe. The corrected transport case now rejects before execution,
retains a safe hint, and dispatches one corrected confirmation that
returns a pending card.
- Red: both shipped recipe checks lacked explicit nested metadata. All
49 first-task admission tests now pass, including advertised-schema
validation, idempotent card replay, actual human acceptance, agent-only
rejection, and question-answer controls.
- Updated native checks pass on the current repair: 95 native Codex
transport tests with 2 existing ignored cases, all 32 provider-bridge
tests, and the schema-feedback unit control.
- API/case persistence regressions were red before the event-boundary
correction. All 71 affected port/redaction tests pass in a clean run at
frozen head `3be3a70f`, including the corrected envelope privacy
regression. The focused 16-test persistence/privacy group and server
typecheck also passed earlier.
- Full local `pnpm build` and `pnpm -r typecheck` pass at exact head
`3be3a70f` in a clean detached checkout with Node 24.22.0 and pnpm
9.15.4. Exact-head CI passes: [full PR
CI](https://github.com/paperclipai/paperclip/actions/runs/40950360142)
and [isolated native Runner
readiness](https://github.com/paperclipai/paperclip/actions/runs/40950359835).
- The full local unit attempt recorded a 15-second timeout in the
unrelated Microsoft Teams 100,000-character existing-comment publication
case. That failure remains recorded. The exact isolated rerun passed (1
test; 1,814 unselected). The aggregate timeout cause is unproven. That
long local run straddled subsequent source edits and also observed the
two port regressions during their red phase; it was stopped and is not
final-head evidence. The clean affected-suite run above and current-head
CI provide the final verification. No timeout or assertion was weakened.
- Release-registry CI initially rejected the native test's compile-time
schema include outside the release crate's hashed input closure. The
guard was reproduced locally. The test now reads the current canonical
schema at execution. All 10 crate-cache controls and the real native
approval regression pass afterward; the cache guard and workflow keys
remain unchanged.
- Current-head Greptile review is 5/5 with no unresolved review threads.
All required checks pass (56 success, 4 skipped). The advisory Paperclip
Review remains neutral/incomplete after one normal re-review request; no
completed assessment or score is claimed.
- `cargo fmt --check`, map structural checks, and `git diff --check`
pass.
- Source and scripted-provider checks do not prove that a model will
follow the guidance. The original live onboarding attempt remains
failed; acceptance at a deployed revision is separate work.

## Risks

- This changes repair guidance and shipped instructions. It does not
make malformed arguments valid or supply a human decision.
- Diagnostic retention adds no rejected arguments, values, or unknown
input keys. The API receipt allowlist is unchanged. The validated-event
exception restores only the safe scalar payload; optional debug and
other envelope metadata still pass through redaction.
- The existing 512-byte guidance bound, strict tool schema, company/run
binding, idempotency checks, and proposal admission remain effective.

## Model Used

OpenAI GPT-6.1 Sol (`gpt-6.1-sol`) through Codex, with tool use and code
execution. The context-window size is not exposed to 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>
2026-10-29 15:48:37 -05:00
Nicky LeachandPaperclip 27be859044 Record runtime activity under Runner authority (#18050)
## Thinking Path

> - Paperclip manages agents and tasks.
> - Runner owns each authenticated run and its provider process.
> - Runtime clients need to report successful activity in that run.
> - A retained process can outlive a run, so an operation needs current
authority.
> - This PR captures that authority before the operation and records
activity afterward.
> - Core can consume the event through the existing run stream.

## Linked Issues or Issue Description

Builds on merged #18129 and targets master. The dependent consumer is
#18131.
Test stability fixes were separated into merged #18128. Earlier
combined-head
verification remains in [the
archive](https://github.com/paperclipai/paperclip/pull/18050#issuecomment-6489369676).

**Subsystem affected**

Runner activity recording and provider command environments.

**Problem or motivation**

A retained provider process can complete an operation after its original
run ends.
Runtime clients need a host-attributed event and a helper that works
inside the
provider sandbox.

**Proposed solution**

Capture opaque Runner context before use. Publish a structured
notification after
success. Runner checks current authority and records
`runtime.activity.recorded`.

## What Changed

- Add `notification-context` and `record-activity CONTEXT NAMESPACE
ACTIVITY_KEY`.
Rotate context on warm attachment and process recovery. Accept no caller
run ID.
- Use one private named pipe. Reuse Runner's bounded JSON-line reader.
Serialize
concurrent publishers with a read-only lock. A blocking reader queues at
most
  128 records; the existing control loop handles at most 16 per pass.
- Grant Codex access to the exact helper, descriptor, lock and pipe.
Preserve
other filesystem and network limits. Preserve host discovery values
through
provider launch boundaries and reject configured-environment overrides.
- Update source identities to Pi 34, Copilot 81 and Grok 12. Keep
historical
  identity fixtures and recovery behavior.

## Verification

- Targeted release notification and sandbox-policy tests: seven passed.
They cover
run rotation, recovery, Unicode limits, concurrent writers, malformed
and partial
  records, bounded queues, failed persistence and fixed-path cleanup.
- Real CLI integration and pinned Codex 0.160.0 sandbox checks: passed.
The sandbox check uses no model, credential or network call. It verifies
activity
recording while unrelated state reads and parent-directory writes remain
denied.
- ACPX profile generator check, formatting and diff checks: passed.
- Rebased onto master `31b927e30` at head `64d0248a8`. Master used the
same profile versions, so the resolution preserves its Pi 33, Copilot 79
and Grok 11 fixtures byte-for-byte and creates new identities. All
identity checks pass. Five targeted provider/environment/recovery files
pass 168 tests with one existing skip; the Rust Pi admission regression
passes. The notification module and Codex sandbox implementation are
byte-identical to the tested pipe head `671373fe0`.
- Removed the mailbox traversal reported by security review. Its
regression replaces
the pipe path with a 100-level tree and an unreadable leaf. Shutdown
preserves the
leaf, performs no traversal and completes promptly, including with a
partial record.
- An initial targeted test failed because its run-rotation fixture
updated config
without updating durable state. The corrected fixture passes. Runner
also rejects
  inconsistent run bindings.
- Previous head `26abbf62b` passed Runner, build and browser CI. Server
shard 11/12
failed in unchanged `attention-service.test.ts`: cleanup/test timeouts
were followed
by a duplicate `ATQ` fixture key. The final head needs fresh CI and
review.
- CI on `64d0248a8` completed with 52 successful checks and four
intentional skips. Runner Vitest shard 1/2 failed when retained-state
copies encountered the live FIFO; the aggregate also failed. Standard
review scored 4/5 and found relative pipe paths plus late context
publication. Follow-up `5c3f14d32` places IPC in a private canonical
temporary directory and publishes context before welcome commands. Its
relative-state CLI test, two authenticated reconnect cases, seven
notification/policy cases and real Codex sandbox pass. One affected
retained-settlement case passes with the matching release binary; the
other 18 cases remain for CI. The sandbox denies sibling-directory and
lock writes. All build, test and security checks pass on `5c3f14d32`;
standard review is 5/5 with no unresolved threads. The requested Apex
review is 5/5 on this exact head, with no findings or unresolved
threads.
- Earlier broad test results in the archive apply to their recorded
source revisions.
Per the requested workflow, final local checks stay targeted; GitHub CI
supplies
  broad coverage.

- After Apex completed, master advanced again and conflicted on Copilot
identity. Head `e94a9359d` is rebased on master `8ac5a128e`; it
preserves the published Copilot 80 fixture and uses Copilot 81. The
notification module, Codex sandbox and durable control-loop code are
byte-identical to Apex-reviewed `5c3f14d32`. All 131 targeted
installation/compatibility/recovery cases and identity generator checks
pass. All 55 checks pass with four intentional skips on the rebased
head. Standard review is 5/5 with no unresolved threads. Apex is 5/5 on
`e94a9359d`, with zero findings and no unresolved threads. GitHub
confirms this head is conflict-free and ready for review.

## Risks

Activity remains an optional hint and grants no access. A successful
helper exit
means publication to the pipe; Runner persists accepted events through
its existing
durable outbox. Full queues, unavailable helpers or failed writes can
drop a hint
without failing the operation. Context reads are local and do not wait
for the
control loop. Publishers have a 500 ms lock deadline and a 2 KiB record
limit.
IPC lives outside durable state. Idle transport blocks; cleanup removes
its fixed files and empty private directory. The host must provide
`/usr/bin/mkfifo`. Rebuild matching Runner/provider artifacts for the
updated source
identities. No dependency, migration, live provider call or deployment
is included.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools and code
execution.
The exact serving snapshot is not exposed.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-29 07:40:50 -07:00
Nicky LeachandPaperclip 80a2b9284b Define durable runtime activity events (#18129)
## Thinking Path

> - Paperclip manages agents and tasks.
> - Runner reports durable run events through PRP.
> - Runtime clients need a typed record of reported activity.
> - The event must keep the normal run authority and read boundaries.
> - This pull request adds the generic runtime activity event contract.
> - Later PRs can publish and consume this contract without private
plugin coupling.

## Linked Issues or Issue Description

First feature prerequisite extracted from #18050. Runner publishing is
#18050; the dependent consumer is #18131. Related independent test
stability work: #18128. The parent search and review history is
preserved in [the verification
archive](https://github.com/paperclipai/paperclip/pull/18050#issuecomment-6489369676).

**Subsystem affected**

Runner event protocol, server run-event persistence and redaction, and
UI event taxonomy.

**Problem or motivation**

Core needs a durable, typed activity record tied to the authenticated
run. A text marker is not an event contract.

**Proposed solution**

Add `runtime.activity.recorded` as a P1 event with a closed `{
namespace, activityKey }` payload. Runner knows no plugin or panel
identity. Run/session identity, sequence and emission time stay in the
outer envelope; Core resolves company, task and agent through the
authorized run binding. Use the existing authenticated persistence,
acknowledgement, replay and redaction paths.

## What Changed

- Define and validate the event in PRP v1, v2 and v3. Update the
generated protocol artifacts and event catalog.
- Preserve saved and live event redaction. Test persistence, replay
deduplication and the existing run-read boundary.
- Register the event in the UI taxonomy without changing panel display
behavior.
- Advance the source-bound Copilot identity to 78. Retain all published
historical identity fixtures without changing their bytes.

## Verification

- Root `pnpm typecheck` and `pnpm build`: passed for the revised
activity contract.
- Node protocol checks: 14 passed. Provider identity and registry tests:
185 passed. Server tests: 103 passed. UI event registry: five passed.
- Maintained Grok and Copilot identity generator checks and `git diff
--check`: passed.
- The first activity-contract review scored 4/5 because the dependent
producer and readers still used the earlier contract. Both dependent PRs
are now updated and restacked; the finding is resolved and review is
running again.
- Initial extraction retained a consumer-only import in one server
suite. That suite failed to load. The consumer case and import were
moved out; the final three server files passed all 103 cases.
- Earlier panel-specific head `6137c50bc` had green CI and standard/Apex
5/5. Those scores do not qualify this revised activity contract. Fresh
CI and reviews are pending. Earlier combined results in the archive
apply only to their recorded source revisions.

## Risks

This change is additive. It adds no route, publisher, filesystem
transport, provider sandbox grant, or panel display behavior. Activation
hints grant no permission to read a plugin view. Provider identities
bind the changed protocol source; historical identities remain
decode-only. No schema migration or dependency change is required.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools and code
execution. The exact serving snapshot is not exposed.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-29 00:32:12 -07:00
DottaandPaperclip 743698e700 fix(runner): preserve OpenCode bash process exit codes (#18076)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native Runner stores structured tool events so people can inspect
actual work.
> - OpenCode puts a completed bash command's process exit in
`state.metadata.exit`.
> - The canonical event mapper only read `state.exit`, so a real nonzero
exit became unknown.
> - This pull request preserves the documented builtin bash exit and
rejects conflicting evidence.
> - The benefit is an accurate process exit without changing tool
completion or native run status.

## Linked Issues or Issue Description

**What happened?**

A completed OpenCode bash part with `state.metadata.exit: 37` produced
`exitCode: null`. The process result was present, but the canonical tool
event lost it. The pinned upstream [shell
result](https://github.com/anomalyco/opencode/blob/v1.18.34/packages/opencode/src/tool/shell.ts#L544-L552)
and [completed
part](https://github.com/anomalyco/opencode/blob/v1.18.34/packages/opencode/src/session/processor.ts#L160-L170)
establish the field path.

**Expected behavior**

The canonical event retains the numeric process exit from a completed
builtin bash part. A completed tool call can have a nonzero command
exit. Missing or invalid evidence must not become a successful command.

**Steps to reproduce**

1. Pass a `type: "tool"`, `tool: "bash"` part with completed state and
numeric `metadata.exit` to `canonicalProviderEventsFromOpenCodePart`.
2. Inspect `tool.execution.completed.payload.exitCode`.
3. Before this fix, the value is null. After this fix, it matches the
process exit.

**Paperclip version or commit**

Reproduced on master `f4f02880cae4f5fa54d47d39565ca99b6b72446d`.
Duplicate searches found no matching fix. Related PR #15715 concerns
OpenCode V2 transport and version support; it does not change this
mapper.

## What Changed

- Read numeric metadata exits only from completed builtin bash parts.
- Keep legacy numeric top-level exits. Return unknown when supplied
legacy evidence conflicts.
- Cover zero and nonzero exits, invalid values, foreign tools,
incomplete calls and conflicting receipts.
- Document the run-log field and the distinction between tool completion
and command success.

## Verification

- The regression suite first failed five assertions on the unchanged
mapper. All 53 provider-event tests now pass.
- A focused no-emit TypeScript check covers the changed mapper, its
tests and imported protocol validators.
- Tests use synthetic structured parts and real protocol validation. No
local database, provider, SDK build or live scenario was run.
- All 48 jobs in the normal PR workflow passed, including the required
`ci / verify` and `ci / e2e` gates. CI also executed all 53
provider-event tests.
- Greptile reviewed this exact head at 5/5 with no actionable findings.
The separate hosted quality-gate review also completed successfully.

## Risks

- Conflicting legacy and metadata exits now remain unknown for completed
builtin bash calls. This avoids selecting one inconsistent value as
proof.
- Other tools retain their existing legacy behavior. Output prose never
supplies an exit code.
- The change adds no schema field and changes no provider version,
budget, accounting, fixture or native run outcome.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with reasoning and terminal tools.
The session does not expose the context-window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-28 15:11:22 -05:00
Nicky Leach 62f60bf158 Measure first deliverable text in native run traces (#17434) 2026-10-24 09:40:08 -07:00
DottaandPaperclip 505e1a5582 feat(cases): add persistent workflows with same-task agent handoffs (#16959)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - An agent workflow needs persistent facts and a safe way to transfer
work.
> - Creating a task for each phase splits the objective, discussion,
workspace, and review policy.
> - A case can retain the facts while one primary task moves between
agents.
> - Transfers must survive process failure and preserve private access,
budgets, and completion gates.
> - This pull request adds durable case handoffs, native case tools, and
read-only case pages.
> - Prompts describe the workflow. Existing task controls decide when
work can run and finish.

## Linked Issues or Issue Description

**Problem or motivation**

Prompt-driven agent teams need a persistent record that can move through
triage, implementation, verification, and correction. Ordinary
reassignment does not provide a durable instruction, safe release
barrier, or recoverable delivery receipt for an agent handing off its
own running task.

**Proposed solution**

Extend standalone cases with one primary task, a descriptive phase,
retained access sources, and a handoff ledger. Apply assignments after
source execution and workspace settlement. Deliver each transfer through
the existing durable wake queue. Provide `create_case`, `read_case`,
`update_case`, and `handoff_case` in the native runner. Creation
requires kickoff and explicit workflow text. An explicit handoff to
`done` closes the case when normal task completion is accepted.

**Alternatives considered**

Do not add a workflow engine, filesystem queue, configured state graph,
or one task per phase. The workflow document combines transition prose
with an optional Mermaid `stateDiagram-v2` map. The server does not
execute the diagram. The Pipelines experiment removed on master stays
removed.

**Roadmap alignment**

This extends Agent Reviews and Approvals, Enforced Outcomes, Bounded Run
Recovery, and Activity Log attribution. Existing approval, review,
budget, privacy, workspace, and responsible-user rules remain
authoritative.

Related work: [ordinary handoff wake
recovery](https://github.com/paperclipai/paperclip/pull/15019). This
feature adds a separate persisted case-handoff receipt and release
barrier.

## What Changed

- Add case routing fields, retained access sources, durable handoff
history, delivery leases, and replay-safe migrations.
- Keep the same primary task across agent phases. Preserve each
returning agent's own session and the task's workspace.
- Fence outgoing mutations, wait for provider and workspace release, and
recover delivery without duplicate task comments or runs. Recheck
current assignment permissions as the original caller before applying a
delayed transfer. Scope a run Stop to that run’s own handoff intents, so
repeated old Stop requests preserve newer work. Cancelling terminal
intent without a source run also requires fenced current task ownership,
including native retries and preparation with an old checkout. For Cloud
callers, revalidate current stack membership with an uncached Cloud RPC
and derive owner elevation from the current managed setting.
- Keep case reads, documents, assets, Audit, and live events inside
retained task/project permissions.
- Add native case creation/read/update/handoff tools. Store required
workflow text and initial documents on the first task. Page oversized
case facts and referenced document bodies through the dedicated tool
with bounded UTF-8 JSON, current routing versions, snapshot conflicts,
and current source authorization on every page.
- Add read-only case lists and detail pages, task-style list controls,
compact dotted field tables, linked handoff tasks, and case filters in
Audit.
- Add native/legacy integration tests, migration/restart tests, browser
coverage, and bounded live receipt fixtures. Extend the trusted sandbox
condition to cover the new subscription fixture profile.
- Hide obsolete paused-agent warnings on completed or cancelled task
threads.
- Synchronize the company-switch settings test with React effects after
a CI failure. Preserve its security assertions and production remount
behavior.

## Verification

The branch directly includes master `9ef434fe79`. The required `ci /
verify` and `ci / e2e` gates pass on head `765fccd67c`. [Current-head
CI](https://github.com/paperclipai/paperclip/actions/runs/39464348614)
passes typecheck, build, tests, and all eight browser shards. Security
checks pass. [Greptile gives this exact head
5/5](https://github.com/paperclipai/paperclip/pull/16959#issuecomment-6291156549),
with no unresolved review threads. The optional Paperclip Review check
remains queued because its assigned agent is paused. It is not a
required branch check. No check result was overridden.

Local workspace typecheck and build pass after the master merge, along
with database migration checks, token gates, map checks, Storybook
catalog checks, and the Copilot profile identity check. Standard builds
refresh Runner vendor output. Focused checks pass 2,008
API/paging/context tests and 7 strict case protocol tests. The final
commit changes one test fixture only; its ten tests, UI typecheck, and
token gates pass. No production source changed in that commit.

On current head `765fccd67c`, all **194 critical workflow tests**
executed and passed with no skipped tests in those files:

- 38 Stop/ownership tests, including board completion intent, native
retries, stale Stop, and preparation with an old checkout.
- 2 actual legacy process/HTTP handoff tests, including scratch failure,
a closed-stdio child that continues writing, and recovery through a
fresh worker after the process group stops.
- 5 migration tests, including SQL column metadata, preserved null
authority context, and all six prior-numbering receipts.
- 3 process-settlement and 5 scratch tests.
- 88 handoff-service, 18 heartbeat handoff, and 21 native case-tool
integration tests.
- 7 bounded case/document-read projection tests and 7 strict
protocol-contract tests.

The current CI checkout was `fed65d49`, merging `765fccd67c` into master
`9ef434fe79`. Its new document regressions reconstruct exact
Unicode/escaped bodies and revisions through real native authority and
authenticated HTTP with generic API tools disabled. They cover
case-owned, primary-task, and linked-task documents; routing on every
page; stale document snapshots; access loss and unlinking;
unrelated/foreign/private sources; system documents; and quarantined
content. Active-task document tools retain their existing behavior.
These are current-head test results from the first CI attempt; the
browser-only retry did not rerun these suites.

The full `64fdd3403f` CI failed one separate settings test: after a
company switch, it observed an unsettled API-key draft. The production
component and test were unchanged from prior green source. Production
switches remount both keyed settings layers; the test directly reused an
unkeyed view and used a timer tick instead of waiting for React effects.
The final commit replaces that fixture synchronization with React `act`.
The exact secret-clearing assertions and all timeouts remain unchanged.
The original assertion failure remains recorded; no unchanged retry was
used. The narrow unchanged baseline and fixed fixture each pass 10
tests.

The upstream result-paging merge preserves case capture namespaces and
current asset privacy. Its API integration suite executed 39 of 41 tests
successfully, with two skipped; the optional assigned-result PRP
integration test was skipped. Neither skipped suite is counted in the
194 critical tests or as live provider proof.

All 361 upstream migration entries and SQL bodies remain unchanged. Six
case migrations follow master as 0363–0368 with identical SQL hashes.
Official Drizzle generators produced the combined metadata and five
retained snapshots. Generation reports no schema changes, and the
snapshot drift test passes. The earlier `ce361ae5d0` CI passed on
attempt 1, including its then-current 179 critical tests. Fresh review
later identified unusable document references with generic API tools
disabled; the bounded document reads and ten additional authenticated
HTTP tests address that finding.

The full local `pnpm test:run` attempt was cancelled after this Mac
exhausted PostgreSQL semaphore capacity; it is **not** counted as a
pass. Later database attempts also stopped before assertions or skipped,
including one sandbox socket denial. Two original stale-Stop regressions
did reproduce the bug against the old source. A REST serialization
assertion passed after two preserved database-startup failures. No test
timeout was increased. All 194 assertions listed above executed and
passed in current-head CI. The complete 385-file, 3,963-test UI shard
also passed after the fixture repair.

A configured-provider receipt workflow passed 13 backend checks across
triage → implementation → verification → correction → done, using one
task and four work runs. The saved receipt is revision 2 with total 6
USD; case and task both closed. The original canonical attempt remains
failed because its browser assertion confused the terminal destination
with completed-task tooltips. The corrected full browser verifier passed
on the same retained database and original runs, with no additional
provider calls. The last clean-build browser readback is on
`6a86c54a53`; later changes affect recovery, authorization, bounded
reads, and settlement. Accounted work cost is a $1.010574 rate-card
estimate; provider billing and setup-probe usage are unavailable.

Copilot profile 49 passed the unchanged seven-fixture private protocol
pack (46/46 checks, no retries) and the product file-edit canary after
master's completion-replay change. All 60 bound implementation hashes
remain unchanged on the current source. Historical profile-47/48
identities and results remain intact. See the [profile-49 compatibility
record](https://github.com/paperclipai/paperclip/blob/codex/persistent-case-handoffs/doc/plans/2026-10-20-copilot-completion-recovery-compatibility-v49.json).
This is bounded compatibility evidence, not a fresh full matrix,
installed-package product test, or Daytona rollout.

Preserved CI failures and retries:

- On `20662930ac`, Slack managed setup failed a five-second success
assertion after reload. Its test/application path was unchanged from a
prior green head. One unchanged-source retry passed all 25 tests in that
shard; no source or timeout changed.
- On `9402b4f17a`, two CI workers received shutdown signals at 15:24:19
UTC. Neither log had an assertion failure before interruption. One
unchanged-source retry of each passed all Runner static checks and all
32 tests in browser shard 6; both aggregates passed. The next head,
`7b4374edaf`, passed all CI without retries.
- On `295f289b5a`, serialized shard 9 timed out during its first
device-login test, then the next test found an active login lease.
Direct login/test paths were unchanged from a prior green head. Cold
import plus a timed-out async body using the next test's mutable
actor/store is the supported fixture-race explanation; no request trace
proves that sequence. The original failure remains recorded. One
unchanged-source retry passed all 294 tests in 19 files, including all
41 device-login tests. No login source or timeout changed. The
subsequent `ce361ae5d0` CI passed without retries.
- On `64fdd3403f`, one company-switch settings assertion observed an
unsettled draft. The final test-only commit uses React `act` without
changing assertions, timeouts, or production code. On `765fccd67c`, all
ten settings tests and the complete 385-file, 3,963-test UI shard
passed.
- On `765fccd67c`, browser shard 3 completed the split-handoff
assertions, then cleanup received HTTP 500 from run cancellation after a
one-second PostgreSQL row-lock timeout. The extra chat run was reporting
completed work while teardown began. Cleanup stopped before restoring a
global experiment flag, causing a later assertion to fail. Relevant
production and test paths are unchanged from an earlier green commit.
Stop's lock order is consistent, and no nested self-lock was found; the
exact PostgreSQL blocker was not recorded. One unchanged-shard retry
passed. The original failure, trace, and downstream flag failure remain
recorded. No Stop implementation, cleanup assertion, or timeout changed.
The retry supports intermittent cleanup contention; it does not identify
the original blocking transaction.


## Risks

- Local process transfers require host-owned PID/group absence. Missing
or uncertain termination evidence keeps delivery pending, independently
of scratch setup.
- Handoff admission and task cancellation share execution ownership. New
regression tests cover temporary holds, late source writes, and
uncertain provider termination.
- Retained private sources use an intersection of existing permissions.
This intentionally fails closed when a source is missing or an existing
task cannot preserve that boundary.
- Migrations add tables and indexes. They preserve master history and
can replay over prior feature installs. Migration 0368 adds nullable
internal authorization provenance with no default or authority backfill.
Historical transfers still awaiting release without this context fail
closed; an operator must cancel them and request a new handoff.
- Delayed Cloud handoffs depend on the current Cloud membership RPC. An
unavailable, ambiguous, expired, or mismatched response holds the
handoff for retry; it does not reuse cached authority or grant access.
- Bounded reads reload and reauthorize the selected projection per page.
Underlying work grows with the data size and number of pages. Changed
data or routing requires restarting from offset zero without the old
snapshot.
- Phase names and workflow diagrams remain advisory. They do not satisfy
review or approval gates.
- This is a large, cross-cutting PR. The maintainer requested one PR for
the whole feature; the commits separate data, runtime, UI, and
qualification work.

## Model Used

OpenAI Codex, GPT-6 family, with tool use, code execution, and delegated
code review. The exact deployment model ID and context-window size were
not exposed in the session. Live fixture agents used the explicitly
configured provider model recorded in their test evidence.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [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>
2026-10-20 12:46:14 -05:00
b448710986 fix: derive startup warmth from per-layer reuse evidence (#16908)
## Thinking Path

> - Paperclip helps people manage AI agents and inspect their work.
> - Agent startup can reuse a lease, workspace, Runner, process, or
session.
> - A session ID can remain the same when other resources are new.
> - The old cold/warm label did not show these separate decisions.
> - This pull request records closed reuse receipts and derives labels
from them.
> - Reports now show partial reuse and missing evidence.

## Linked Issues or Issue Description

Refs #11685. That issue tracks a specific ACPX fingerprint repair. This
change records reuse evidence across startup resources. It does not
repeat that repair.

**What existing behavior does this improve?**

Startup labels in the operator trace and Product E2E reports.

**Current behavior**

A cached runtime or a continuous session can suggest a warm startup even
when workspace staging or provider launch work occurs.

**Proposed behavior**

Record each layer's reuse decision and a closed reason. Record observed
provider launch counts. Use `warm`, `cold`, `partial_reuse`, or
`unknown` from those receipts. Keep old runs unknown when they have no
receipts.

## What Changed

- Add local run-log receipts for the lease, workspace, Runner, provider
process, and provider session.
- Count physical ACPX launches, including respawns. Preserve the reason
for a new process.
- Reuse native workspace adoption and session lifecycle evidence. Keep
native and bridged remote provider-process reuse and launch counts
unknown where physical evidence is absent.
- Show the per-run summary in retained results and the dashboard. Keep
missing legacy values explicit.
- Set the legacy trace boolean only for fully evidenced cold or warm
startup. Keep the operator OTLP gate. Add no first-party Telemetry.
- Test full reuse, workspace invalidation, two direct provider launches
with session continuity, and report generation.
- Start the final diagnostic write without waiting for persistence. Keep
lease cleanup independent of a stalled event sink.
- Keep remote provider evidence unknown after runtime setup fails or the
host relay starts twice.
- Align a stale Pi support-test expectation with current master profile
28 and its suite fingerprint. Keep the qualification rules unchanged.

## Verification

- Passed: all 319 runtime, startup-timing, reuse-summary, and native
workspace-history tests. Disposable local Git tests used the native Git
binary.
- Passed on the current commit: all 2,768 Runner E2E support tests
across 128 files. This includes report and history tests for generated
HTML and normalized JSON.
- Passed: `pnpm --filter @paperclipai/adapter-utils typecheck`.
- Passed on `df60f8dbdbfcb6d80c84cd3bf563f8d58b2f9f03`: all PR CI gates,
including repository typecheck and build, all test shards, and browser
tests. Both automated reviews are 5/5. GitHub reports 55 successful
checks, one neutral review, and two skipped Storybook checks.
- The first server shard run failed because an unchanged
workspace-runtime fixture did not accept health connections within 10
seconds. Its isolated local test and the single failed-jobs rerun both
pass. This establishes recovery; the cause of the first timeout remains
unknown.
- Passed in the hosted source run on this exact commit: recursive
typecheck, token gates, the large Runner E2E typecheck, Runner E2E unit
tests, and build. The retained `source.txt` matches this PR head.
- The extra unsharded `pnpm test:run` reached its 60-minute limit (exit
124). It has no complete verdict. The broad workflow therefore reports
failure. No assertion-failure marker appears in the retained partial
log. Full-suite acceptance uses the passing canonical PR CI partitions.
A read-only inventory check found no gaps across 1,023 general server
files, 161 serialized suites, and 20 workspace projects. The separate
chat/native Runner lanes cover the excluded server files. No tests or
timeouts were weakened. The local environment has 4 GiB of memory. Use
CI for the large Runner E2E typecheck.
- The initial report tests failed because the harness could not resolve
package imports from the repository root. Relative source imports fixed
that failure.
- The first broad local run had timeouts from the environment Git
bridge. Native Git resolves the ancestry timeout and all runtime tests
pass. The added report fixture lacked its required screenshot; the
corrected report tests pass. A stale master assertion expected Pi
profile 27, while the generated profile is 28. Updated its expectation
and exact suite fingerprint; all 78 targeted support tests pass. The
clean full support run passes all 2,768 tests.
- The three review regression cases failed before repair. They showed
remote counts of zero and two, and a completed run that waited for the
final diagnostic sink. They now pass. The stalled-sink case keeps
persistence pending while the run returns, both bridge stops run once,
and the staging lock map becomes empty.
- Hosted evidence for this commit: [source
verification](https://github.com/paperclipai/paperclip/actions/runs/39335475416),
[retained command logs and status
table](https://github.com/paperclipai/paperclip/actions/runs/39335475416/artifacts/12228829282).
All listed commands exited zero except the incomplete unsharded test
command described above.
- The physical-launch test drives the executor's spawn callback twice.
The native workspace test uses real Git repositories and verifies reuse
and invalidation. These are deterministic tests, not a paid provider
qualification campaign.

## Risks

- Consumers must handle `partial_reuse` and `unknown`. Partial evidence
no longer produces a legacy cold/warm boolean.
- Native and remote bridged provider process evidence is still
unavailable on some paths. The report shows unknown values for those
paths.
- Receipt emission is best effort. Missing receipts reduce confidence in
a label and must not change execution or settlement.
- No schema migration, first-party Telemetry event, or endpoint default
changes are included.

## Model Used

OpenAI Codex, based on GPT-6, used reasoning, repository tools, and code
execution. The exact provider model ID and context window are not
exposed to this session. The existing implementation checkpoint was
preserved from the earlier agent run.

## 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>
Co-authored-by: devinfoley <139239+devinfoley@users.noreply.github.com>
2026-10-19 21:52:57 -05:00
DottaandPaperclip ac9459bae3 feat: configure company agent observability (#16874)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Companies use several agent harnesses and execution environments.
> - Operators need one place to select where native agent logs and
traces go.
> - An inference proxy would add a dependency to the agent request path.
> - This pull request supplies native OpenTelemetry settings to managed
Codex and Claude Code processes.
> - Company secrets protect destination credentials. Export failure does
not change the task result.

## Linked Issues or Issue Description

**Subsystem affected**

Company settings, shared contracts, database, server, adapters, and
runner.

**Problem or motivation**

An operator cannot configure a company destination for native agent logs
and traces. Manual harness configuration is hard to apply across local
and remote execution.

**Proposed solution**

Add one company destination: Braintrust or a custom OTLP HTTP/protobuf
endpoint. Apply a transient snapshot when execution preparation starts.
Keep credentials out of API reads, task environment manifests, recovery
files, and command arguments. Replace a warm process when its export
settings change and preserve its conversation.

**Alternatives considered**

Multiple destinations can use a user-operated collector for fan-out.
This change does not add a collector, an inference proxy, or transcript
scraping.

**Roadmap alignment**

This extends company settings, secret management, and support for
multiple harnesses. The maintainer requested this implementation.
Searches for observability and Braintrust found no duplicate company
destination feature. Related infrastructure tracing work in #16236 has a
separate content-free contract.

## What Changed

- Add strict schemas, revision checks, encrypted secret storage,
activity records, and board-only settings routes.
- Add explicit synthetic logs and traces tests with a bounded HTTP
request and sanitized results.
- Supply native Codex configuration and Claude Code environment values
through local and remote launch paths.
- Preserve Codex login refresh through the existing managed credential
link. Retain unsaved drafts across background reads. Require complete
custom header re-entry when the endpoint origin changes.
- Separate process export compatibility from conversation identity.
Isolate generated Codex configuration and preserve resumable sessions,
including remote home refresh after export changes.
- Advance the immutable Pi/Copilot source attestations for shared
environment and sidecar code changes. Keep their provider payloads
unchanged and retain prior identities for decoding.
- Redact destination credentials throughout launch diagnostics and HTTP
logs.
- Add the production settings form, Storybook states and journey,
documentation, and feature/access map entries.

## Verification

- Real managed local Codex 0.160.0 and Claude Code 2.1.286 tasks
exported to Braintrust. API readback and the Braintrust UI showed the
test records. A real remote Daytona Codex task exported through the
legacy `exec` path.
- Live tests exercised content capture off, export disabled, warm
conversation reuse, and a destination that accepted connections but
never replied. Agent work succeeded and export requests stopped at the
configured three-second timeout. The UI connection test returned “Test
payload accepted”; the saved API key stayed masked after reload.
- All current-head CI test lanes, typechecks, packaging/build checks,
and required aggregates passed on
`71e994b2be9c8fb6d6e938a4ad7cf4c95fdafe31` ([CI
run](https://github.com/paperclipai/paperclip/actions/runs/39319194318)).
Greptile completed on that exact commit at 5/5, with zero unresolved
review threads. The additional optional Paperclip Review check remains
queued waiting for its assigned agent.
- Local workspace typecheck and build passed. Focused verification
included 239 combined runner tests, 789 API/native-session tests, 107
Codex home/ACP/sandbox-site tests, 204 feature/privacy tests, and the
affected Rust suites. Migration, token, map, Storybook catalog, and
Storybook build checks passed during acceptance. These checks ran at the
relevant implementation or merge boundary; the current-head CI is the
complete final gate.
- Managed AI credential projections now receive enabled and disabled
company export settings; genuine caller-owned Codex homes remain
untouched. Two regressions reproduced the skip before the repair. All
276 engine/lifecycle tests and adapter-utils typecheck pass afterward.
The launched TOML retains provider/MCP settings, credential continuity,
and redaction.
- Settings saves, removal, and the real company-deletion owner acquire
secret mutation ownership before database locks. PostgreSQL NOWAIT
probes cover all three paths; concurrent save/rotation/removal/deletion
requests settle correctly. The affected suites passed 137 tests,
followed by all 32 service/connection cases after expanded deletion
coverage. Server and Codex adapter typechecks pass.
- Codex source profiles use the effective CLI argument list. A spawned
fixture CLI reproduced both ignored-argument failures; all three
precedence/fallback cases now pass.
- Real tar staging reproduced remote conversation loss in both API-key
and subscription modes before the repair. Both now pass: changing
managed settings preserves sessions, archives, native indexes, and
refreshed subscription credentials. A corrupt upload preserves the prior
config and conversation.
- Preserved failure evidence: initial CI caught stale Pi/Copilot source
attestations, a missing test-harness helper, and packaging fixture/probe
mismatches; those were corrected and covered by focused checks.
Superseded heads had intermittent attachment-composer timeouts despite
successful uploads; [the original trace and screenshot remain
available](https://github.com/paperclipai/paperclip/actions/runs/39309701089/artifacts/12215109203).
The same attachment case passed on the final head: [25/25 tests in shard
3](https://github.com/paperclipai/paperclip/actions/runs/39319194318/job/118288637051).
- A connection-fault fixture also exposed a stale pooled socket after
its hang case; closing fixture responses isolated scenarios and the full
affected suite passed.
- The broad local test run was not clean: database resource failures,
timeouts, an older cached module graph, and a stale optional-resource
manifest assumption were recorded. Focused checks passed after the
documented fixes. This does not establish a clean full local pass.
- Native remote Codex, remote Claude, and the full ACPX Codex bridge
remain unqualified. The direct Codex resume test does not establish full
bridge acceptance.

## Risks

- Native exporters perform network requests from each execution
environment. A server connection test does not prove remote
reachability.
- Prompts, assistant responses, and tool content are enabled by default
when supported. Native caps and harness-specific controls still apply.
- Caller-owned Codex homes are unsupported for automatic configuration
and remain untouched.
- Native harnesses and their tool processes can inspect export
credentials. The settings form and documentation advise using
ingestion-scoped credentials. Retained headers remain bound to their
saved scheme, host, and port; changing origin requires fresh values or
removal.
- Migration 0357 adds a company-scoped row and secret reference. It is
safe to reapply. Secret removal and company deletion use the existing
secret lifecycle.
- Slow or unavailable destinations must not block task settlement.
Export settings take effect at the next execution preparation boundary.

## Model Used

OpenAI Codex, GPT-6, with reasoning, tool use, code execution, and
browser verification. The exact serving model ID and context window size
are not exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-19 15:36:04 -05:00
DottaandPaperclip b294abb9eb perf(server): append ordinary run events in one statement (#16569)
Ordinary run events now validate their binding, allocate a sequence and insert the decoded receipt in one atomic PostgreSQL statement. Native replay and retry-exhaustion transactions keep their existing behavior.

Verification: exact-head full CI/E2E, Superagent, legacy security and Apex 5/5 passed. PostgreSQL concurrency, rollback and payload controls pass. Controlled network-delay comparison reduced median event persistence from about192ms to48ms; no staging response-time claim is made before deployment.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-16 08:51:01 -05:00
Nicky LeachandPaperclip ec3eb1968b feat(runner): expose credential updates through PRP (#16463)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Runner uses PRP to send harness events to the host.
> - A harness can replace its stored OAuth tokens during a run.
> - The host has no PRP signal that tells it to read the updated
credential.
> - This pull request adds a durable credential update event and
observers for supported stores.
> - The host can react before it acknowledges the event. Token values
stay outside PRP.

## Linked Issues or Issue Description

**Subsystem affected**

`packages/paperclip-runner`: native provider execution, PRP schemas, and
host event delivery.

**Problem or motivation**

A Paperclip host that consumes PRP needs to react when a harness changes
its refresh-token credential. Existing credential copy-back paths do not
provide a common event during a warm session.

**Proposed solution**

Add `auth.credentials.updated` as a P0 runner event. Include only the
provider, binding ID, revision, and detection method. Observe native
Codex and Codex/Grok ACPX credential files. Retain each pending event
across runner restart until the durable outbox accepts it. Raw PRP
consumers opt in with `observeCredentials: true` before session open.
The bundled transport opts in for fresh sessions.

**Alternatives considered**

`harness.diagnostic` would mix a runner observation with harness
diagnostics. Sending token values through PRP would expose credentials
to event logs. Both approaches are unnecessary.

**Roadmap alignment**

I checked `ROADMAP.md`. This change extends the existing Runner protocol
and does not duplicate a planned core feature.

**Additional context**

Refs: #11094, #14056, #12696. These describe credential copy-back and
staging work. This PR adds the notification contract. It does not
replace those private credential channels or claim to fix their full
scope.

## What Changed

- Add strict credential update payload validation to PRP v1, v2, and v3.
Regenerate validators, the manifest, and the event catalog.
- Watch supported credential directories with `notify`. Add a periodic
read as a fallback and check at terminal and close boundaries.
- Persist a private baseline and pending event together. Notify on
account identity changes and retain the new baseline for later
refreshes. Ignore incomplete files, unsafe files, and metadata changes.
- Forward the event through the host transport. Test commit-before-ACK
delivery and replay.
- Commit pending runner observations before suspend and shutdown
commands complete. Preserve the provider event backlog.
- Create the private ACPX runtime directory before configuring
observation. Cover fresh session startup with observation enabled.
- Fence asynchronous PRP commits to the current authenticated connection
and run authority. Ignore replaced callbacks while preserving the
maintenance drain of admitted work.
- Deliver observations found during final provider cleanup before
clearing terminal recovery state. Retain the outbox and recovery state
when the host ACK is lost.
- Advance the pending Copilot candidate profile to v19 for the changed
shared PRP source hashes. Preserve its v18 history and pending
qualification status. Include the current fixture in the Docker build
context.
- Commit credential updates through an awaited host callback before PRP
ACK, including during idle and close. Persist them in a separate run-log
source namespace with company, agent, session, and runner checks.
- Preserve public event metadata through server redaction. Register the
event in task run details.
- Document opt-in behavior, supported stores, event semantics, and the
local run-log path.

## Verification

- All 45 durable runner control tests and 24 native executor integration
tests pass.
- Ten Rust credential observer tests pass. They cover atomic
replacement, polling, recovery, account-only changes and later refreshes
for Codex and Grok, unsafe files, fresh runtime creation, and the Grok
close handoff. Account identity stays private.
- The full Runner TypeScript suite passes: 3,565 tests in 221 files. An
additional 12 tests are skipped by the suite.
- All 214 focused tests pass for the contract, Codex event routing,
catalog, and durable host delivery.
- All 13 Node protocol contract tests pass.
- The exact Docker context integrity command passes: `docker buildx
build --file .github/docker-context-checks.Dockerfile .`.
- Rust formatting and generated protocol checks pass.
- `pnpm -r typecheck` and `pnpm build` pass. The full local `pnpm
test:run` was attempted with CI-compatible `init.defaultBranch=master`,
then stopped after a reproducible failure outside this change:
`git-workspace-expiration.test.ts` expects one re-expired workspace but
gets zero. The same case fails in isolation on this macOS checkout. Full
repository validation is provided by CI; the focused Runner and host
tests pass locally.
- All 253 native transport and restart tests pass against the final
release binary.
- All 3 durable recovery tests and 139 candidate profile and native
admission tests pass.
- All 41 host redaction and task protocol surface tests pass. `pnpm
check:token-gates` passes.
- All 467 CI policy tests pass, including the 15 CI profiling tests. The
upstream secret-reference test fix is retained from master. No workflow
changes are included.

- The Apex host-delivery fix passes 381 targeted Runner tests, six host
database tests, repository typecheck, and the production build. The
Runner tests cover commit-before-ACK, retry after a rejected host
commit, warm attachment, and close after the turn queue ends.
- Prior head `2f125cfcb` passed CI, standard Greptile 5/5, and Apex 5/5.
One CI shard was interrupted by GitHub runner shutdown; its single rerun
passed. Both Apex findings are addressed: idle/cleanup host delivery and
late callback reconnect fencing. Current head `5db78f082` passes CI (56
successful statuses and four optional skips) and standard Greptile 5/5.
Apex review of the account-change update is 5/5 on this exact head, with
no actionable issues.
- All 121 durable control-plane and credential callback tests pass,
including account-only change delivery, subsequent refresh on the new
account during close, host commit retry, late success/failure races,
forward cursor delivery on a replacement connection, and the existing
maintenance retirement contract. All 27 native provider and durable
recovery integration tests pass after the account-change update.

## Risks

- File changes signal a stored credential update. They do not prove that
an OAuth refresh succeeded. Changes between reads can be combined.
- Unix Codex and Codex/Grok ACPX stores are supported. Other harnesses
and credentials held only in memory need their own integration.
- Hosts must update their event schemas before opting in. Older hosts
omit the flag and receive no new event. Older runners ignore the flag.
- The host must use its existing private credential channel and account
checks to read or copy the token. Account switches trigger notification
but do not authorize accepting the new account. The event grants no
credential access.
- Tests use synthetic credentials. No live OAuth refresh has been
forced.

## Model Used

OpenAI Codex, exact model ID `gpt-6.1-sol`, with high reasoning effort,
repository inspection, tool use, and code execution. The session 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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-15 14:49:00 -07:00
DottaandPaperclip db11d899cf fix(native): recover remote sessions while preserving warm runs (#16451)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native agents keep remote processes and conversation history across
warm turns.
> - A completed turn can leave handoff authority only in controller
memory.
> - Losing that controller can block a follow-up even while its sandbox
survives.
> - This pull request verifies durable ownership and reconnects the
exact prior process before handing it to the next run.
> - Healthy follow-ups stay warm. Recovery preserves saved work without
replaying a completed turn.

## Linked Issues or Issue Description

**What happened?**

A remote warm turn can finish without suspended backup authority. A
replacement controller rejects that prior owner even when its exact
process and completed checkpoint remain. A single unavailable identity
probe can also stop an otherwise valid reattachment.

**Expected behavior**

Keep healthy follow-ups warm. Recover a completed session after proving
that its controller stopped and that the successor holds current
authority. Reuse compatible processes; otherwise save and resume the
conversation. Retry unavailable identity reads within one shared
deadline.

**Steps to reproduce**

Use `doc/testing-native-remote-continuity.md`. Its Linux cases execute
real turns through PostgreSQL, TLS ingress, and the Rust runner. They
kill a separate controller process, recover old checkpoint metadata, and
change process configuration. The provider is deterministic.

Related: #16432 and #16441 fix live warm-owner handoffs. #16341 adds
recovery diagnostics. #14695 covers provider process retention.

## What Changed

- Preserve warm process reuse between healthy remote turns.
- Verify a committed prior result, stopped controller, matching
checkpoint and sandbox, and current task/coordinator ownership before
recovering an orphaned warm session.
- Reconnect through the prior run route, then use the existing
authenticated run attachment to change authority.
- Persist a process-configuration digest. Close and save recovered
sessions when old or changed configuration cannot support reuse.
- Retry unavailable state, marker, and process reads within one
30-second deadline. Keep signals and definitive identity failures
single-attempt.
- Record closed recovery diagnostics, including unreadable or malformed
saved state.
- Retire the recovery-only ownership check after verified attachment, so
later warm turns use their current authority.
- Add real-process Linux regressions and refusal controls.
- Expose the existing source-only file-recovery form for retained native
holds, with the same board authority, evidence, and exact stop-receipt
requirements as legacy holds. Recording recovered files does not change
task status or replay a run.

## Verification

- `pnpm -r typecheck` passed; the server check was repeated for the
third-turn fix.
- `pnpm build` passed after the final UI change. The UI typecheck, token
gates, and map checks also pass.
- Four Linux continuity cases passed: warm reuse, controller loss,
legacy checkpoint metadata, and changed configuration. Each creates one
conversation and starts exactly three turns, including a healthy
follow-up after recovery. The fixture uses expired prior leases; the
third turn keeps the runner and provider processes.
- Greptile caught the retained recovery gate; the third-turn regression
now covers it. An initial test omitted the heartbeat’s saved input and
left a test process after failure; the fixture was corrected and its
disposable container reset before the passing run.
- The current focused executor/restart group passes all 749 tests,
including the original failure classifications.
- Native file-recovery and UI tests pass (52 tests), including blocked
tasks and both native and legacy holds.
- After merging current master and retaining its equivalent CI
expression check, the CI policy tests pass (15 tests) and all four
three-turn Linux continuity cases pass again on
`dec418ae55ff21b4fb067132613658cce445f181`.
- `pnpm test:run` completed its general server group with 20,572 passes,
103 skips, and three failures: the skill-audit child process and two
HTTP test timeouts. The same three suites then passed in isolation (89
tests) without code changes. That local command stopped before later
groups; the complete final-head CI, including general, serialized,
Runner, build, and browser gates, is green.
- Greptile reviewed the combined head
`dec418ae55ff21b4fb067132613658cce445f181` at 5/5 with no actionable
findings; both earlier findings are resolved. All final-head CI gates
and the security scan passed. Staging deployment and retained-incident
disposition remain deployment acceptance work.

## Risks

- Recovery requires exact durable evidence. Live conflicting
controllers, replacement sandboxes, Stop/pause, and uncertain effects
must remain blocked.
- After controller loss, tool-contract or process-configuration changes
can restart the provider. Normal healthy follow-ups retain their
processes.
- The new idle-recovery path has Linux fake-provider evidence. Live
Codex/OpenCode and retained quarantined-state recovery still need
staging qualification.
- No database schema, telemetry, or runner protocol changes.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository inspection, code
execution, and browser tools. The session does not expose a more
specific model version 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 (targeted checks and
isolated reruns; full-run limitation recorded above)
- [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
`dec418ae55ff21b4fb067132613658cce445f181`
- [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>
2026-10-15 13:26:09 -05:00
DottaandPaperclip cccde7122b Trace execution preparation and durable response persistence (#16419)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The server prepares an execution environment before it starts an
agent.
> - Operators need to distinguish preparation, provider wait, and
response persistence.
> - Existing preparation phases start separate traces, and the native
session trace ends before the final response is saved.
> - This pull request adds an execution-attempt trace and detailed spans
at the existing work boundaries.
> - The benefit is a measurable startup and settlement path without
changing execution policy.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Operator-configured OpenTelemetry tracing for agent preparation,
startup, and durable response storage.

**Subsystem affected**

Server orchestration, native Runner startup, and the Honeycomb run link.

**Current behavior**

Preparation phases start independent roots. Workspace lock waits,
authorization groups, runtime asset work, provider-pack fallback, and
the gap after provider completion are hard to attribute.

**Proposed behavior**

Use a server-owned async context for each execution attempt. Parent
nested work to that attempt. Link the separately sampled native session
because its ingress and queue history can begin earlier. Record
transaction entry, lock waits, grouped authorization, asset preparation,
artifact fallback, copyback, and response commit.

**Reason and benefit**

Operators can identify expensive work from recorded boundaries instead
of inferring the cause from elapsed time. Nested and concurrent
durations remain visible and must not be added together.

**Breaking changes**

No API or execution-policy change. New Observability span names and
closed count attributes are documented. The existing local
run-performance schema also records the additional provider-pack and
harness measurements.

Refs: #16178, #16205, #16330.

## What Changed

- Add `task.execution_attempt` with same-run async ownership,
independent root sampling, and native-session links.
- Measure workspace transaction entry and settlement, advisory and row
waits, source scans, reader groups, notifications, and writes.
- Measure runtime selection and each concurrent instructions, skills,
and MCP branch. Separate manifest, cache, materialization, and
verification work.
- Measure provider-pack discovery, verified reuse or upload, final
verification, and remote harness state checks.
- Measure workspace settlement, accounting, liveness, response
authorization, comment commit, and publication enqueue.
- Preserve the silent endpoint-off path, including when another library
has registered a tracer. Bound repeated detail spans to 16 per name per
attempt.
- Extend the Honeycomb run link and document the actual coverage and
remaining gaps.

## Verification

- After resolving the conflict with retained-reader batching on
`b98cdaea531c8447637cd4dcc19c61b3f5f41844`, all 61 targeted query,
authorization, workspace-preparation and trace-boundary tests passed.
The server typecheck passed. Reader tests still bound query count, avoid
loading retained run payloads and enforce fresh project grants on every
publication attempt.
- Before that base integration, the tracing/exporter group passed 855
tests with 2 optional-SDK skips across 10 files and no inherited OTLP
endpoint. It covers real PostgreSQL waits/rollback, durable response
commit, assembled heartbeat preparation, concurrent parentage, sampling
and tracer failure isolation. Native workspace/history tests passed 23
cases. These results remain supporting evidence for the unchanged
tracing paths.
- Full workspace typecheck and build passed during implementation. Full
sharded CI, release validation, E2E and Apex 5/5 passed on prior head
`8cebad80f3856dbcff6f3a6d5d34880ff77a20f2`. Fresh CI, security and Apex
review are required on the current head before merge.
- `pnpm check:token-gates` passed. No styles changed.
- Earlier CI runs exposed exact-source-expression assertions changed by
wrappers, missing run hashes on transaction-boundary spans, and sampling
fixtures without an enabled endpoint. The fixes preserve the calls, add
the declared hash and span cap, and enable tracing explicitly in the
sampling fixtures. Focused repairs passed; the full prior-head CI passed
after those repairs. Earlier failed run:
https://github.com/paperclipai/paperclip/actions/runs/38729131366.
- The full local `pnpm test:run` was stopped after the base advanced
during that process. Its mixed-version results are not final-head
evidence. A full local-suite pass is not claimed.
- The old-head Superagent scan stalled at 35%. The current head has a
fresh scan; no security pass is inferred from the old pending result.
- No deployed Honeycomb acceptance is claimed by this code change.

## Risks

- Enabled tracing creates more spans. Repeated detail spans are bounded,
and inventories use group spans.
- The attempt and native session remain linked traces. Historical queue
time can precede the attempt, and nested intervals overlap.
- A committed comment proves durable server storage. Publication enqueue
and realtime dispatch do not prove external delivery or browser paint.
- New custom attributes use only the existing run hash and validated
file/byte counts. No SQL, paths, permissions, content, or exception text
is added.

## Model Used

OpenAI GPT-6 through Codex. The serving model ID and context window size
are not exposed in this session. The agent used reasoning, tools, code
editing, and code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-15 08:13:53 -05:00
Devin FoleyandPaperclip 307c27e3d8 fix(native): reclaim task execution before same-run recovery
Restore the original task execution binding atomically during native same-run recovery. Preserve current owners, stops, pauses, assignments, and dependency waits.

Isolate candidate failures, bound deferral writes, prioritize undated retries, and allow recovery ahead of queued waiters without premature queue promotion. Add PostgreSQL attachment, ownership, contention, fairness, and queue regressions.

Verification: 99 focused tests, workspace typecheck, build, full current-head CI, and Apex 5/5.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-15 01:53:26 -07:00
DottaandPaperclip af4191f45e fix: keep workspace notices from suppressing final replies (#16400)
Keep stored system notices visible without letting them occupy the final reply slot. Read presentation metadata in live and recovery selection, and cover explicit replies, progress receipts, company boundaries, and native replay.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-15 02:39:07 -05:00
76164b53cb fix(heartbeat): retry a legacy run that lost its lease before dispatch (#16358)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The heartbeat runner executes legacy agent runs. A controller lease
watchdog keeps durable ownership of each legacy run, and it aborts the
run when it loses the lease.
> - #16084 records a lease loss as a `controller_lease` cancellation
when the adapter returns or throws. It does not cover a lease loss
during setup, before the adapter starts.
> - In setup, the abort reason goes to the setup catch. The catch
records an opaque `setup_failed` and sends the raw
`LegacyControllerLeaseLostError` to Sentry with no cause label.
> - Issue continuation recovery treats `setup_failed` as non-retryable.
The task moves to `blocked`, but no provider work started.
> - This pull request records a lease loss before dispatch as
`controller_lease_lost`, with the same `controller_lease` evidence.
Recovery retries it as transient infrastructure, and so does the
accepted-interaction (plan approval) continuation retry.
> - The benefit is that a host lease loss during setup no longer strands
the task, and the next Sentry event shows which renewal failure caused
the loss.

## Linked Issues or Issue Description

Refs #16084
Refs #16355
Refs #16359

**What happened?**
A staging server reported `LegacyControllerLeaseLostError: Legacy
controller lease lost` from the heartbeat. The run failed during setup
with `setup_failed`. Continuation recovery then said "non-retryable
failure on this issue's continuation run (`setup_failed`)" and moved the
task to `blocked`. A scanner had to move the task back to `in_progress`
by hand.

**Expected behavior**
A lease loss before adapter dispatch is a host infrastructure stop. The
run records the `controller_lease` cause, like the adapter-phase path.
The run keeps `providerWorkStarted: false`. Recovery retries the
continuation with its bounded transient budget, and it does not block
the task.

**Steps to reproduce**
1. Start a legacy run that has a controller lease.
2. Before the adapter dispatch, make the lease renewal fail: the update
matches no row, the query errors, or the lease deadline passes.
3. `controllerLease.assertOwned()` throws the abort reason in workspace
preparation or before dispatch.
4. Before this change: the run is `failed`/`setup_failed`, `resultJson`
has no `cancellation`, and continuation recovery blocks the task. After
this change: the run is `failed`/`controller_lease_lost` with
`cancellation.source: "controller_lease"` and a closed cause, and
continuation recovery classifies it as `transient_infra`.

**Additional context**
#16355 handles a different path: a restart orphan that the next boot
reaps as `process_lost`. This change handles a live controller that
loses its own lease during setup. This change does not find the root
cause of the lease loss. The closed cause label in the next Sentry event
gives that fact.

## What Changed

- `server/src/services/heartbeat/run-completion.ts`: `failRunSetup`
receives the execution abort signal. When the abort reason is a
`LegacyControllerLeaseLostError`, it writes error code
`controller_lease_lost`, adds the `controller_lease` cancellation
evidence, and keeps the existing bootstrap recovery facts. The agent
goes back to idle, not `error`, because the stop is a host fault. This
applies only before provider dispatch: `failRunSetup` also receives
`providerDispatchStarted`, and a setup-path error after a legacy adapter
or native dispatch began keeps `setup_failed`, so started work is never
retried.
- `server/src/services/legacy-controller-lease.ts`: add the
`CONTROLLER_LEASE_LOST_FAILURE_CODE` constant.
- Merged with #16359 (lease-loss timings): the setup failure keeps its
`resultJson.controllerLeaseLoss` timings and adds this PR's
`controller_lease` cancellation from the abort signal. #16359's setup
test now aborts the signal first, as `assertOwned()` does, and expects
`controller_lease_lost`.
- `server/src/services/heartbeat.ts`: pass the execution abort signal
and `providerDispatchStarted` (`legacyAdapterEntered ||
nativeDispatchStarted`) to `failRunSetup`.
- `server/src/services/recovery/service.ts`: add `controller_lease_lost`
to the transient-infrastructure continuation codes (3 bounded attempts
with backoff).
- `server/src/services/heartbeat/retries.ts`: add
`controller_lease_lost` to the accepted-interaction continuation retry
allowlist, next to `process_lost`. Without it, a plan-approval
continuation that lost its lease before dispatch was escalated and
blocked before generic recovery could see it.
- `server/src/services/heartbeat/run-lifecycle.ts`: add
`controller_lease_lost` to the pre-adapter setup codes, so the comment
policy does not queue a missing-comment retry.
- `doc/run-log-events.md`: document the new error code.
- Tests: setup lease loss when the abort reason is thrown, and when a
setup step throws a different error after the abort. A unit test shows a
lease loss after dispatch keeps `setup_failed`. A classifier test shows
`controller_lease_lost` is transient and `setup_failed` stays
non-retryable. Two assembled heartbeat tests
(`heartbeat-process-recovery.test.ts`) lose the real lease during
workspace preparation: one for a plain issue continuation (one
`transient_failure` retry) and one for an accepted plan-approval
interaction continuation (one `interaction_continuation_infra_retry`
retry). Both check the saved run, no adapter dispatch, and the issue
still `in_progress`.

## Verification

- `cd server && npx vitest run
src/services/heartbeat/run-completion.test.ts`: 25 passed (embedded
Postgres), including the new post-dispatch test.
- `cd server && npx vitest run src/services/run-cancellation.test.ts
src/services/recovery/provider-failure-classification.test.ts`: passed
on the first head (29 with run-completion; 25 classifier).
- `cd server && npx vitest run
src/services/legacy-controller-lease.test.ts`: 13 passed (needs `pnpm
--filter @paperclipai/plugin-sdk... build` first).
- After merging master (head `2148b27`): `run-completion`,
`legacy-controller-lease`, `run-cancellation`,
`provider-failure-classification` and `run-failure-report` suites, 310
passed; `heartbeat-process-recovery.test.ts -t "lease|dispatch"`, 31
passed.
- Head `c52825c`: `retries`, `run-completion`,
`heartbeat-retry-scheduling`, `legacy-controller-lease` and
`provider-failure-classification` suites, 134 passed;
`heartbeat-process-recovery.test.ts -t "lease|dispatch|interaction"`, 41
passed.
- Negative controls: with the lease-loss branch in `run-completion.ts`
disabled, the plain-continuation assembled test fails (`setup_failed`
instead of `controller_lease_lost`). With the `retries.ts` allowlist
change reverted, the accepted-interaction assembled test fails because
no retry is scheduled.
- Not run locally: full `tsc` and `pnpm build`. The sandbox has a 4 GB
memory limit and the OOM killer stops `tsc`. CI is the typecheck and
build gate.

## Risks

- Low risk. The change applies only when the execution abort reason is a
`LegacyControllerLeaseLostError` in the setup catch before dispatch.
Other setup failures keep their current codes.
- The retry uses the existing continuation recovery budgets and
admission checks (agent state, budget, pause). The
interaction-continuation retry keeps its existing 2-attempt budget. The
retry is a new run. The revoked controller cannot dispatch, because its
renewal compare-and-set fails.
- A run that the reaper settles first keeps the reaper outcome.
`setRunStatusIfRunning` makes the first terminal write win.
- Sentry still gets this unexpected stop, with the closed
`controllerLeaseLossCause` label. If the lease loss is frequent, the
event volume does not change.

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with
tool use (shell, file edits, 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
- [ ] I have run tests locally and they pass (targeted suites pass;
typecheck/build not possible in this sandbox, see Verification)
- [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: Devin Foley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:31:42 -07:00
ccc77c5b5e fix(server): record legacy controller lease-loss timings (#16359)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The heartbeat runner executes legacy agent runs, and a controller
lease watchdog keeps durable ownership of each run
> - The watchdog renews the lease every 10 seconds and stops the run
when a renewal does not finish within the 60-second lease
> - A staging run failed in setup with `LegacyControllerLeaseLostError`
and the `renewal_deadline` cause, after about eight minutes of
preparation and with no server restart
> - The setup failure path did not record the cause, and no evidence
showed why the renewal did not finish: a stalled database query or a
blocked event loop
> - This pull request records bounded watchdog timings with each lease
loss, on the setup failure path and on the cancellation path
> - The benefit is that the next event shows which of the two causes
stops renewal, which is the missing fact to fix the root cause

## Linked Issues or Issue Description

Refs #16084 (added the closed lease-loss cause for cancelled runs)
Refs #16355 (retries a run that a restart stopped before dispatch; a
different path)

**What happened?**
A legacy `paperclip_runner` run on staging failed with `setup_failed`
and the message "Legacy controller lease lost". The stack shows the
watchdog deadline timer (`renewal_deadline`). The run was in
`executionStage: "preparing"` for about 467 seconds. The server started
about 11 minutes before the run, so this was not a restart. The run
record and the Sentry event do not show the lease-loss cause, because
the setup failure path does not read it. Nothing shows if a renewal
query was in flight at the deadline, or if the deadline callback ran
late.

**Expected behavior**
When the watchdog loses the lease, the run records the closed cause and
the watchdog timings. This applies when the loss fails setup and when it
stops the adapter. The failure report contains only the closed cause and
bounded numbers.

**Steps to reproduce**
1. Start a legacy run with a controller lease.
2. During setup, make a lease renewal query hang for more than 60
seconds, or block the event loop past the deadline.
3. Let the watchdog abort the run.
4. Read `resultJson` on the failed run. Before this change, it has no
lease-loss evidence.

## What Changed

- `legacy-controller-lease.ts`: The watchdog tracks the last successful
renewal, the renewals in flight, the scheduled deadline, and the renewal
count. `LegacyControllerLeaseLostError` now carries `leaseLossTiming`:
`sinceRenewalMs`, `renewalPendingMs` (or `null`), `deadlineLateMs` (only
for `renewal_deadline`), and `renewalCount`. Ownership behavior does not
change.
- `run-cancellation.ts`: Add the `LegacyControllerLeaseLossTiming` type
and `readControllerLeaseLoss()`. The reader accepts only a closed cause
and non-negative finite numbers, and clamps each value to 24 hours.
- `heartbeat/run-completion.ts`: Persist
`resultJson.controllerLeaseLoss` with the cause and timings. This
applies to a lease loss that stops the adapter and to a lease loss that
fails setup (the thrown abort reason).
- `run-failure-diagnostics.ts`: Report `controllerLeaseLossCause` and
the four bounded timing fields from `controllerLeaseLoss`. A setup
failure now gets the cause label too.
- `doc/run-log-events.md`: Document the new record and how to read the
timings.
- Tests: watchdog timings for a stalled query, a blocked event loop, and
a failing query; completion records the evidence for an adapter stop and
for a setup failure; the failure report clamps values, drops unknown
keys, and drops a record without a closed cause.

## Verification

- `pnpm --filter @paperclipai/server exec vitest run
src/services/legacy-controller-lease.test.ts
src/services/heartbeat/run-completion.test.ts
src/services/__tests__/run-failure-report.test.ts
src/services/run-cancellation.test.ts`
- I could not run these tests locally. The sandbox has a 4 GB memory
limit, and `pnpm install` was stopped by the out-of-memory killer during
lockfile resolution. CI is the test run for this change.
- After deployment, a new lease loss shows
`controllerLeaseRenewalPendingMs` and `controllerLeaseDeadlineLateMs` in
the Sentry execution context. A pending time near 50 seconds or more
points at the database. A large deadline lateness points at the event
loop.

## Risks

- Low risk. This change adds diagnostics. It does not change when the
watchdog stops a run, and it does not prevent the lease loss.
- The new `resultJson.controllerLeaseLoss` field is server-written.
Readers ignore unknown values. Old rows do not have it.
- The failure report gets five closed or numeric fields. It gets no
reason text, IDs, or user content.
- The event-loop test blocks the loop for about 80 ms to make the
deadline late. It uses real timers, so a very slow CI host makes the
lateness larger, not smaller.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with
tool use (shell, file edits, Sentry and GitHub connectors). The exact
context-window size is 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
- [ ] I have run tests locally and they pass (not possible in this
sandbox; see Verification)
- [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: Devin Foley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:52:23 -07:00
cafa25029c fix(native-runtime): recover unclaimed remote startup (#16349)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The native runtime preserves task execution across server restarts.
> - Remote workspace preparation can finish before the executor claims
its first provider attempt.
> - The recovery classifier required a provider checkpoint even when the
durable coordinator proved that execution had never started.
> - A deployment during this interval therefore left safe startup work
on a permanent recovery hold.
> - This pull request admits that narrow startup state to the existing
same-run bootstrap path. Existing execution and ownership checks remain
in force.
> - The original preparation yields when recovery owns the coordinator.
It cannot fail or clean up the recovered run.
> - Tasks can continue after this interruption without an operator retry
or a duplicate provider turn.

## Linked Issues or Issue Description

**What happened?**

A server deployment interrupted a prepared remote workspace before the
native executor's first claim. The coordinator had attempt zero,
controller generation zero, and no owner, process, provider event, or
checkpoint. Restart classification returned
`ambiguous_remote_recovery_evidence` and blocked the task.

**Expected behavior**

The server should recognize this durable never-claimed state and
continue the original run through normal execution admission. A prior
claim, uncertain ownership, or contradictory execution evidence must
retain the recovery hold.

**Steps to reproduce**

1. Prepare a remote workspace for a native run.
2. Stop the controller before the executor commits its first claim.
3. Restart the controller and classify the running native task.
4. Observe that the old classifier blocks it because no provider
checkpoint exists.

Related: #16341 preserves remote recovery failure reasons. #16344
strengthens remote restart qualification. Neither changes admission for
an untouched remote startup.

## What Changed

- Require a server-owned fencing version written only with fresh native
identities. Older unmarked preparations stay held during rolling
upgrades; caller profile data cannot add that marker to an existing run.
- Leave the current server's active preparation with its existing
execution.
- Yield through the controller-detachment path when a different
execution owns the coordinator lease. Lease expiry does not grant
takeover authority.
- Preserve run-scoped gateway tokens during detachment so stale
preparation cannot revoke the recovered worker's tools.
- Admit a valid prepared remote workspace only when the locked
coordinator proves attempt and controller generation zero, no owner, and
no prior session or execution evidence.
- Check launch receipts and provider source events before granting
bootstrap recovery.
- Keep the original run and session. Preserve atomic recovery claims,
executor durable-state checks, and normal admission gates.
- Add real PostgreSQL regression tests for competing controllers and
seven contradictory evidence cases.
- Update recovery documentation, the run-log reason, and both capability
maps.

## Verification

- The competing-controller regressions failed before the ownership fix
and pass for both live and expired foreign leases.
- On the final revision, 77 runtime-selection, restart recovery, real
PostgreSQL, and heartbeat boundary tests pass with no skips, including
all local runner process fixtures. The full native executor file passes
all 698 tests.
- Another 91 live-run route tests pass locally. An earlier CI timeout in
that unrelated file passed on retry; the complete final-head CI passed
without that failure.
- `pnpm -r typecheck`, `pnpm build`, `node scripts/check-maps.mjs`, and
`git diff --check`: passed locally. The map check verifies structure
only.
- Local `pnpm test:run` did not finish: an early attempt had
build-related collection failures, and later full-suite execution was
stopped after complete CI passed. Targeted suites passed after build
preparation. This is not a local full-suite pass.
- The complete [PR
workflow](https://github.com/paperclipai/paperclip/actions/runs/38669996667)
passed on `2baffb3652bfeb4705a30ca3706fbda6c3d72aa4`, including all test
shards, build/typecheck, runner verification, and browser E2E. All 55
applicable review/security/CI checks succeeded; two Storybook checks
were skipped.
- [Greptile
Apex](https://github.com/paperclipai/paperclip/pull/16349#issuecomment-6180848957):
5/5 on the same final head, with no unresolved findings.
- No live remote provider restart is claimed by the database regression.

## Risks

- A bare executor can no longer take a foreign coordinator lease after
expiry. A verified recovery claim must transfer authority first.
- Existing preparations without the server-owned fencing marker retain
their recovery hold during rolling upgrades.
- Incorrect admission could duplicate provider work. This path requires
a positive durable never-claimed receipt, rejects contradictory launch
evidence, and keeps the executor's existing durable-root verification.
- Previously claimed or incomplete remote recovery evidence remains
blocked. This change does not clear historical terminal holds.
- No schema, API, dependency, or UI changes.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, tool use, code execution,
and browser inspection. The exact serving snapshot ID and context window
size are not exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Devin Foley <devin@paperclip.ing>
2026-10-14 19:32:39 -07:00
a64969b288 fix(server): attribute legacy runs stopped by controller lease loss (#16084)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The heartbeat runner executes legacy agent runs. A controller lease
watchdog keeps durable ownership of each legacy run.
> - When the watchdog loses the lease, it aborts the adapter. The abort
reason was a plain `Error`, and nothing recorded why the run stopped.
> - Completion then saved the cancellation as source `unknown`. Sentry
got an unexpected cancel that it could not tell apart from other
unrecorded stops.
> - We saw this on a sandboxed Grok run. The system cancelled it after
about 22 minutes with `cancellationSource: unknown`. Workspace copy-back
then failed. Every `cancelRun` path records a stop request, so an
unrecorded stop this late points to the lease watchdog.
> - This pull request records a lease loss as its own cancellation
source, with a closed cause label.
> - The benefit is that the next event shows whether the lease loss
caused the stop, and which renewal failure caused it. That is the
missing fact to fix the root cause.

## Linked Issues or Issue Description

Refs #15790
Refs #15920

**What happened?**
A legacy sandbox run was stopped by the system while it was working. The
run record and the Sentry diagnostics show `cancellationSource:
unknown`, with no initiator. The controller lease watchdog aborts the
adapter with a plain `Error("Legacy controller lease lost")`. Completion
does not read the abort reason, so it records the stop as `unknown`.

**Expected behavior**
When the host stops its own adapter because it lost the controller
lease, the run records that cause. Diagnostics name the cause with a
closed label. A stop that a user or the control plane requested earlier
keeps its recorded source.

**Steps to reproduce**
1. Start a legacy run that has a controller lease.
2. Make lease renewal fail: the renewal query errors, the update matches
no row, or the lease deadline passes.
3. Let the adapter return its cancelled result.
4. Read `resultJson.cancellation` on the run. Before this change,
`source` is `unknown`.

**Agent adapter(s) involved**
Not adapter-specific (core bug). We saw it with `grok_local` in a
sandbox.

**Additional context**
This change adds diagnostics. It does not prevent the lease loss. #15920
retries a transient copy-back failure. It does not help here, because
the stopped sandbox rejects every retry.

## What Changed

- `legacy-controller-lease.ts`: The watchdog aborts with a new
`LegacyControllerLeaseLostError`. The error carries a closed cause:
`renewal_rejected`, `renewal_deadline`, or `renewal_error`.
- `run-cancellation.ts`: Add the `controller_lease` cancellation source.
Add `controllerLeaseLossCancellation()` and
`readControllerLeaseLossCause()`. The reader accepts only the
server-written reason text.
- `heartbeat/run-completion.ts`: When the outcome is `cancelled` and the
abort reason is a lease loss, record a `controller_lease` cancellation
(`expected: false`). This covers both an adapter that returns a
cancelled result (`completeRun`) and one that throws after the abort,
for example through `signal.throwIfAborted()` (`failRun`). A stop
request recorded earlier still takes precedence.
- `run-failure-diagnostics.ts`: Add `controllerLeaseLossCause` to the
execution diagnostics. Only the closed label goes to Sentry, never the
reason text.
- `heartbeat/run-state.ts`: preserve `controller_lease` in the bounded
SQL projection when saved results exceed 64 KB. A database regression
verifies the default run-detail read.
- `canContinueCancelledRun` stays unchanged. It does not accept
`controller_lease`, so a lease loss does not authorize automatic
continuation.
- `doc/run-log-events.md`: Document the new source and cause labels.
- Tests: unit tests for the cause on each watchdog path, completion
tests that record the source when the adapter returns and when it throws
the abort reason (both fail without the change), tests that a recorded
stop request takes precedence, and a test that the Sentry report
contains the closed label and not the reason text.

## Verification

- `cd server && npx vitest run src/services/run-cancellation.test.ts
src/services/legacy-controller-lease.test.ts
src/services/__tests__/run-failure-report.test.ts
src/services/heartbeat/run-completion.test.ts`: 238 passed, including
the embedded Postgres suites.
- I reverted only `run-completion.ts` and ran the new completion test.
It failed with `source: "unknown"`. With the change, it records
`controller_lease`.
- I removed only the `failRun` attribution and ran the new thrown-error
test. It failed without the `controller_lease` record, and passes with
it.
- Final full repository `pnpm -r typecheck` and `pnpm build` pass.

- Final local regression verification: all 247 tests pass across
run-state, cancellation, watchdog, failure reporting, and completion
suites. The large-result test fails before the SQL allowlist change. All
latest-head CI checks pass (54 successes and two intentional skips).
Fresh Apex is 5/5 on `708856c568d07d7b446a4a4af2edc6d9eec7d155` with no
unresolved findings.

## Risks

- Low risk. The change adds a new source value to server-owned JSON. Old
rows keep their values. The readers accept the new value, and no UI code
switches on this field.
- A lease-loss cancel stays `expected: false`, so it is still reported
as unexpected, as before. Now it has a more specific source.
- The Sentry payload gets one new closed label
(`controllerLeaseLossCause`). It does not include reason text or IDs.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with
tool use (shell, file edits, test runs).

OpenAI GPT-6 (`gpt-6`) through Codex added the large-result regression
and SQL projection fix, with shell, file-edit, and test tools. The exact
context-window size is 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: devinfoley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-13 14:50:40 -07:00
DottaandPaperclip fc3b15537b fix(runner): match call_api to existing agent REST permissions (#16040)
Route native call_api through existing agent REST permissions. Preserve admitted credentials across restarts, protect sensitive replies and private snapshots, and compare saved completion with provider history through the same redaction projection. Add permission, privacy and recovery regressions and update the access map and run-log guide.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-13 07:34:15 -05:00
DottaandPaperclip f329b24ee1 feat: give tasks durable project-optional workspaces (#15749)
Give tasks durable company-scoped workspace bindings independent of project assignment, channel defaults and authorized repository preparation. Reuse verified input seeds and sparse output recovery while preserving trust, privacy and controller provisioning boundaries.

Full CI and E2E, Superagent, legacy checks and Greptile Apex 5/5 passed on reviewed head 753680b61c. All review findings are resolved; the PR records focused verification and historical acceptance limits.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-12 20:45:18 -05:00
DottaandPaperclip bf9dbd18a8 fix(runner): recover incompatible Codex models before launch (#15519)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Runner prepares and saves each task's provider configuration
before launch.
> - A sandbox image can have a supported Codex CLI that is too old for
the selected model.
> - The current version check stops that task even when the image can
run a similar model.
> - This pull request selects a compatible model before it saves a fresh
execution.
> - The task continues with a visible warning, and recovery uses the
saved effective model.

## Linked Issues or Issue Description

- Refs #15053. This fixes the same CLI and model mismatch for fresh
remote Runner tasks. It does not change subscription onboarding.
- Refs #14721. This adds a bounded startup choice for Codex CLI
compatibility. It does not add user-defined fallback chains or quota
failover.
- Related PR: #15399 added the model-specific CLI checks that this
change preserves at launch.

## What Changed

- Check the preinstalled remote Codex CLI before saving a fresh
execution that uses a model with a verified CLI minimum.
- Select the closest compatible older model in the same class, then the
stable Runner default. Consider each candidate once.
- Save the effective model before checkpoint selection. Keep the
requested agent and task settings unchanged.
- Add a task warning and a local `runner.model_fallback` run-log event
with both models and the CLI version.
- Share executable discovery with the launch verifier. Preserve explicit
artifact and install settings, saved executions, and existing artifact
checks.
- Add regression tests and document the selection and warning behavior.

## Verification

- Red: the three new provider-configuration regression cases failed
before the fix. They kept the incompatible requested model.
- Red: both rejected-probe cases failed before the review fix. They now
defer to launch verification.
- Green: targeted provider configuration, remote preflight, task
warning, and launch-verifier tests pass (680 tests).
- `pnpm -r typecheck` passes.
- [Full
CI](https://github.com/paperclipai/paperclip/actions/runs/37713759744)
passes on `eb273fc32661526d90a864b42c73a7da61dd62da`: 47 successful
jobs, including all general and serialized Vitest groups, browser
shards, Runner verification, typecheck, build, and the canary dry run.
- Local extended verification: three serialized shards pass (115
suites). HTTP route tests hit intermittent 15-second timeouts. The
authorization suite passes on rerun (130 tests). The document suite
passes on both master and the PR head in the same isolated setup (6
tests). The local general run was stopped after the equivalent full CI
matrix passed.
- `pnpm build` passes.
- Server typecheck and build pass again after the probe-error review
fix.
- `git diff --check` and `pnpm check:module-boundaries` pass.
- Example: request `gpt-6.1-sol` on Codex 0.158.0 to use `gpt-6-sol`; on
0.156.0, use `gpt-5.6-sol`. The warning names the requested model,
effective model, and CLI version.
- This PR has not been deployed to staging.

## Risks

- A fallback can have different capabilities. The task warning makes the
substitution visible. Later runs can use the requested model after the
image CLI is updated.
- Preparation adds two remote commands for models with a verified CLI
minimum. Failed or invalid version probes retain the existing launch
checks.
- This only handles known CLI and model mismatches before a fresh
provider launch. Authentication, capacity, and artifact failures keep
their existing behavior.
- No database migration is required.

## Model Used

OpenAI GPT-6 through Codex, with tool use and code execution. This
session does not expose the exact deployment model ID, context window
size, or reasoning setting.

## 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>
2026-10-08 04:57:26 -05:00
Devin FoleyandPaperclip 465140596f Preserve bounded workspace sync diagnostics across RPC (#15481)
Preserve bounded error codes and HTTP/exit statuses across the environmentSyncOut worker RPC boundary, and revalidate that method-scoped envelope before attaching host restore diagnostics. Keep the original error and all recovery policy unchanged; do not transmit provider payloads or credentials.

Verified real RPC roundtrip and privacy regressions, 95 focused tests, 68 independent tests, full typecheck/build and all exact-head CI. Greptile 5/5 with no unresolved comments.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-07 12:22:53 -07:00
Devin FoleyandPaperclip 06484b3c41 fix: preserve conversation retries through execution cleanup (#15463)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Recovery schedules bounded retries after a provider disconnects.
> - A stopped run can still hold its environment lease while cleanup
runs.
> - Retrying before that lease is released cancels the new run before it
starts and spends another retry.
> - Restoring the task can also send the worker repair instructions from
an already resolved recovery action.
> - This pull request preserves the waiting retry and removes settled
recovery instructions from later wakes.
> - The task can continue after cleanup without an operator repairing
the same incident again.

## Linked Issues or Issue Description

**What happened?**

A legacy conversation run disconnected while it was doing ordinary work.
Its environment cleanup took longer than the retry delay. Two retries
were cancelled before dispatch with `execution_reconciliation_required`.
Those cancellations exhausted the failure budget. After an operator
restored the task, the wake still told the original worker to repair the
runtime and hand the task back to itself.

**Expected behavior**

Cleanup waits preserve the pending attempt. The same retry can continue
after ownership is released, subject to all current gates. Once a
recovery action is resolved or cancelled, subsequent task wakes omit its
repair instructions.

**Steps to reproduce**

1. Fail a legacy conversation run while its environment lease remains in
`pending_cleanup`.
2. Schedule a bounded retry and run promotion before cleanup releases
that lease.
3. Repeat the scheduler sweep. Before this fix, retries promote and then
cancel without starting.
4. Resolve a stranded-task recovery action and build the restored task
wake with that action ID. Before this fix, the wake still includes the
settled repair instructions.

**Paperclip version or commit**

Reproduced with database regressions against `ceabc3bc880` on master.

Related: #15019 restores a skipped assignment handoff after lease
release. #15235 filters stale handoff evidence in the recovery sweep.
This change preserves an existing scheduled conversation retry and
corrects restored wake content. It does not create a new handoff wake.

## What Changed

- Keep an unstarted legacy conversation retry on the same durable row
while prior execution ownership remains active. Recheck after 30 seconds
without increasing retry accounting.
- Return a queued retry to scheduled state if it encounters that hold at
the claim gate. Retain its issue claim and publish the status change.
- Record one local lifecycle diagnostic per blocking run. Remove that
wait marker on promotion.
- Include recovery action metadata only while the referenced action is
active or escalated.
- Return an explicit `waiting` response and the saved schedule when
Retry now meets cleanup. Show the wait inline without a false success or
disabled button.
- Add database, rendered-prompt, route, and UI regressions. Document the
execution and run-log contracts.

## Verification

- Five cleanup and restored-wake regressions fail against the original
production code. The Retry now route and UI regressions also fail before
their correction.
- Related retry, dispatch, stale-queue, and recovery suites: 321 tests
pass across seven files. All ten focused cleanup/restored-wake cases
pass after rebase. The final dispatch adjustment passes all 46 adapter
tests.
- Retry now routes and affected UI suites: all 46 tests pass. The tests
cover repeated clicks, the saved schedule, unchanged accounting,
promotion after release, and no false success or error state.
- `pnpm -r typecheck`, `pnpm build`, `pnpm build-storybook`, and `pnpm
check:token-gates` pass.
- Full local `pnpm test:run` was started and then stopped after the
final commit passed all GitHub CI test shards. No complete local
full-suite result is claimed; CI supplies the complete test result for
the final commit.
- Final head `6f1058a332c039e33c4f002b296d20a5554e760e`: all 55 GitHub
checks are green or intentionally skipped. Apex review is 5/5 after two
reviews, with no unresolved threads. The PR has no merge conflicts.

## Risks

The wait applies only to unstarted legacy conversation retries. Native
runs and non-conversation execution keep their existing recovery rules.
Cleanup must actually release ownership before execution can resume. The
wait does not fix a cleanup service that never finishes. Promotion and
dispatch still enforce cancellation, reassignment, pause, budget, and
reconciliation gates. No schema change is required. The Retry now
response adds a `waiting` outcome; the shared contract and all three UI
controls handle it.

## Model Used

OpenAI Codex based on GPT-6, with repository analysis, tool use, and
local code execution. The runtime does not expose the precise serving
model ID or 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 (focused suites; complete
final-head test coverage in CI)
- [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>
2026-10-07 11:15:49 -07:00
DottaandPaperclip 63f3aa2dbf fix(runner): continue restart-interrupted Codex turns (#15297)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native runs retain their provider conversation across server
restarts.
> - A dead runner can restore a Codex conversation after its active turn
is lost.
> - The old recovery path synthesized a failed task result from that
interruption.
> - The task then required operator action even though its conversation
and workspace were available.
> - This pull request preserves the interruption cause and uses the
admitted restart attempt for one continuation in the same conversation.
> - The agent can reconcile unfinished actions and complete the current
request without resending the original task.

## Linked Issues or Issue Description

Related: #12845 added native restart recovery. #15042 covers admission
during shutdown. #14796 covers legacy shutdown recovery. This change
covers a lost native Codex turn after successful conversation
restoration.

**What happened?**

After a server restart killed the local runner, Paperclip restored the
saved Codex thread. Runnerd found that the old turn was no longer
active. It synthesized a failed terminal and a needs-review result from
the last progress message. The transport also discarded the terminal
error when it reconstructed thread history. The task failed instead of
continuing.

**Expected behavior**

After proving that the old process stopped and admitting a bounded
recovery attempt, resume the current request in the same conversation.
Preserve the workspace. Inspect unfinished actions before proceeding.
Keep real provider failures, accepted results, intentional stops,
unknown unreconciled effects, and exhausted attempts subject to their
existing rules.

**Steps to reproduce**

1. Start a local native Codex run and leave its turn active.
2. Kill the isolated runner and provider processes, as can happen during
a server restart.
3. Restore the same provider thread with no active turn.
4. Observe the synthetic task failure. The new real-process regression
reproduces this boundary with a scripted provider.

## What Changed

- Record an explicit recoverable process-loss cause without inventing a
task result.
- Preserve terminal errors and prior turns in reconstructed provider
history. Recover the authoritative saved result when adopting an
accepted continuation.
- Send one continuation in the same conversation for an admitted
dead-runner recovery. Require reconciliation of unfinished commands and
external actions.
- Persist the interrupted terminal before submission and retain the
existing recovery marker across another controller loss.
- Keep provider attempt limits, terminal failures, and intentional
cancellation behavior.
- Add red/green regressions, real process-kill coverage, restart
checkpoint coverage, and retry-budget coverage. Document the behavior
and run-log evidence.

## Verification

- Red: the new native runtime regression rejected with
`NativeProviderTerminalFailure` on the original code; the Rust restore
regression found a missing recovery cause.
- Red/green: if restoring the conversation fails and replacement is
allowed, the replacement receives the full task and fresh-session
handoff. Both prepared and legacy execution inputs are covered.
- Red: a second controller crash after the provider accepted the
continuation caused an extra `turn/start`. The regression now proves
there are exactly two submissions total: the original and its
continuation.
- Green: focused runtime, backend, driver recovery, and real-process
restart suites (207 tests). After the final history/result changes,
driver recovery and real-process restart suites passed again (36 tests).
- Green: complete Codex transport suite (186 tests), server restart
classification/database integration suites (34 tests), and Rust Codex
provider suite (92 passed, 2 ignored).
- Full `pnpm -r typecheck` and `pnpm build` passed on `60141e649`. The
subsequent replacement-prompt guard passed the Runner TypeScript check
and the complete runtime plus process-restart suites (148 tests).
- Local full-suite attempt: `pnpm test:run` reported two failures in the
untouched chat integration suite. Both passed individually, and the
complete chat suite passed on rerun (1,063 tests). After all remote test
shards passed, the duplicate serial local run was stopped with SIGINT;
it is not claimed as a full local-suite pass.
- Latest-head CI (`b1297dcd4`): 55 successful checks and 4 intentionally
skipped checks, including all test shards, typecheck, build, native
Runner verification, end-to-end tests, and canary dry run. [CI
run](https://github.com/paperclipai/paperclip/actions/runs/37405346303).
- Greptile: 5/5 on the latest head, with no unresolved review threads.
The PR is mergeable.
- The process tests use the real runner binary and a scripted Codex
provider. They do not call a live model service.

## Risks

- This changes local Codex recovery after process loss. A continuation
can execute more work in the retained conversation. Its prompt requires
state inspection before repeating an uncertain action; the system does
not replay tool calls.
- Recovery shares the existing three-attempt budget and one-shot
continuation marker. Real failures and older unmarked failed checkpoints
are not reopened.
- No database migration or API change is required.

## Model Used

- OpenAI Codex, based on GPT-6. The exact model ID and context-window
size are not exposed in this session.
- Capabilities: reasoning, source inspection, tool use, code editing,
code execution, and test analysis.

## 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>
2026-10-06 07:49:47 -05:00
Devin FoleyandPaperclip efac8ff2f4 Add bounded Git integration restore diagnostics (#15291)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Sandbox runs must restore their workspace before finalization can
succeed.
> - Restore diagnostics identify the failed phase and operation.
> - A Git integration exit code can still describe several different
failures.
> - This pull request adds fixed command labels and supported failure
classes.
> - Operators can distinguish these failures without collecting private
Git output.

## Linked Issues or Issue Description

Refs #15005. This branch includes merged #15268. It labels that change's
locked ref transaction and nested branch probe without changing their
behavior.

**What happened?**

A failed Git integration can report only `git_integration`, `unknown`,
and an exit code. That evidence does not identify the failed command.
Some Git versions also return exit 1 for both a merge conflict and an
invalid object.

**Expected behavior**

Record a fixed command family and a supported failure class. Keep
unknown cases as `unknown`. Exclude command arguments, process output,
paths, repository URLs, filenames, and ref names.

**Steps to reproduce**

The tests create local repositories with conflicting commits, a missing
object, and an expected-old ref mismatch. They call the real Git
operations and inspect the resulting diagnostic. No hosted workspace or
external provider is used.

**Paperclip version or commit**

Base: `e99854249c`.

**Deployment mode**

Built from source. The diagnostic applies to sandbox workspace restore.

## What Changed

- Label Git integration calls with a closed command enum. Preserve
arguments, options, errors, and retry behavior.
- Recognize supported object and ref errors and OS permission codes.
Require both exit 1 and completed tree output for a merge conflict.
- Carry the closed fields through the existing restore receipt, saved
adapter result, and Sentry projection. Revalidate saved metadata before
projection.
- Test nested wrappers, parallel failures, reused errors, handled
probes, result settlement, and privacy with the real Sentry SDK.
- Document the field contract and its limits.

## Verification

- Eight focused suites pass: 295 tests. These cover Git sync, restore
diagnostics, result settlement, teardown, sandbox runtime, failure
projection, the real Sentry SDK, and native warm-workspace Git history.
- `PAPERCLIP_REQUIRE_SENTRY_TEST_SDK=1 pnpm exec vitest run
packages/adapter-utils/src/workspace-restore-diagnostics.test.ts
packages/adapter-utils/src/workspace-restore-result.test.ts
packages/adapter-utils/src/git-workspace-sync.test.ts
packages/adapter-utils/src/workspace-restore-teardown.test.ts
packages/adapter-utils/src/sandbox-managed-runtime.test.ts
server/src/services/__tests__/run-failure-diagnostics.test.ts
server/src/__tests__/run-failure-sentry-real-sdk.test.ts
server/src/__tests__/native-workspace-sync-history.test.ts`
- Local checks use Node 24.21.0 and the pinned pnpm 9.15.4. The optional
Sentry SDK is pinned to the declared 10.71.0 and uses an in-memory
transport.
- `pnpm -r typecheck` and `pnpm build`: pass on the rebased head.
- The full local `pnpm test:run` was stopped before source changes when
#15268 merged and required a rebase. It reported three pre-existing
company-skill cache test failures on macOS. The same three cases fail on
clean bases `2c43b39167` and `e99854249c` and the earlier head with
`EACCES` when publishing a read-only cache directory. The relevant test,
service, and cache source blobs are identical. No cache changes are
included. Later local test groups were not reached.
- All 54 checks pass on exact head `8448896bc6`, including the
post-ready security scan, with two intentional Storybook skips. Greptile
scores this head 5/5 with no review threads. Full Linux CI covers the
local groups that were not reached.
- Independent review found no blocker. Its diagnostics, Git workspace,
and native history suites pass 115/115 on this head.
- A source comparison confirms that removing only diagnostic wrappers
yields the merged upstream Git integration code exactly, including ref
locks, transaction protocol, arguments, options, and retry decisions.
- The clean base has stale dependency overrides in its lockfile. Local
installation resolved them as the existing PR CI fallback does. No
manifest or lockfile change is included.
- `git diff --check` and a local secrets/PII review pass.

## Risks

A Git version or localized message may not match a known failure form.
Such cases remain `unknown`. Up to 16 KiB of stderr and the bounded
tree-ID prefix of stdout are inspected only in memory. The saved fields
contain enum values only. These diagnostics do not establish workspace
recovery or authorize retries. Restore decisions and Git mutations are
unchanged. No schema migration is required.

## Model Used

OpenAI Codex, GPT-6, with reasoning, tool use, and code execution. The
service does not expose the exact model deployment ID 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>
2026-10-05 17:10:05 -07:00
DottaandPaperclip a65ca09508 fix(runner): settle accepted results after shutdown failures (#15217)
## Thinking Path

> - Paperclip manages AI agents and the tasks they perform.
> - The native runner saves tool results and completion reports before
it releases a session.
> - Large project discovery responses can exceed the durable command
limit.
> - A shutdown failure can leave a saved answer waiting for workspace
repair.
> - Recovery reused the old assessment for a different status decision,
which violated a database constraint.
> - This pull request bounds discovery responses and lets recovery
commit the saved result after workspace repair.
> - The benefit is a task that reaches its correct final status without
another provider turn.

## Linked Issues or Issue Description

**What happened?**

A native run can save its final answer, fail during shutdown, and leave
the task In Progress after workspace repair succeeds. Reconciliation
tries to reuse the failed-workspace assessment for a new decision. The
one-decision-per-assessment constraint rejects the write. Replaying the
old decision can also retain a fresh coordinator lease. Separately,
retained session cleanup only recognizes the old `adapter_failed` label.

The project-list tool returns full project records, including large
descriptions and workspace configuration. A large response exceeds the
runner's durable command limit. The settlement diagnostic previously
recorded only a failure flag.

**Expected behavior**

Project discovery stays within the command limit. Recovery finishes the
saved result after workspace repair, releases its lease, and preserves
the original error for inspection. It does not repeat provider work or
relax session ownership checks.

**Steps to reproduce**

1. Return large project records from `list_projects` and observe an
oversized semantic result.
2. Persist an accepted native completion result, then record a shutdown
failure.
3. Finalize with a failed workspace, repeat that attempt, then record
successful workspace repair.
4. Reconcile the run. Before this fix, the issue stays In Progress.

**Paperclip version or commit**

Reproduced on `a386a599983519eb1d399f8b770bfccdb2a74762`.

**Deployment mode**

Self-hosted server with Paperclip Runner.

Related transport work: #12208 drains queued events; #12241 resumes
interrupted semantic calls. This change addresses bounded project
discovery and accepted-result finalization.

## What Changed

- Read bounded project summary projections from the database and return
at most 50 authorized summaries with a continuation cursor and explicit
description truncation. Agent and run trust boundaries narrow the
database candidates; project-specific policies still receive full
authorization. Only visible projects determine continuations. The
default project-list API remains unchanged.
- Record bounded, content-free settlement failure causes for command
limits, storage errors, and rejected dispatch.
- Include workspace state in assessment identity. Preserve the initial
assessment for interrupted finalization, and commit replacement
assessment and decision references together.
- Release the coordinator lease when an existing decision is replayed,
without repeating its effects.
- Clear stale errors when recovery succeeds and retain them in
`recoveredExecutionFailure`.
- Accept both current and legacy close-failure labels in the existing
exact-state cleanup path.
- Add regression tests and update the tool contracts and recovery
documentation.

## Verification

- Red: the new project paging, settlement diagnostic, current cleanup
label, and repaired-workspace regressions failed on the original
implementation.
- Green: protocol/catalog/tool checks (117 tests), the full project-tool
and finalizer suites (47 tests), cleanup ownership cases (70 tests), and
cleanup sweep cases (4 tests) pass. Database-backed pagination covers
large descriptions/configuration, complete enumeration, uppercase
cursors, agent/run restrictions, project-policy scope contributions, and
identical results/cursors when hidden projects are added.
- The initial CI failures in interrupted Board waits, contended endpoint
proof, and semantic schema validation were reproduced and fixed; all
affected cases pass locally.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — started before the review corrections; it spanned
several source revisions and was stopped after reporting old-behavior
and timing failures. It is not claimed green. Fresh project and recovery
suites pass; the recovery suite also passes all 25 cases with the broad
runner’s isolated home/config. The Slack timing case passed in
isolation. Latest-head CI is the authoritative complete test matrix.
- Existing authorization suite — 66 tests passed.
- Latest-head CI on `8e5763d915aa6f375bdab6601996899ea01496fc` — 55
checks passed, 4 intentionally skipped, no pending or failing checks.
- Greptile — 5/5, zero unresolved threads on the same commit.
- `git diff --check` — passed.

## Risks

- `list_projects` now returns summaries. Callers must follow
`nextCursor` and use the authorized project API for full records.
Candidate narrowing is only an optimization: project policy and
responsible-user authorization remain authoritative.
- Assessment identity changes for workspace finalization. Existing
evidence remains intact; no migration is needed.
- Cleanup still requires matching identities, settled tool evidence, and
verified process ownership. Unknown tool outcomes remain blocked from
session reuse.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, code execution, and GitHub
tools. The exact serving model ID 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>
2026-10-05 10:31:11 -05:00
Devin FoleyandPaperclip d6d88b9de2 fix: preserve run outcomes when agent file cleanup is deferred (#14945)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The heartbeat service records each agent turn and releases its
working files.
> - A turn can save its work and finish before instruction-copy cleanup
runs.
> - A cleanup exception can replace that completed result with an
adapter failure.
> - This also loses result accounting and can prevent environment lease
release.
> - This pull request records a cleanup warning and keeps the original
run outcome.
> - The existing recovery sweep retries cleanup from the durable
working-copy record.

## Linked Issues or Issue Description

Related cleanup and lock work: #14866 and #14869. Related, distinct
work: #14695 retains warm-process files; #12021 handles provider-process
SIGTERM after a terminal result.

**What happened?**

An agent saved its plan, posted a comment, and requested approval. The
provider completed its turn. Instruction-copy cleanup then timed out on
a directory lock. Its exception escaped a `finally` block and replaced
the provider result, so the completed turn showed `Run failed`.

**Expected behavior**

Keep the provider outcome, usage, cost, saved work, and pending
approval. Record a cleanup warning and let the existing recovery sweep
retry. A real provider failure must keep its original error. A failed
file save must keep its failed-save receipt.

**Steps to reproduce**

1. Complete a legacy adapter turn that saves work and requests approval.
2. Make instruction-copy release throw a directory-lock timeout.
3. Read the run result. Before this change, the cleanup error replaces
the provider outcome.

The new heartbeat tests reproduce the failure without a live provider or
external service.

**Paperclip version or commit**

The regression reproduces on `c83df091b1a5207375eaf23466bb5c62e4e1518e`.
This branch is rebased onto `cf8ad63c80`.

**Deployment mode**

Server-managed agent execution with persistent instruction working
copies.

## What Changed

- Catch instruction-copy release failures in both heartbeat teardown
paths. Stop repeating a failed cleanup attempt within the same run.
- Write a sanitized `instruction_cleanup` warning. A warning-write
failure also preserves the run result.
- Test successful, failed, and throwing providers; both teardown paths;
warning-write failure; accounting; approval state; and execution-control
release.
- Extend the held-lock test to prove a fresh recovery worker removes the
deferred copy and preserves its failed-save receipt.
- Document deferred cleanup and the run-log event.

## Verification

- Red proof: all five new heartbeat regression cases fail with the
original release calls.
- At head `20bea4f431c916d2f5db1970213aab85f5daa34c`, all 417 tests
passed across heartbeat process recovery, agent directory working
copies, and directory merge locks.
- Full local `pnpm -r typecheck`, `pnpm build`, and `git diff --check`
passed.
- [GitHub
CI](https://github.com/paperclipai/paperclip/actions/runs/37031119795)
passed at this head. All 53 reported checks passed; the two Storybook
checks were correctly skipped. This includes general and serialized
tests, browser shards, runner verification, build, typecheck, and the
canary dry run.
- Greptile reviewed this head with 5/5, no code comments, and no
unresolved review threads. The branch has no merge conflicts.
- The local `pnpm test:run` attempt was stopped after it reported eight
failures in unchanged suites. Four Slack/AgentMail cases selected an
unrelated ancestor skills directory and failed with `ENOENT`; the two
Slack cases passed with a temporary local skill-root link, which was
then removed. Three company-skill cases reproduced macOS `EACCES` errors
when renaming read-only cache directories. One gateway case passed when
rerun alone. No full local-suite pass is claimed; the complete CI test
jobs passed.

## Risks

- Cleanup errors now leave recovery work pending. The durable
working-copy record remains available for the existing retry sweep.
- This change preserves provider failures and failed-save receipts. It
does not claim that unsaved file edits were saved.
- Native instruction reservation errors retain their existing behavior
because they guard process ownership.
- No schema, lockfile, workflow, API, or UI changes.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools, and code
execution. The exact backend model ID and context-window size are not
exposed by 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>
2026-10-02 14:16:18 -07:00
DottaandPaperclip 7a52dcdc74 fix: repair MCP validation and cancelled execution recovery (#14951)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The tool gateway gives agents access to connected services. Recovery
controls what happens when a run stops.
> - Generated tool names can exceed the provider limit after the MCP
client adds its prefix.
> - The same invalid definition can fail each automatic retry. A
cancelled run can also hold saved messages without showing its cause.
> - This pull request bounds tool names, stops configuration retries,
and retains cancellation evidence.
> - It shows the stopped run and admits saved input only after the
existing safety checks pass.
> - The benefit is a clear recovery path that preserves operator Stop
and prevents duplicate message delivery.

## Linked Issues or Issue Description

**What happened?**

A long connected MCP tool name makes the provider reject the entire
request. Automatic recovery repeats the invalid request. Separately,
unexpected legacy cancellations can leave saved input behind a recovery
hold. The notice does not identify the stopped run or its cause.

**Expected behavior**

Complete MCP names fit the provider limit. Tool-definition errors
require configuration repair. Cancelled runs retain their source and
reason. The recovery notice shows the cause and saved-message count.
Verified unexpected cancellations can start a fresh turn through the
existing admission checks.

**Steps to reproduce**

1. Assign an App gallery connection with a long application key and tool
name to a Claude agent.
2. Start a run. The provider rejects a name over 128 characters,
including its MCP prefix.
3. For cancellation recovery, stop a legacy provider turn without an
operator Stop request and send a user message while the recovery hold is
active.
4. Inspect the recovery notice and the deferred message queue.

**Paperclip version or commit**

Rebased onto master at `cf8ad63c806685bfd7c48e3ed4a919d61a7c55f1`.

**Deployment mode**

Hosted or self-hosted server with legacy Claude or Codex execution.

Related public work:

- Refs #14017. That PR caps name segments. This PR preserves existing
short names and uses stable hash aliases for long complete names. It
also covers classification and recovery.
- Refs #4510. That PR adds a cancellation-source column. This PR records
bounded evidence in the existing run result, without a migration.
- Refs #12552 and #4506. Those PRs suppress recovery after operator
cancellation. This PR preserves operator intent and uses the existing
continuation gates.

## What Changed

- Bound gateway names with the full provider prefix in the 128-character
budget. Retain the original upstream tool name for dispatch and
permissions.
- Classify invalid tool definitions as configuration failures before
diagnostic redaction. Stop automatic retries and continuation attempts
for that error code.
- Persist cancellation source, expectedness, initiator, reason, and
time. Preserve recorded Stop intent when adapter results arrive. Report
unexpected started cancellations with closed diagnostic labels.
- Show the run cause, saved-message count, and Inspect run link. Offer
Continue for eligible unexpected cancellations. Require verified
provider stop, empty tool inventory, ownership, and the existing pause,
budget, approval, and dependency gates. Use the existing queue for
single delivery.
- Add regression coverage and update the execution, MCP gateway, and
run-log documentation.

## Verification

- `pnpm -r typecheck` and `pnpm build` passed.
- `pnpm check:token-gates` passed.
- Ran `pnpm test:run` and completed its workspace and serialized groups.
Initial resource and timing failures passed on isolated reruns. All 149
serialized route suites passed.
- Reran the changed server, adapter, and UI suites after the rebase.
Coverage includes long-name upstream dispatch, configuration retry
suppression, cancellation evidence retention, privacy labels, oversized
run projection, and concurrent saved-message delivery.
- `pnpm test:e2e tests/e2e/legacy-failure-continuation.spec.ts` passed
all six browser scenarios. The recovery notice shows the run cause and
inspection link, and each recovery entry point reaches one new response.
- Added database-backed checks for active, removed, paused, unavailable,
and disabled chat connections. The final continuation and
recovery-notice suites passed 167 tests. Externally bound chats hide
board Continue and show a usable next action.
- All 55 GitHub checks passed on
`42afbf1371dcaeb72646e3d8f65c19ff7cddf8de`. Two unrelated Storybook jobs
were skipped by their normal conditions. Greptile reviewed that commit
at 5/5 with no findings and no open review threads.

## Risks

- Long tool names change to aliases. Existing short names stay
compatible. The original connection and upstream name remain the
dispatch authority.
- Invalid tool definitions no longer get automatic retries. An operator
must repair the configuration before a new attempt.
- Continuation changes apply only to positively identified unexpected
legacy cancellations with complete empty tool inventory. Operator Stop,
unknown historical cancellations, outstanding tools, and unverified
provider termination keep their holds.
- No database migration. The added projection fields are optional.
Cancellation reason and initiator IDs remain local run evidence; Sentry
receives only closed source and initiator-type labels and expectedness.

## Model Used

- OpenAI GPT-6 through Codex, with reasoning, repository editing, shell
execution, and GitHub tool use. The runtime does not expose the exact
model variant 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>
2026-10-02 13:47:59 -05:00
DottaandPaperclip ec3bacc9bd fix(chat): hide ignored provider information (#14929)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task and agent chats show agent progress and problems that need
attention.
> - Codex also sends account, skill, and unrelated thread notifications.
> - The runner correctly ignores that information but reports it as a
warning.
> - Chat then shows an internal diagnostic as an actionable provider
notice.
> - This pull request keeps the diagnostic in run logs and removes it
from chat.
> - Real provider warnings, errors, and agent replies remain visible.

## Linked Issues or Issue Description

**What happened?**

Chat showed “Received a provider update” and a warning with the text
“ignored
unrelated provider information”. Its details said “User Actionable: Yes”
even
though no user action was needed. Saved conversations retained the same
noise.

**Expected behavior**

Keep ignored provider information in the run log. Do not show it as chat
activity
or a user warning. Preserve real warnings and errors.

**Steps to reproduce**

1. Start a conversation with the native Codex runner.
2. Have the provider send an account update, skill change, or unrelated
thread
   notification during the turn.
3. Inspect live chat and reload its saved history.

The regression tests also reproduce the old stored notice without a live
account.

**Paperclip version or commit**

Source implementation on master at `e00d10d5d`. The duplicate search
found no
open PR for this fix. Related prior work: #13109 improved
provider-notice
presentation. #12367 added Codex thread normalization. This change
addresses
the internal information that those paths still projected as chat
warnings.

**Deployment mode**

Native Paperclip Runner with the Codex app-server provider. The issue
was seen
in hosted chat and can be reproduced with local provider fixtures.

## What Changed

- Map ignored unrelated Codex information to `harness.diagnostic` in the
Rust
  and TypeScript normalizers.
- Retain a bounded allowlist of redacted provider method and thread/turn
identifiers.
- Use the same Unicode character limit and truncation marker in both
normalizers.
- Share the text redactor through a pure helper. Keep provider
connection code
  out of the standalone demo's source closure.
- Omit that diagnostic and the matching legacy notice from live chat.
- Omit the matching legacy notice from saved chat history.
- Test diagnostic retention, account-notification integration, live and
saved
  chat, and continued visibility of real warnings, errors, and replies.
- Document the local run-log event and historical display behavior.

## Verification

- Passed: 68 tests in the two affected UI transcript suites.
- Passed: 60 TypeScript tests across provider events, transport
behavior, and
  the standalone demo boundary.
- Passed: 13 Rust provider-event tests and the Codex
account-notification
  integration test.
- Passed: `pnpm check:token-gates` and Cargo formatting checks.
- Passed: full `pnpm build` and `pnpm -r typecheck`. After the review
fix,
the provider package build, typecheck, and both provider-event suites
passed again.
- Full local `pnpm test:run` failed: 608 files / 10,904 tests passed, 30
server
suites failed, and 104 files / 4,012 tests were skipped. Most failures
were
  embedded PostgreSQL startup errors. Two tests timed out in
`heartbeat-comment-wake-batching` and
`workspace-git-snapshot-streaming`.
  PostgreSQL startup also failed in `heartbeat-run-event-sequencing` and
`native-finalization-migration`. These server files are unchanged by
this PR.
Isolated heartbeat reruns were skipped locally. The stable test script
stopped
  after this general-server group, so later groups did not run locally.
- The original review thread is resolved. Greptile is 5/5 on current
head
  `683dab7cce57187c57e84c83f5e9da4ad75c9c04`.
- All current-head CI gates passed, including the full
server/chat/workspace
test matrix, Rust and TypeScript runner suites, browser E2E, build,
typecheck,
and release canary. [CI
run](https://github.com/paperclipai/paperclip/actions/runs/37021330663).
- Replay the exact old warning in either transcript adapter. It must
produce
no chat row. A genuine provider warning or error must still produce a
row.

## Risks

- Low risk. The display filter matches one diagnostic code or the
complete
  legacy warning shape. Other provider notices remain visible.
- New ignored-information events use the existing harness-diagnostic
event
type. They retain diagnostic evidence without original account payloads.
- No database migration, API permission, provider execution, or recovery
  behavior changes. This affects the local run log, not Telemetry or
  OpenTelemetry exports.

## Model Used

OpenAI Codex, GPT-6. The exact backend model ID and context-window size
are
not exposed in this session. Used reasoning, repository inspection, code
editing, tool use, and test execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run the affected tests locally and they pass (the broad
local run has PostgreSQL startup errors and timeouts documented above;
the full CI matrix passed)
- [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>
2026-10-02 09:59:34 -05:00
DottaandPaperclip b54b2dc35c fix: preserve warm Codex turns with incremental managed file checkpoints (#14735)
## Thinking Path

> - Paperclip manages AI agents and keeps their instructions and files
durable.
> - Native Codex runners can keep a process alive between compatible
turns.
> - Managed file collection stopped that process after each turn, which
defeated warm reuse.
> - Agent folders can contain large images and other files, so full
copies on every turn are expensive.
> - This change keeps one managed directory for the live session and
saves only file changes after each turn.
> - Ownership, authorization, instruction changes, and process
retirement still control when reuse is safe.

## Linked Issues or Issue Description

Related: #13710 introduced native warm session reuse. This fixes managed
file collection that still forced those sessions to stop. No duplicate
open PR or issue was found.

**What happened?**

With managed instructions and warm native Codex enabled, consecutive
turns reused a Daytona sandbox but started a new runner process each
time. The managed directory collector required process termination
before saving files.

**Expected behavior**

Compatible turns keep the same process and managed `AGENT_HOME`. Each
completed turn saves added, changed, and deleted files before the next
turn starts. Unchanged large files do not transfer again.

**Steps to reproduce**

1. Use a native Codex agent with managed instructions and a reusable
Daytona environment.
2. Enable warm session reuse and run three turns on the same task.
3. Write a large binary on the first turn, edit a small note on each
turn, and delete a file on the second turn.
4. Compare process identity across turns and read the canonical files
through the public agent-files API.

**Paperclip version or commit**

Reproduced on `d30b03bd8c17604cdab1533eeeeb087aba30e8b1`.

**Deployment mode**

Local server with remote Daytona execution; cloud native runner uses the
same path.

## What Changed

- Retain the managed directory only for the verified owner of a live
native Codex session.
- Checkpoint each completed turn before releasing the session for reuse.
Retry unstable captures, then stop and collect when a warm checkpoint
cannot be validated.
- Compare metadata and cached hashes, stream only changed file payloads,
record deletions, and validate path, content, quota, and authorization
before saving.
- Rotate sessions when canonical files, loaded instructions,
credentials, or launch policy change. Fence stale collection and cleanup
callbacks from later owners.
- Keep cleanup and recovery aware of the current session owner. Recheck
canonical files under the writer lock at handoff, attach the successor
collector before fallible bookkeeping, and emit one final save receipt
on checkpoint fallback. Preserve storage warnings across unchanged
checkpoints.
- Add regression coverage and a three-turn Daytona test with independent
public API file checks, an unchanged 8 MiB binary, deletion checks, and
strict process identity checks.
- Document checkpoint consistency, lifecycle behavior, and local run-log
counters.
- Replace a timing assumption in the Daytona teardown test with explicit
transfer-arrival gates after CI exposed an unset release callback.

## Verification

- Full local `pnpm -r typecheck` and `pnpm build` passed. Server checks
were repeated after the final storage-warning fix.
- Runner E2E typecheck and 749 runner E2E unit tests passed.
- Focused file checkpoint, directory ownership, instruction collection,
native session, and merge tests passed. After review fixes, the
managed-directory and native-session suites passed 550 tests, including
intervening canonical edits, same-run fresh restore, failed handoff
collection, and one-call fallback collection. Server typecheck passed
again. The Daytona plugin suite passed 218 tests. The quota-warning
regression failed before the fix and passed afterward.
- Three real Daytona campaigns passed before the final handoff review
fixes. The latest kept PID 547 across all three turns. The first
checkpoint copied 8,388,635 bytes; the next two copied 36 and 54 bytes.
Public API reads verified the binary, note contents, and deletion after
every turn. Test cleanup deleted the sandbox.
- The final head was also deployed to an isolated cloud staging instance
and passed three UI-triggered native Codex turns with managed
instructions. All three retained the same process ID/start time, native
session, provider session, runner instance, and Daytona sandbox.
Checkpoints copied 8,388,643 bytes on turn 1, then only 52 and 78 bytes
on turns 2 and 3; those warm captures also hashed only 52 and 78 bytes.
Independent canonical API reads verified every byte of the unchanged 8
MiB binary and the exact note contents after every turn; the deleted
file returned 404 after turns 2 and 3. After restoring the original
lifecycle and agent-auth configuration, removing the temporary secret,
pausing the test agent, and deleting both test sandboxes, independent
canonical API reads still verified the entire binary, the final 78-byte
three-line note, and the deletion. The native runner flag remained
enabled and the final serving revision remained the PR head.
- Two earlier staging attempts are preserved as failures and are
excluded from the acceptance result: a saved ChatGPT login failed with a
provider routing 401, and its subsequent stopped-sandbox retry failed
before provider startup with a closed-lease admission error. The
successful campaign used a fresh sandbox and a temporary encrypted
API-key binding. The stopped-lease retry remains unexplained; this
campaign does not establish recovery of that failed sandbox.
- All [Paperclip CI
gates](https://github.com/paperclipai/paperclip/actions/runs/36750397355)
pass on `26ef2ef56a389259246809805c0b34a4747eb86b`, including full test
partitions, build, typecheck, runner verification, E2E shards, and the
Canary clean public-npm install. Greptile reviewed that exact head at
5/5 with no unresolved review threads or outstanding findings.
- Full local repository coverage used the existing CI partitions, but
the 40,000-file Git streaming stress test timed out and its local retry
was interrupted by macOS thermal emergency sleep; this is not a green
full local suite claim. The exact stress test passed on the final head
in [CI server shard
2/12](https://github.com/paperclipai/paperclip/actions/runs/36750397355/job/110008294290),
in 111.9 seconds.
- Repeat the live test with configured credentials and a Linux runner
artifact: `pnpm test:e2e:runner -- --id
daytona-warm-continuity.runner-codex.daytona.warm-three-turn`.

## Risks

- This is a file-level checkpoint, not an atomic snapshot of the whole
folder. Background writes after a capture are saved by the next
checkpoint or final stopped collection.
- Metadata scans still visit all paths. Modified files transfer in full;
unchanged files do not rehash or transfer.
- Incorrect ownership or reuse could collect the wrong directory. Run
ownership fences, current authorization, stable capture validation, and
stopped collection fallbacks are covered by tests.
- Warm reuse remains opt-in. No database migration or fleet default
changes.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, code editing, tool use, and
test execution. The exact serving model ID and context-window size are
not exposed by 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>
2026-09-30 13:33:37 -05:00
DottaandPaperclip 94e8dec56b fix(runner): preserve tool outcomes through shutdown and restart (#14734)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The runner sends authorized tool calls to the server and saves their
results.
> - A provider turn can stop while a server write is still running.
> - The old shutdown path invented a failed result that could conflict
with the real result.
> - Truncated execution input and incomplete recovery records made the
failure harder to diagnose.
> - This pull request preserves exact inputs and actual outcomes through
shutdown and restart.
> - Tests force the race and crash boundaries so safe retries do not
repeat writes.

## Linked Issues or Issue Description

**What happened?**

Stopping a turn during a server tool call could record a false failure,
then reject the actual result as a conflict. The diagnostic input
formatter could truncate instruction content before execution. A crash
during saved-result delivery could leave that delivery permanently
indeterminate. Cleanup could hide the first failure, and a retry could
overwrite earlier run logs.

**Expected behavior**

Keep dispatched tools pending until their actual result is known.
Preserve accepted input bytes. Accept identical result delivery without
failing the task. Reject conflicting results with enough evidence to
diagnose them. Recover saved-result delivery without repeating the
business operation.

**Steps to reproduce**

1. Hold an instruction update at the filesystem commit barrier.
2. Stop its provider turn before the server returns the result.
3. Release the write, deliver its result, and replay the same result.
4. Repeat with a restart before and after the delivery receipt is saved.
5. Check that there is one write and one audit row, and that the exact
result survives.

**Paperclip version or commit**

The change was developed from `44736c9c7` and rebased onto `0e5830887`.

**Deployment mode**

Self-hosted server with the native runner. Tests use local runner
processes, scripted providers, and PostgreSQL.

Related work: #12353 added durable semantic tool receipts; #12384 added
durable Codex tool recovery; #12404 bound semantic tools to ACPX
sessions. #14633 covers separate native-provider cancellation and
qualification work. This PR addresses server semantic-tool outcomes and
their durable delivery. No duplicate fix was found. AgentMail discovery
is outside this PR.

## What Changed

- Close turn admission without inventing results for dispatched tools.
Keep pending calls and accept late actual results.
- Accept identical result replay with a diagnostic warning. Include call
identity and both result hashes in real conflict errors.
- Preserve exact execution arguments. Reject prohibited or oversized
input before dispatch. Keep diagnostic previews redacted and bounded.
- Commit instruction-attempt evidence before the filesystem write. Save
completed mutation receipts so concurrent and restarted duplicates
return the first result. Recheck authorization before replay. An attempt
without a completed result stays unknown and cannot execute again.
Definite pre-write failures save and replay their original error without
another write.
- Recover an interrupted saved-result delivery only for backends with
durable result receipts. Never replay an ordinary business operation
with an unknown outcome.
- Preserve the initiating error when cleanup also fails. Record
incomplete settlement evidence. Propagate typed unknown-outcome errors
through the native tool wrapper without creating a false completed tool
result.
- Append run-log attempts and restore the durable log before appending
after local file loss. Reject incomplete restores. Publish a restored
prefix only if the destination is absent so concurrent attempts cannot
overwrite new lines.
- Add deterministic race, crash, replay, authorization, exact-content,
and log-restoration tests. Document their assertions in
`packages/paperclip-runner/docs/durable-recovery.md`.

## Verification

- Current head: `7e088f4c7fba8ebabf98ae95485a5753b013d489`. All 55
applicable checks pass; four conditional/manual checks are skipped. This
includes build, typecheck, Rust, both runner TypeScript shards, server
and workspace tests, all eight browser shards, isolated runner
compilation, and the clean-install release dry run. [CI
run](https://github.com/paperclipai/paperclip/actions/runs/36746101110).
- Greptile reviewed this exact head at 5/5 with zero new findings. All
three earlier review threads are resolved.
- Focused local verification includes 11 instruction integration tests,
23 surrounding authority/tool tests, 26 run-log tests, and 169
controller/driver tests. The post-rebase controller/transport/runtime
selection passed 415 tests. The full Rust release suite passed 617 tests
with two ignored. The real-process SIGKILL recovery test passed three
consecutive runs.
- The fault matrix in
`packages/paperclip-runner/docs/durable-recovery.md` uses explicit
barriers, real PostgreSQL rollback, durable journal reloads, and killed
runner processes. It covers late results, identical and conflicting
replay, exact long content, concurrent log restoration, lost commit
acknowledgements, and definite failure replay after the original CAS
base becomes valid again. No paid model calls are needed.
- Full local recursive typecheck and build passed during implementation.
Server typecheck and the runner TypeScript build passed after the review
fixes. The broad local repository test run was stopped after repeated
database startup timeouts. Four timing/launch failures in an earlier
broad runner run passed focused reruns without changed assertions or
timeouts. These are local verification limitations; the complete
current-head CI suite is green. An earlier CI workspace job received an
infrastructure shutdown signal; its current-head replacement passed.

## Risks

- A stopped turn can remain blocked when a dispatched operation has no
proven result. The system does not guess its outcome or rerun its
effect.
- Conflicting results still fail settlement. Existing failed or
conflicting journals are not repaired automatically.
- Accepted semantic input is limited to 480 KiB of encoded JSON to fit
the encrypted transport. Larger input fails before execution.
- Instruction filesystem writes and database receipts are not one atomic
storage operation. A separately committed attempt and audit record
survive rollback. An attempt without a completed success or definite
pre-write failure receipt remains blocked as an unknown outcome. It is
not replayed or reported as success.
- Run-log restoration now reads the durable object before appending when
the local log is missing. Failed or incomplete reads reject the append.
- No schema migration, dependency change, workflow change, or AgentMail
change is included.

## Model Used

OpenAI Codex, GPT-6, with reasoning, tool use, code execution, and test
analysis. The exact served model ID and context-window size are not
exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass (focused suites; the broad
local run limitation is recorded above)
- [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>
2026-09-30 11:54:56 -05:00
DottaandPaperclip 3b4b270650 fix(adapters): preserve ACP terminal failure diagnostics (#14573)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The shared ACP adapter engine records agent failures for operators.
> - ACP providers can report a failure category, title, and detailed
cause.
> - Our patch kept only the category in the saved error, so an operator
could not diagnose a failure when tracing was off.
> - This pull request preserves redacted provider diagnostics in the run
error, transcript, and structured run result.
> - Operators can now inspect the provider message and any supplied
request ID or stack trace after the run ends.

## Linked Issues or Issue Description

Refs #13889 (the diagnostic gap; this PR does not update the bundled
Claude version).
Refs #14484 (related model-refusal classification; this PR retains
diagnostics for all terminal failure categories).

**What happened?**
An ACP turn failed with only `ACP agent reported a terminal service
failure.` The provider's title and details were available in memory but
absent from the saved error and transcript.

**Expected behavior**
The run retains useful provider diagnostics even when raw tracing is
disabled. Credentials remain redacted. A size limit must report
truncation instead of silently removing the cause.

**Steps to reproduce**
1. Run an ACP agent that returns an error-severity typed session
failure.
2. Include an HTTP error, request ID, and stack text in its title and
details.
3. Inspect the failed run with tracing disabled. Before this change,
only the category survives.

## What Changed

- Both pinned ACPX patches pass complete error text to the in-memory
callback, so redaction happens before truncation.
- The shared engine retains the sanitized category, title, and details
in `resultJson.terminalSessionFailure` and includes the text in the run
error and error transcript.
- Diagnostics redact configured environment values even under arbitrary
names, unknown launch-environment values, connection URL passwords, run
credentials, and common credential syntax. Known boolean settings remain
readable, while credential values are redacted even when embedded in
other text. Diagnostics remove control characters and invalid Unicode.
- Title and detail limits keep escaped transcript JSON below the
server's chunk limit. Truncated fields include an omission count. The
safe run-result projection preserves a byte-bounded diagnostic preview
when the result exceeds its byte budget, with an explicit pointer to the
full adapter-bounded run error and transcript.
- The existing UI and CLI display the error. Diagnostics do not become
assistant output. Issue continuation summaries and session-compaction
prompts receive only the generic category, preventing provider text from
becoming handoff instructions. Existing quota classification, warnings,
timeout precedence, and control-channel failure precedence remain in
place.
- Regression tests cover real ACP child processes with both pinned
versions in one-shot and persistent modes, credential redaction, request
IDs after the old 4 KiB cutoff, transcript parsing, storage bounds, and
database retrieval of oversized multibyte diagnostics.

## Verification

- Full CI on `20ad4f5f1f66c46d2c260e6ad0339cbea607b4cf`: **54 passed, 2
intentionally skipped, no pending or failing checks**. Includes
typechecking, build, all Vitest shards, Runner checks, browser E2E, and
the canary packaging/public-install dry run.
- Greptile: **5/5** on this commit. Superagent security scan passes. All
review threads are resolved.
- Local verification passed: shared ACP engine suite (395 tests); real
Claude ACP child-process and diagnostic regressions across both pinned
runtimes and both execution modes; run retrieval and model-handoff
regressions (59 tests); ACPX patch packaging (16 tests); full typecheck
and build. Affected package typechecks and focused tests were rerun
after review fixes.
- The broad local `pnpm test:run` was stopped after review edits made
its cached imports stale. Fresh targeted runs pass, including both
affected server suites. Cold-build import failures were also rerun after
dependency builds: chat integration (1,063 tests) and tool access (351
tests) pass. The final commit's complete CI matrix is green.

## Risks

- Provider diagnostic text is untrusted. This change retains more of it
in company-scoped run records. Redaction and size bounds apply before
persistence.
- Diagnostics are limited to fields the provider supplies. Old runs
cannot recover discarded error text.
- No schema migration, recovery-policy change, or new Telemetry or
OpenTelemetry export.

## Model Used

- OpenAI GPT-6 through Codex, with reasoning, repository inspection,
code editing, and test execution. The exact serving model ID and
context-window size are not exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-09-29 10:18:30 -05:00