Commit Graph
2341 Commits
Author SHA1 Message Date
DottaandPaperclip a0250284e3 fix: preserve proposal revision turns and prove chat completion (#19105)
## Thinking Path

> - Paperclip manages work done by AI agents.
> - A completed delegated task must report its result to the originating
chat.
> - That report uses a real provider turn and must stay in cost
accounting.
> - Ordinary first-task proposals must also support the promised chat
acceptance path.
> - A revision response can reject an older proposal before it creates
the replacement.
> - The old rejection transition clears that active response's ownership
and blocks its next tool.
> - This change proves required completion work, guides ordinary
proposal policy and preserves the revision turn under the existing
locks.

## Linked Issues or Issue Description

No separate issue exists. The original failures remain in [the complete
Product
run](https://github.com/paperclipai/paperclip/actions/runs/41915965118).
The independent continuation repair is #19076. Related #12209 concerns
prior-run confirmation binding. This change fixes the ownership reset
during rejection, rather than changing binding authorization.

**What happened?**

The complete 20-case run finished with 13 passes and 7 failures. Three
delegated-chat cases counted one required `chat_task_completed` provider
turn as unexpected work. Two first-task agents chose optional human-only
cards, then correctly received 403 when they tried conversational
acceptance.

A [reviewed two-case candidate
run](https://github.com/paperclipai/paperclip/actions/runs/41931445311)
then used the changed first-task skill. Both models inherited the
ordinary resolver policy. Clarify/propose/accept passed all 12
predicates. Revise/accept failed after the new response rejected its
predecessor's proposal. The saved rejection was followed by
`paperclip_runner_tool_binding_not_authorized` on its next tools. The
final `native_session_lease_lost` label followed those binding failures;
this PR does not assume lease expiry caused them. Both cells cleaned up
and published their original grades.

**Expected behavior**

Keep the existing ordinary-turn bounds and account for one independently
proved completion notification. Inherit the normal resolver policy for
ordinary proposals that accept chat replies. When an active response
rejects an older proposal, save the rejection and retain that response's
current ownership so it can create the revised proposal. Keep human,
current-source and native completion-review rejection behavior and all
real approval restrictions.

**Steps to reproduce**

The original full run used `4740d251`. The two-case candidate used
trusted workflow `7d1e9b34` and target `74da56c4`. Run the focused
provider-free controls below. The dual-run API regression first fails
because rejection changes the issue to `todo` and clears the newer run's
execution and checkout fields.

## What Changed

- Prove one completion notification only for native Codex/local
`multi-repository`, `clarify-reuse` and `plan-handoff`. Keep the
ordinary bounds of 2, 3 and 4. Preserve actual totals of 3, 4 and 5, all
run IDs and all usage.
- Read those declared totals from the current catalog, which now
includes the completion reply. Check the total once and prove that
exactly one turn is the required notification. Do not add another turn
while waiting. The required-server controls use the actual catalog
tasks.
- Bind the notification to the same company, agent, conversation
generation, completed child, single delivery, accounted native run,
saved reply and rendered result link. Reject extra work, duplicate
deliveries, unknown events and mutations, including native work without
a Paperclip tool call.
- Collect workspace operations through the real run API for these three
proofs. Keep other collector calls unchanged. Keep the proof controls in
the existing required server test lane.
- Stop idle polling on a permanent completion-proof error. Continue to
retry transient observations within the original 240-second window.
- Guide ordinary chat-accepting first-task proposals to omit optional
`resolverPolicy` and inherit server/company defaults. Preserve explicit
and governed human-only restrictions. Do not lower or recreate saved
restricted cards.
- Under the existing locked issue and interaction rows, preserve the
active responding run when it rejects a different source run's proposal.
Change only the ordinary requeue predicate. Keep the saved rejection,
notifications, activity and existing Telemetry calls. Keep global
status, lease and tool-authority guards unchanged.
- Extend the existing native API controls with the actual dual-run
rejection/revision sequence and policy, provenance, human,
current-source and native completion-review countercontrols. No new
workflow or test framework is needed.

## Verification

- Current combined head is `a8567ded67e5ab811675390e9a5c5ed2194aee2f`,
integrated cleanly on `fb96537e28c4c7bfced4939909cb334392320868`. The
upstream completion-reply waiter is preserved. [Current required
CI](https://github.com/paperclipai/paperclip/actions/runs/41940780852)
passes. Actual test checkout `ff93c034d7bb40f3df7bc75cf51828fd07bb0d29`
merges this head into `9c6993f0aab7ebb22a26d78f5d227accb35a6ad1`.
- Fresh Greptile is 5/5 on exact head `a8567ded`, with no blocking
finding. All addressed review threads are resolved. The separate
automated reviewer emitted a paused-work response; that response is not
a review approval.
- Current required server CI actually passed all 11 native confirmation
API cases, 46 completion-proof controls and 13 onboarding asset checks.
Canonical typecheck, Build, the native package/server suites, all
browser and serialized shards, `ci / e2e` and `ci / verify` pass. No
current-head CI retry was needed.
- Local dual-run RED: the unmodified transition returned HTTP 200 and
saved rejection, then changed the issue to `todo` and cleared the newer
run's execution/checkout fields. Whole setup and check took 28.95
seconds; the case took 596 ms.
- Local treatment: all 11 real native/API cases pass. The same-session
response retains execution/checkout ownership, reads task context and
persists the recorded-shape revised confirmation. Its stored lease
receipt stays intact and one rejection activity is saved. Human-only
rejection, current-source rejection, human rejection with/without a
newer owner, Plan/Ask, foreign provenance, authentication and formal
approval controls pass.
- All 59 existing confirmation resolver controls pass in 16.62 seconds.
These cover cancellation, lost ownership, company/user scope, policy
changes, transactional rollback, concurrent retries and stale revisions.
- All 46 completion controls pass in 4.07 seconds. The two added clock
controls preserve the original idle deadline and prove immediate
permanent-proof failure plus transient observation retry. The existing
live collector-to-proof and native mutation negatives remain in this
required server file.
- The first current-catalog integration check failed 5 controls in 2.70
seconds. The old consumer added the already-declared reply again and
could accept an extra ordinary turn. The corrected consumer passes all
46 controls using the actual catalog. The existing full 20-case
catalog/reporting compatibility check also passes in 4.93 seconds. Case
identities, configured models and reporting match the declared 20.
- The API controls used one owned loopback PostgreSQL instance through
existing supported test hooks. It was stopped and removed. Earlier
missing-library/setup failures and one corrected current-source test
expectation remain retained; they are not counted as product failures or
passes.
- The unchanged first-task skill has SHA-256
`b789090996e7925676590c006418eb8b57f4514e646f6bca18ffd7b2b8e864de`. Both
candidate runtime copies matched it. The inherited policy was observed
in both models; one complete acceptance journey passed. This does not
qualify the new rejection repair.
- The three retained real completion streams pass the stronger local
proof, including native items, workspace observations and recorded DOM
links. Their original failed grades stay unchanged.
- Earlier related checks passed 153 hiring/non-execution controls and 13
asset checks. Their source is unchanged. Required discovery includes the
completion, API and asset files exactly once.
- Prior candidate `74da56c4` passed all required CI, including canonical
typecheck and build, in [CI run
41929148958](https://github.com/paperclipai/paperclip/actions/runs/41929148958).
Its fresh Greptile was 5/5. This is prior-head evidence.
- The intermediate `053150d9` head also received Greptile 5/5. Its [CI
run](https://github.com/paperclipai/paperclip/actions/runs/41940014165)
and review stay retained as superseded evidence. They do not qualify the
final catalog correction.
- A clean full local typecheck/build is not claimed because the reused
dependency cache has stale adapter-utils/Runner declarations. No source
or lock workaround was made. Fresh canonical CI must pass before merge.
- The original full-plan result remains 13/20. The separate two-case
probe remains 1/2. A fresh complete saved-plan run against delivered
source is required for acceptance. No case, assertion, model, budget,
timeout or failed grade is changed.

Focused commands:

```sh
# From server/; database URLs point only to an owned disposable local instance.
node ../node_modules/vitest/vitest.mjs run src/__tests__/chat-completion-proof.test.ts
PAPERCLIP_TEST_DATABASE_URL="$OWNED_TEST_DATABASE_URL" node ../node_modules/vitest/vitest.mjs run src/services/native-runtime/confirmation-comment-api.integration.test.ts
PAPERCLIP_TEST_POSTGRES_ADMIN_URL="$OWNED_TEST_DATABASE_URL" node ../node_modules/vitest/vitest.mjs run src/__tests__/confirmation-comment-resolution.test.ts
```

## Risks

- The ownership exception applies only to an agent acting as the current
execution run against a different non-null source run. Human, unowned
and current-source requeue behavior must stay intact. Native
completion-review handling remains separate.
- The completion proof fails closed on new native tools or event shapes.
A new shape needs review.
- A stored unchanged lease receipt is supporting local evidence. The
final real journey must prove full revision, execution and delivery.
- The new head still needs normal protected merge approval. The optional
queued Paperclip review is not a passing review. No causal repair of
unrelated CI intermittency is claimed.

## Model Used

OpenAI `gpt-6.1-sol` in Codex, with `max` reasoning effort. The runtime
reports a 475,000-token context window. Used reasoning, code editing,
shell tools and provider-free controls.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-04 22:04:12 -06:00
DottaandClaude Opus 5.5 06826915d7 fix(dispatch): park a workspace-busy wait instead of recreating runs (#19143)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Child tasks can reuse their parent's execution workspace. Only one
run at a time may write in a shared sandbox workspace.
> - A run that finds the workspace held by another live run waits with a
`workspace_busy` scheduled retry.
> - That wait cancelled the run and created a new retry run every 60–120
seconds, until the holder ended. Each check made one cancelled run
record.
> - Three sibling tasks that waited for a parent made 15 cancelled runs
in 13 minutes. They also got the workspace in random order, not in
arrival order.
> - This pull request keeps the waiting run as one parked retry while
its holder is live, and wakes it when the holder ends.
> - The benefit is a clean run history, a prompt handoff, and fair
arrival-order access to a shared workspace.

## Linked Issues or Issue Description

Refs #18691 (the workspace wait for a live holder). Refs #19075 (sends a
stopped writer with a pending save into the same wait). Refs #12587 (an
earlier proposal about fairness for this retry).

**What happened?**
A parent task delegated three subtasks that inherit its execution
workspace. Each subtask run was created, then cancelled with
`workspace_busy` ("Shared execution workspace is busy: run … is still
running"), then created again about every 1.5–2 minutes. One subtask got
8 cancelled runs, one got 5, and one got 2. Every cancellation named a
holder that was really running: first the parent run, then the sibling
that got the workspace. So serialization was correct. The cost was the
churn of run records, and the subtask that waited first ran last.

**Expected behavior**
A run that needs a busy shared workspace waits as one durable record. It
continues when the holder finishes. Siblings that wait for one workspace
get it one at a time, in arrival order. Two writers never share the
workspace, and the user can still stop a waiting run.

**Steps to reproduce**
1. Bind two or more issues to one shared execution workspace in a
sandbox environment.
2. Keep a run live on one issue (the holder).
3. Wake the other issues. Before this change, each due check promotes
the retry, cancels it with `workspace_busy`, and creates a new retry
run. After this change, the retry stays parked until the holder ends.

**Paperclip version or commit**
Observed on staging at `53735db7`. The code is unchanged on `master`.

## What Changed

- `finalizeWorkspaceBusyDeferral` records
`contextSnapshot.workspaceBusyWait` = `{ key, holderRunId, waitingSince
}`. The key is `execution:<id>` or `project:<id>`. `waitingSince`
carries over when the run defers again for the same workspace.
- New `server/src/modules/run-dispatch/adapters/workspace-busy-wait.ts`:
- `parkWorkspaceBusyRetryInTx` runs in the scheduled-retry promotion
transaction, after the existing gate allows the retry. If the recorded
holder is still running and is not stale, or an earlier waiter for the
same workspace is still scheduled, queued, or running, the retry stays
`scheduled_retry`. Only its deadline moves. It does not create a run, an
event, or a wake. The fallback deadline is 5 minutes, and never later
than the holder's one-hour stale point.
- `wakeWorkspaceBusyWaitersInTx` runs in the transaction that ends a
running run (`writeRunStatus`). It makes due the retries that wait for
that run, and the retries that wait for the same workspace as that run.
It registers the scheduled-retry dispatch notification before the write.
- `WORKSPACE_BUSY_HOLDER_STALE_AFTER_MS` moves to the run-dispatch
domain, so the deferral gate and the parked check use one value.
`heartbeat/retries.ts` exports it again.
- A retry without a recorded wait (for example, a retry from before this
change) keeps the old behavior.
- `doc/execution-semantics.md` describes the parked wait.

## Verification

- Red before the fix, green after
(`server/src/__tests__/heartbeat-workspace-busy.test.ts`, "reused shared
execution workspace in a sandbox"):
- "waits on a live holder in one parked run, then runs once when the
holder ends": before, the retry was promoted while the holder was live.
After, it stays parked through due checks at +1 s, +10 min, and +30 min.
Ending the holder makes it due at once. It runs exactly once, and there
is only one `workspace_busy` cancellation.
- "hands the workspace to three waiting siblings in arrival order, one
writer at a time": before, nothing woke the waiters when the holder
ended. After, exactly one waiter is admitted per handoff, in arrival
order. Each runs once, and there are 3 cancellations in total (one first
deferral for each sibling).
- "still lets the user stop a parked waiter": before, the waiter was
promoted while the holder was live. After, Stop cancels the parked
retry, and nothing brings it back.
- Safety case (green before and after): "lets a stale holder reach the
existing quiet-holder failure instead of parking forever".
- `npx vitest run src/__tests__/heartbeat-workspace-busy.test.ts`: 35
passed.
- `heartbeat-remote-workspace-busy`, `heartbeat-retry-scheduling`,
`src/modules/run-dispatch`, `heartbeat/retries.test.ts`,
`execution-projection.test.ts`: 266 of 267 passed. The one failure,
"records pre-provider quota rejection…", is a timing flake on a loaded
machine and passes when it runs alone. It does not use this path.
- `tsc --noEmit` reports no errors in the changed files.
- Staging evidence was read only. This change is not deployed.

## Risks

- One extra indexed `SELECT … FOR UPDATE` on `scheduled_retry` rows when
a running run ends. The update and the notification happen only when
waiters exist.
- If an earlier waiter stays queued for a long time (for example, its
agent is paused), later waiters for the same workspace wait behind it.
Each check is a deadline update, not a run. The fallback check runs
every 5 minutes.
- A remote writer that has ended but has not finished its save (#19075)
is not parked, because its run is no longer `running`. Such a wait can
still make a small number of deferrals until the save settles.
- Low migration risk: no schema change. Old retries without the new
context key keep the old behavior.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use (shell,
file edits, browser read of staging run records) in Claude Code.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:01:37 -06:00
19c710fe93 fix(issues): record an interrupt comment as the user's Stop (#19149)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A board user can interrupt a running agent with a comment
(`interrupt: true`) to redirect the work
> - An interrupt is a Stop plus a message, but it did not record the
user's stop identity the way the Stop control does
> - The release path therefore treated an interrupted native run as a
failed run: it blocked the task and took the finalization lease from the
runner that was still settling
> - Workspace preservation then stopped the sandbox under that live
runner, so the runner reconnected for about 6 minutes and the stop could
not be verified afterward
> - The redirect message stayed saved, but it waited on workspace repair
that could not finish
> - This pull request records the board user's stop identity on
interrupts, the same as the Stop control and terminalization stops
> - The benefit is that an interrupt now settles like a Stop and the
redirect can continue

## Linked Issues or Issue Description

Refs #19122 (the Stop-then-redirect fix for the dispatch gate; this PR
covers the interrupt-comment path).

**What happened?**
A board user posted a comment with `interrupt: true` on a task with a
running native run (Daytona sandbox). The run was cancelled with
`operator_interrupted` and the native cancel was acknowledged. Within a
second, the task went to `blocked` and a
`native_continuation_requires_reconciliation` hold was created. The
runner transport then logged `state=reconnecting` for about 6 minutes,
and the run ended with `runner_process_termination_unconfirmed: ...
Daytona sandbox lease ... is no longer active`. Workspace recovery
started about 10–17 minutes later and failed with
`agent_stop_unconfirmed` / `provider_unavailable`. The redirect comment
stayed saved as a deferred wake waiting on `workspace_repair_required`.
No follow-up run started.

**Expected behavior**
An interrupt behaves like Stop followed by a message. The stopped runner
settles normally. The task is not blocked as a failure. The saved
message continues once the previous execution has stopped.

**Steps to reproduce**
1. Start a native run on a remote sandbox.
2. Post a board comment with `interrupt: true`.
3. Observe the task become `blocked` with a
`native-continuation:<runId>` hold, and the run's finalization row
rewritten to `terminal_failure` with no lease owner while the executor
is still settling.
4. Observe workspace preservation stop the sandbox, the runner transport
reconnect until its grace ends, and recovery fail to confirm the stop.

**Root cause**
`recordNativeTerminalRecoveryIfNeeded`
(`server/src/modules/wake-queue/adapters/postgres.ts`) skips intentional
stops through `isAcknowledgedNativeStop`. That check requires
`resultJson.cancelledByActorType === "user"` and `cancelledByUserId`.
The Stop control and terminalization stops record these fields.
`operatorInterruptCancelOptions` (`server/src/routes/issues.ts`)
recorded only `interruptedByActorType` / `interruptedByActorId`. So an
acknowledged interrupt took the failure branch. That branch blocks the
task and sets `nativeRunFinalizations` to `terminal_failure` with
`leaseOwner: null`. Cancelled-workspace preservation then saw a terminal
row with no owner and stopped the sandbox while the runner was still
settling.

**Paperclip version or commit**
Seen on staging at 69dea878c0. The touched
code is unchanged on current `master`.

**Agent adapter(s) involved**
Not adapter-specific (core bug). Seen with the Paperclip runner adapter
on a Daytona sandbox.

## What Changed

- `operatorInterruptCancelOptions` now records `cancelledByActorType:
"user"` and `cancelledByUserId` for a board user, in addition to the
existing interrupt fields. All three interrupt call sites (comment POST,
issue PATCH, queued-comment interrupt) use it.
- Route regression in `issue-update-comment-wakeup-routes.test.ts`: an
`interrupt: true` comment on a running native run sends these fields,
and the route's actual fields satisfy `isAcknowledgedNativeStop` with an
acknowledged native stop receipt.
- Release-path regression in `wake-queue/adapters/postgres.test.ts`: an
acknowledged interrupt Stop, while the executor still owns finalization,
releases the task without blocking it, creates no recovery hold, keeps
the finalization lease owner, and keeps the saved redirect deferred.

## Verification

- Red before the fix:
- The route test failed because `cancelRun` received no
`cancelledByActorType` / `cancelledByUserId`.
- The release-path test failed with the old interrupt fields: the task
was blocked, a hold was created, and the finalization owner was cleared.
- Green after the fix:
- `cd server && npx vitest run
src/__tests__/issue-update-comment-wakeup-routes.test.ts
src/modules/wake-queue`
- Related suites: `acknowledged-native-stop`,
`issue-queued-comments-routes`, `issue-comment-reopen-routes`,
`issue-execution-policy-routes`, `explicit-native-continuation`,
`heartbeat-process-recovery`.
- The staging incident was read through the read-only run, event, log,
recovery-action and wake diagnostics APIs.
- Not done: a live staging reproduction at the fixed revision.
Deployment is out of scope.

## Risks

- An interrupt now counts as the user's Stop everywhere the Stop control
does. This includes case-handoff supersession in `cancelRun`: an
interrupt supersedes a pending case handoff, like Stop.
- Only board users get the stop identity. Agent actors cannot use
`interrupt: true` (the route returns 403).
- Runs interrupted before this change keep their old fields. They are
not migrated.
- Not changed here, and suitable for follow-ups:
- `recordNativeTerminalRecoveryIfNeeded` can still clear a live
finalization `leaseOwner` for other failed native runs.
- The runner PRP ingress reconnect loop
(`server/src/realtime/runner-prp-outbound.ts`) does not stop early when
the sandbox lease is closed.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, in Claude Code with
tool use (shell, file edits, subagent code tracing, browser for
read-only staging diagnostics).

## Checklist

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

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:58:51 -06:00
DottaandPaperclip 7c66b6b1f6 test(onboarding): cover plan, hiring, and execution approval history (#19069)
## Thinking Path

> - Paperclip manages agent work and human decisions.
> - Onboarding can approve a plan, a hire, and an execution step in
separate turns.
> - Tests covered single approvals more closely than that full history.
> - The repaired gate must retain valid plan approval through later
decisions.
> - Every consumer must recognize the saved confirmation kind and
selected scope.
> - This change tests that contract and registers a complete
interview-to-execution journey.

## Linked Issues or Issue Description

Refs #19043, #17914, #18283.

The earlier tests admitted one accepted proposal at a time.
Decomposition fixtures used standard confirmations. The direct-task E2E
path forbids hiring. Those cases did not cover an accepted checkbox plan
followed by hiring and a separate untargeted execution approval.

## What Changed

- Add eight PostgreSQL contract cases for standard and checkbox plan
approvals. Native tools create the cards. An authenticated human accepts
them through HTTP. A later native run reads the approved revision and
actual selected scope.
- Exercise native task creation, generic REST creation, child REST
creation, and accepted-plan decomposition. Check the assigned child and
one creation audit event. Check durable replay on the three entry points
that support it. A pending execution confirmation still prevents
creation.
- Register the explicit-only `onboarding-approval-history` suite for
local native Codex and ACPX Claude. The browser chooses the interview,
selects only drafting in the saved plan, approves one writer hire, and
approves a separate Start drafting confirmation. The model must hire the
writer and use that writer's own child execution to save the welcome
document.
- Grade each human decision, the unchanged plan revision and selection,
approval timing, one assigned child and worker execution, the exact
writer-authored saved revision, no pending requests, and persistence
after reload. Calibrate the oracle against missing, forged, premature,
duplicate, and changed outcomes.
- Preserve synthetic option choices, document/run bindings, and saved
revision authorship in the existing public first-task evidence
projection. Keep private provider/account data excluded. Document the
workflow, evidence, billing, and cleanup bounds.
- Align the approval-history fixture with its completion oracle: the
onboarding parent must be Done. Test both native profiles against graded
completion evidence and reject a completed child whose parent remains In
review. Count parent heartbeats separately from the writer: accept one
to seven parent records while retaining the twelve-company-run cap and
the independent three-approval/one-writer oracle.
- Retain the existing 52-cell first-task suite. The new suite has one
attempt, seven expected work turns, a twelve-run maximum, a
fifteen-minute deadline, and the existing $5 company task budget. It is
excluded from `--all`.
- Update two stale cleanup assertions to include the existing
thirty-second deletion timeout.

## Verification

- Red control: temporarily restore the pre-fix admission module and run
the new checkbox/native history case. It fails with the expected
`First-task proposal requires human acceptance` error after plan,
hiring, and execution acceptance. Restore the repaired module after the
probe.
- The final post-rebase history matrix and existing onboarding safety
suite passed together: 74 tests, no skips.
- Final focused Runner E2E support passed: 413 Vitest tests and all 370
Node support checks. This covers catalog discovery, journey controls,
wrong-outcome calibration, public evidence, and existing first-task
behavior.
- Review controls: four authorship cases fail before the revision check.
A delayed browser-response control fails before the approval-time fix.
Both pass after repair. The final oracle requires the exact current
document revision, writer ID, native child run ID, matching body, and
readback after reload.
- Credential-free discovery lists both new native cells. No provider
call was made. `OPENAI_API_KEY` and `ANTHROPIC_API_KEY` are absent, so
the real provider journey remains unqualified.
- The earlier broad credential-free E2E support run reported 6,728
passing cases, three skipped cases, and nine failures in three unrelated
files. All nine reproduce with the changed E2E source restored to the
unchanged baseline: three expired overhead-fixture cases, one stale Pi
profile-version assertion, and five workflow security assertions. This
change does not alter those files, paid workflow policy, or provider
pins.
- Previous full workspace typecheck/build and [Linux
CI](https://github.com/paperclipai/paperclip/actions/runs/41921754638)
passed on `4f431f5aae99d072cc75027e90d7b847b4c342ca`, including the new
eight-case PostgreSQL file. Ponytail subsequently found a fixture/oracle
terminal-status mismatch. Both native-cell controls fail on that fixture
(`done` observed versus `in_review` expected), while the
parent-in-review negative control passes. A second comparison uses the
actual `matchesRunCount` predicate: graded evidence with six parent runs
and one writer run fails the old exact-seven-parent requirement. Both
fixes pass these controls, all 413 focused support tests, all 370 Node
checks, and the owning E2E project typecheck. [Fresh
CI](https://github.com/paperclipai/paperclip/actions/runs/41936699686)
passed on `91c980b110252da451b92eda597c168517de1276`: 48 successful jobs
and one expected skip. Greptile reviewed this head at 5/5 with no
unresolved findings. Ponytail's terminal-status finding is fixed and its
thread is resolved; its current-head re-review remains queued. Live
provider cells remain unqualified.

## Risks

The production API and approval gate are unchanged. The new paid journey
can expose model or product failures at an intermediate approval
boundary. It retains those failures and uses the existing diagnostics
and cleanup. Registration and deterministic tests do not establish a
live provider pass. The unrelated baseline support failures remain
explicit.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository tools, and code
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 (focused tests; unrelated
baseline failures are 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-11-04 21:46:55 -06:00
Devin FoleyandClaude Opus 5.5 b9fbbf8d30 fix(native): continue runs woken by withdrawn or cancelled interactions (#19163)
Withdrawing a card or cancelling a question wakes the assignee with that
interaction id. The native interaction bridge required every wake
interaction to be accepted, rejected or answered, so preparation failed
with native_interaction_unresolved before the provider started.

Project a cancelled interaction as a closed response with its stored
result. Consumers grant authority only for accepted, answered or pending
items, so the closed response carries context and no approval. Pending
requests and governed tool reviews keep their existing checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:10:59 -08:00
Devin FoleyandClaude Opus 5.5 3c7af35103 fix(errors): return tool gateway rejections with their own status (#19158)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents call connected tools through the tool gateway. Some tool
calls need a human review before they run.
> - A user approves a tool review from a task card. The approve route
calls the gateway.
> - The gateway can reject the approval with `ToolGatewayHttpError(409,
"approved_tool_target_changed")`. This is an expected rejection: the
tool or connection changed after the review.
> - `ToolGatewayHttpError` does not extend `HttpError`. The shared error
handler only keeps the status of an `HttpError`. Thus the route answers
500 "Internal server error" and sends the rejection to Sentry as a
crash.
> - The user does not see the reason. The user clicks Approve again, and
each click makes one more crash report. One user made 69 reports on
staging this way.
> - This pull request makes the shared error handler keep the status and
reason code of client (4xx) gateway errors.
> - The benefit is a clear 409 with a reason for the user, and no false
crash reports.

## Linked Issues or Issue Description

Refs #12775 (related community PR. It changes `ToolGatewayHttpError` to
extend `HttpError`, which affects every `instanceof HttpError` check.
This PR is smaller and keeps gateway server failures on the crash path.)

**What happened?**
A user approves a tool review on a task card. The tool definition or
connection changed after the review. The API answers 500 "Internal
server error". Sentry records the expected rejection "Tool definition or
connection changed; request a new review" as a server crash. Each retry
records one more event.

**Expected behavior**
The API answers 409 with `code: "approved_tool_target_changed"` and the
message "Tool definition or connection changed; request a new review".
Sentry does not record a crash.

**Steps to reproduce**
1. Make a connected tool that needs approval, and start a tool call so
that a review card appears.
2. Change the tool connection (for example, its config) before you
approve.
3. Click Approve on the task card (`POST
/api/issues/:id/interactions/:interactionId/accept`).
4. See 500 "Internal server error" and a new Sentry event from
`approveActionRequest`.

**Paperclip version or commit**
`master` at `482ec0aea9ab6f2684baaa1487f2c7a2eedf009e`.

## What Changed

- `server/src/middleware/error-handler.ts`: the handler converts a
`ToolGatewayHttpError` with a 4xx status to an `HttpError`. The new
error keeps the status, the message, the gateway details, and the reason
code as `code`. The original stack stays.
- Gateway errors with a 5xx status do not change. They still go on the
crash path, answer 500 "Internal server error", and go to Sentry. Thus
provider text in those messages stays hidden.
- `server/src/__tests__/error-handler-tool-gateway.test.ts`: new tests
for the 409 mapping, for the unchanged 5xx path, and for an Express
route that lets the gateway error escape.

## Verification

- `cd server && npx vitest run
src/__tests__/error-handler-tool-gateway.test.ts
src/__tests__/error-handler.test.ts` passes: 21 tests.
- Without the fix, the new tests fail with `expected 500 to be 409`.
With the fix, they pass.
- `src/__tests__/issue-thread-interaction-routes.test.ts` and
`src/__tests__/json-parse-error-handler.test.ts` also pass: 175 tests in
4 suites with the two error handler suites.
- I checked the runtime import graph of `services/tool-gateway.ts` (685
modules). It does not import `middleware/error-handler.ts`, so the new
import makes no cycle.
- I did not run the full server typecheck locally, because it ran out of
memory in an 8 GB sandbox. CI must do this check.

## Risks

- Low risk. Only gateway errors with a 4xx status change. Before, they
became 500. Now they keep their own status.
- Some routes outside the gateway router can now return gateway 4xx
details in the `details` field. The gateway routes already return the
same details through `sendGatewayError`.
- `middleware/error-handler.ts` now imports `services/tool-gateway.ts`.
Unit tests that import the error handler load more modules. The app
already loads this module.
- This PR does not fix the cause of some of these rejections. A routine
OAuth token refresh changes values in the signed approval snapshot
(token secret versions, the order of grant secret refs, and token expiry
times). This can make a pending review fail. That fix changes
`services/tool-gateway.ts` and comes in a separate PR.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used as a coding agent
with tool use (shell, file edits, test runs, and GitHub API).

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes (no
documentation change is necessary)
- [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 18:14:52 -08:00
Devin FoleyandClaude Opus 5.5 7edbc1fc07 fix(server): keep a successor's issue lock when a finalizing native run releases its checkout (#19156)
`release_checkout` now clears `checkoutRunId` and the execution lock only when they belong to the committing run, so a successor that claimed the issue while its predecessor was finalizing keeps its claim and passes reviewed-chat attestation.

`attestReviewedExternalChatRun` reports the first failed check as a closed label. Setup failures store it in `resultJson.reviewedChatBinding.denialReason`, and Sentry run-failure diagnostics export it as `execution.reviewedChatBindingDenialReason` (no IDs or text). The error message is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:49:03 -08:00
DottaandClaude Opus 5.5 482ec0aea9 fix(native): run saved copy-back retries on schedule behind a slow export (#19145)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Native runs copy their sandbox workspace back after the provider
finishes. A failed copy-back saves a retry 30 seconds later, with a
bound of 3 attempts.
> - One shared delivery queue runs these retries. A pass of that queue
ends only when its slowest check ends.
> - A legitimate sandbox export can take 10+ minutes. While it runs, all
other due retries wait.
> - On staging, a run active across a deploy showed "Reconnecting" with
an overdue retry for about 24 minutes. Its retries ran at +24 min and
+3.5 min instead of +30 s. It settled only after about 32 minutes.
> - This pull request lets idle check slots admit entries that come due
during a pass. It does not overlap a running check's resource and does
not start work after abort or drain.
> - The benefit is that the existing 3-attempt bound settles a failing
copy-back in about 1 minute, not after the slowest unrelated export.

## Linked Issues or Issue Description

**What happened?**
A native run whose result is durable but whose workspace copy-back
failed stays in `running` with the label "Reconnecting" for a long time.
Its saved retry time is in the past. The retry does not run until an
unrelated slow copy-back in the same queue pass finishes.

**Steps to reproduce**
1. Start two native runs on different tasks with accepted results.
2. Let the copy-back of run A take many minutes.
3. Let the copy-back of run B fail. It saves a retry 30 seconds later.
4. Watch run B. Its retry does not run until run A's copy-back ends.

**Expected behavior**
Run B retries on its saved schedule. After 3 failed attempts it settles
with `native_workspace_sync_out_retry_exhausted` and a recovery action,
while run A continues.

**Paperclip version or commit**
master at a0c0851766. Observed on a staging deployment of 69dea878.

**Agent adapter(s) involved**
Not adapter-specific (core bug). Observed with the Paperclip runner.

**Relevant logs or output**
In-run copy-back failed at 23:44:57 (retry saved for 23:45:28). The
deploy restarted the server at 23:49:45. One queue check exported
another task's workspace from 23:53:54 to 00:06:15. The overdue retry
ran at 00:09:06, then 00:12:39, and the run settled at 00:12:40 with
`native_workspace_sync_out_retry_exhausted`. About 20 other runs
followed the same timeline. Their failures at 00:12–00:13 are this same
retry exhaustion. They do not come from a reattach sweep.

## What Changed

- `registerScopedDeadlineWork` has a new opt-in
`concurrentChecks.admitDueWhileActive` option. While a pass still has a
running check, idle slots admit entries that are due (saved deadline,
failure backoff, and minimum spacing). They wait for the next deadline
or the next check completion.
- Admission skips entries owned by the pass and entries whose resource
keys a running check holds. It stops on abort or when `canRun()` is
false. The pass still ends only when all running checks end.
- The native workspace finalization queue opts in. Its `check` already
rereads the durable coordinator row before it acts.
- Regression tests: a real-database test in
`native-workspace-finalization-work.test.ts`, plus two helper tests
(resource-key safety and shutdown).

## Verification

- `cd server && npx vitest run
src/__tests__/native-workspace-finalization-work.test.ts
src/__tests__/scoped-deadline-work.test.ts
src/__tests__/delivery-work-coordinator.test.ts` passes (68 tests).
- Red before the fix: "keeps another task's saved retries on schedule
and settles it while a slow copy-back is running" failed because
`onWorkspaceSettled` was never called for the failing run. "admits saved
deadlines that come due behind a slow check without overlapping its
resource" failed too.
- Green after the fix: the failing run makes exactly 3 attempts, settles
as `terminal_failure` / `native_workspace_sync_out_retry_exhausted`, and
the slow copy-back is admitted once and then succeeds.
- `tsc --noEmit` shows no errors in the changed files.
- Not done: a live staging check. This change is not deployed.

## Risks

- Low to medium. Only the workspace finalization queue opts in. Other
queues keep the current behavior.
- An admitted entry is not reconstructed first. The finalization `check`
rereads durable state, so a stale in-memory entry cannot start a second
export. The advisory lock and owner receipt still prevent two copy-backs
for one run.
- This change does not fix the cause of the copy-back failures on
staging (`native_session_lease_lost` in-run, then
`workspace_sync_out_failed` with no detail). That needs a separate
change.

## Model Used

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

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native execution can save an accepted result before workspace export
fails.
> - Recover must preserve the files and settle that same result without
another agent turn.
> - The export failure can own the task's Blocked transition and its
recovery notice.
> - Recovery must prove preservation, stop, source identity and current
authority before settlement.
> - Recover can also transfer repair ownership to the system, which must
keep explicit board export repair available.
> - This change restores those paths while preserving later task state,
live owners and unknown action outcomes.

## Linked Issues or Issue Description

Related #17916 protects unfinished exports. Related #19004 preserves
deliberate Retry authority. This change repairs accepted-result
finalization and its recovery notice.

**What happened?**

A native run accepted its result, then exhausted workspace export.
Normal Recover could save the files but leave the result and old
recovery notice unsettled. A saved Retry could also use a different
generation from preservation. Separately, Recover changed the action
owner from board to system. Explicit export Retry then rejected that
same source as stale, even when its accepted result and stopped
allocation still matched.

**Expected behavior**

Preserve files, finish the original accepted result and close only its
obsolete notice after exact proof. Keep explicit board export repair
available after system adoption. Preserve current authority, Stop, later
task changes, newer execution and every physical ownership fence.

**Steps to reproduce**

1. Accept a native result and exhaust its workspace export.
2. Request ordinary Recover, optionally with an existing saved Retry.
3. Let file preservation complete. Inspect the task and source recovery
notice.
4. To reproduce the explicit-repair defect, call export Retry after
Recover adopts the action. Before this repair it rejects the valid
system-owned source before any provider call.

## What Changed

- Record the exact accepted result, contract, task status version and
prior decision when export exhaustion creates Blocked.
- Bind passive finalization to complete, source-bound preservation and
the current chat/review authority. Recheck the proof under status commit
locks.
- Keep the saved Retry identity separate from its preservation
generation. Retain coalescing and reject malformed or mismatched
history.
- Preserve an existing published GitHub assessment for the
no-publication status-unproven wait. Keep all semantic, result,
authority and publication checks.
- Close an old export notice only after its own accepted result, export
or preservation receipts, effects, projection and old Runner ownership
settle. Keep newer work and files unchanged.
- Permit explicit board export Retry after Recover transfers the exact
source to the system. Require valid system and workspace recovery
records for that same run, plus every existing result, stop, allocation,
task and shared physical-lock check. Reject missing proof, another
source and agent ownership before provider access.
- Merge current master while retaining the stronger Stop, source and
verified preservation checks.
- Update the execution contract. No schema, endpoint, Runner protocol,
model, trust or provider artifact change.

## Verification

- Current head: `9be5a73489801095c9f39c9bfcb285a22b055912`.
- The explicit-repair regression fails before the new guard change:
ordinary Recover adopts the real database action, then export Retry
rejects it as stale. It passes after the repair.
- On the merged source, three owning PostgreSQL/file suites pass **220
tests**: `preserved-native-response-wait.integration.test.ts`,
`native-workspace-export-retry.test.ts` and
`idle-completed-history.test.ts`. The export suite includes four
unrelated-owner/source rejection controls and both board-owned and
system-adopted active-exporter controls. Results and run counts prove
that repair creates no provider turn.
- Server layered typecheck, module boundaries, `git diff --check`, and
the added-line secret/private-incident scan pass.
- Full workspace typecheck (`pnpm -r typecheck`) and build (`pnpm
build`) pass. The complete local `pnpm test:run` is still running; its
completed serialized chat partitions pass. Current-head CI is green (53
successful checks, 3 expected skips). Greptile reviewed
`9be5a73489801095c9f39c9bfcb285a22b055912` at 2026-11-05 00:58 UTC with
confidence **5/5**; all review threads are resolved.
- Original qualification remains supporting evidence: the
accepted-result fixture exercises HTTP Recover, real files, the
finalizer, saved Retry coalescing, eight commit races, current
principal/resource/head changes, Stop, later task changes, notice
closure and repeated delivery. No accepted response, provider turn or
publication is replayed.
- Earlier failures remain recorded. Original red controls exposed
wrong-action preservation, unrelated finalization barriers, generation
mismatch and a lost GitHub assessment; the original repair added
controls for them. The prior CI run
[41908572248](https://github.com/paperclipai/paperclip/actions/runs/41908572248)
failed an unchanged completed-history runtime fixture because its
loopback service refused connections through the original ten-second
readiness window. That owning suite passes unchanged in the 220-case run
above. The earlier readiness failure's cause remains unproven; no wait
or assertion was weakened.
- Local and CI tests do not establish hosted task recovery. The serving
revision and original-task outcomes require separate verification after
rollout.

## Risks

- Historical failures without the exact export-owned Blocked receipt
keep their current task status. A saved result does not grant execution
or publication authority.
- Explicit export repair still resumes only the same retained allocation
under the shared physical lock. It cannot take over an active exporter,
create another agent turn, choose a replacement sandbox or discard saved
files.
- Retiring a source notice does not release physical resources or modify
a newer task owner, later Done state or saved snapshot history.
- The proof adds bounded reads only for concrete recovery obligations.
It adds no recurring worker or polling timer.

## Model Used

OpenAI GPT-6 through Codex for the original repair. OpenAI GPT-6.1 Sol
(`gpt-6.1-sol`), xhigh reasoning, for the merge, integration
verification and system-adopted export repair. Both used repository
tools, code execution and PostgreSQL tests. The runtime does not expose
a context-window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used
- [x] I have checked ROADMAP.md and searched for duplicate or related
work
- [x] I have described the issue and linked related public PRs
- [x] I have included no internal task identifiers or private incident
links
- [x] I have added behavioral and safety regression tests
- [x] I have documented verification limits and risks
- [x] I have updated the execution contract
- [x] All Paperclip CI gates are green on the current head
- [x] Greptile is 5/5 on the current head with no unresolved comments
- [x] I will address review comments before requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Devin Foley <devin@paperclip.ing>
2026-11-04 17:16:17 -08:00
DottaandPaperclip 9e5039f6c8 fix(daytona): retain duplex close ownership after lease cleanup (#19150)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Remote agents use provider plugins to manage sandbox leases and
duplex channels.
> - The host verifies the exact route and session when a bound channel
closes.
> - Daytona lease teardown removed that binding before a later host
close.
> - The incomplete reply made the host retire the shared worker. A later
command then failed during setup.
> - This pull request retains bounded close receipts and joins the
original cleanup.
> - Later work can use the same worker while invalid or unconfirmed
closes still fail closed.

## Linked Issues or Issue Description

**What happened?**

A normal lease teardown can close a duplex channel before the host
closes its bound route. Daytona then replies with only the route ID. The
host requires the original session ID too. It logs `duplex channel close
not acknowledged; retiring worker`, stops the worker, and rejects a
later `environmentExecute` call as stopped.

**Expected behavior**

A later close must identify the original route and session and join its
existing cleanup. It must not revive the channel or restart the worker.
Unknown ownership and an unconfirmed close must retain the existing
failure behavior.

**Steps to reproduce**

1. Acquire two leases through one Daytona provider worker.
2. Open a duplex channel on the first lease.
3. Release that lease, then close the bound channel through the host.
4. Execute a command on the second lease.

**Paperclip version or commit**

The real host/worker regression fails on
`69dea878c0b920b335594debb0286443ba9f6c35`. This branch includes master
`3a72efebe5b050e7594bb309e71a0d5f3072db00`.

**Deployment mode**

Managed Daytona sandbox execution in Cloud. The regression uses a local
real host and SDK worker. Only the external Daytona SDK and PTY are
simulated.

Related work: Refs #19004, Refs #10429, Refs #18065, Refs #19050. These
cover task Retry, managed worker startup, drain release, and stale
provider sessions.

## What Changed

- Retain at most 4096 retired route/session bindings with their original
close promises.
- Remove live channel authority before cleanup starts. Concurrent and
later closes join the same promise and return the stored binding.
- Reject reuse of a retained route ID. Keep unknown and evicted routes
unable to provide a session acknowledgement.
- Add a real child-process host/SDK-worker/Daytona regression with only
the external Daytona SDK and PTY simulated.
- Cover release, cancellation, stop, destroy, resume, a stalled close,
concurrent closes, failed cleanup, forged IDs and bounded eviction.
- Document close receipt scope and its separate provider-termination
checks.

## Verification

- RED before the repair: the actual host and SDK worker reproduced the
retirement log, SIGKILL, stopped state, and later `environmentExecute`
rejection. The direct provider cases also failed because the session ID
was missing.
- Combined-branch provider suite: 364 passed.
- Combined-branch host manager, duplex, managed readiness and composed
suites: 142 passed. The final test-only manifest-loading change passed
all 6 composed cases again.
- Full repository typecheck (`node scripts/typecheck-low-memory.mjs`),
focused server types and the standalone Daytona build passed. The
provider build uses pinned SDK `0.203.0` and TypeScript `5.9.3`.
- The composed cases check the same worker/PID, one later command,
preserved saved content, revoked channel writes/stops, and continued
rejection of commands on closed leases. A stalled close still retires
the worker and dispatches no later command.
- Reproduce with `pnpm --dir server exec vitest run --config
vitest.config.ts
src/__tests__/plugin-worker-manager-daytona-close.test.ts` from a
checkout with the SDK build dependencies prepared. Run the provider
suite with its own Vitest config.
- An initial local typecheck lacked the excluded providers' SDK links
after an install with scripts disabled. The standard root postinstall
linker restored those local links, and full typechecking then passed.
The fixture loads its actual built manifest. Failed setup output is
retained; no type configuration was relaxed.
- The first two composed attempts stopped during fixture setup. They are
not the behavioral RED above. Their failed outputs are retained.
- The first broad local typecheck stopped at an IPC permission error
during preflight. It passed after rerunning with allowed local IPC and
the standard SDK links.
- [Full repository
CI](https://github.com/paperclipai/paperclip/actions/runs/41945022881)
passed all 48 jobs on this head. Selection fell back to the complete
task plan, so the run was unfiltered. The complete check inventory has
54 successes and two expected Storybook skips; the separate Snyk status
passed. Fresh Greptile review on
`daebbeb5005b9d9bd59d32ea30094279048b9213` is [5/5 with no actionable
comments](https://github.com/paperclipai/paperclip/pull/19150#issuecomment-6646621914).
Independent production and fixture review found no actionable findings.
Hosted acceptance of this repair has not run.

## Risks

- A receipt removed at the 4096-entry bound cannot confirm an old bound
close. The existing host guard remains effective in that case.
- Close acknowledgements identify channels. They do not certify remote
process termination or clear unknown external work. Provider termination
receipts and existing recovery gates remain in force.
- Hosted evidence confirms the unacknowledged-close retirement class.
The historical log does not distinguish a missing session ID from a
timeout or another close error. The regression proves and repairs the
missing-binding path; it also preserves timeout retirement.
- The provider is outside the main pnpm workspace. A new Cloud image
must compile its changed source. The unchanged plugin version alone does
not prove activation.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, source editing and tool
execution. The exact 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-11-04 19:15:50 -06:00
DottaandClaude Opus 5.5 d8dbe55368 fix(workspaces): let a missing task repository ref be corrected (#19072)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents can ask Paperclip to prepare a task repository at a specific
ref
> - Paperclip records that request and clones the repository at the next
admission
> - Paperclip did not check that the requested ref exists
> - When the ref was absent, every admission retried the same checkout
and failed setup
> - A corrected request was refused as a conflicting receipt, so the
task stayed blocked with no repair path
> - This pull request identifies a missing ref and lets a corrected
request replace it while nothing was published
> - The benefit is that a bad ref becomes a repairable error instead of
a permanent setup failure

## Linked Issues or Issue Description

**What happened?**
An agent asked for a catalog repository at ref `main`. The repository
default branch is `master` and has no `main` branch. Each later
admission failed with `setup_failed`: `git checkout main` reported
`pathspec 'main' did not match`. The task failed admission for more than
a week and became blocked. A new request with ref `master` returned 409
because the receipt with ref `main` already existed.

**Expected behavior**
Paperclip should report that the requested ref does not exist. A new
request with a valid ref should replace the bad ref when no checkout
exists.

**Steps to reproduce**
1. Use a repository whose only branch is `master`.
2. Request it as a task repository with `ref: "main"`.
3. Admit the task. Preparation fails, and the receipt goes to
`needs_repair`.
4. Request the same repository with `ref: "master"`. Before this change,
the request returns 409.

## What Changed

- `managed-repository-checkout.ts`: When `git checkout <ref>` fails with
Git's unknown-ref message, and each quiet `rev-parse --verify` probe
(`<ref>^{commit}`, `refs/remotes/origin/<ref>^{commit}`) exits 1, throw
`RepositoryRefNotFoundError`. The message tells the caller to request
the repository again with an existing branch, tag, or commit. Other
checkout failures keep their original cause.
- `execution-workspace-repositories.ts`: Record `failureCode:
"repository_ref_not_found"` for that error. Other failures keep
`repository_preparation_failed`.
- `execution-workspace-repositories.ts`: A request for the same
repository with a different ref replaces the ref only when all of these
are true: the receipt is `needs_repair` with `repository_ref_not_found`,
it has no pinned commit, the workspace is local (no remote authority),
and the checkout path is absent. The update is conditional on that state
and writes a `workspace.repository.ref_corrected` activity entry. All
other ref changes keep the existing 409 conflict.
- `execution-workspace-repositories.ts`: Local admission re-reads each
receipt inside the checkout lock and claims it with a conditional update
on its current ref. A correction that commits after the receipts were
listed is used, not discarded.
- Tests: one regression for the full missing-ref path, and one safety
test that keeps the receipt immutable when files exist at the checkout
path or when the failure was not a missing ref.

## Verification

- Red before the fix: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/task-workspace-repositories-persistence.test.ts` failed
with `expected ... /ref "main" does not exist/ but got 'Failed to
prepare managed checkout fo…'`.
- Green after the fix: the same file passes (11 tests). The new test
admits with ref `main` (fails, `repository_ref_not_found`, no checkout),
corrects to `master`, admits again (ready, pinned to the source commit),
and then confirms that a later ref change after publication still
returns a conflict.
- The regression also holds the checkout lock while admission waits,
corrects the ref, and then releases the lock. Without the in-lock
re-read it fails with 409; with it, admission publishes the corrected
ref.
- `heartbeat-managed-clone-credentials.test.ts` passes: an
authentication failure during checkout keeps its cause and is not
reported as a missing ref.
- Other checkout suites pass (9 files, 317 tests): reopen, base storage,
expiration, project env, rehome, nested clone migration, object cache,
trust, source copy privacy.
- Neighbor suites pass: `managed-repository-checkout.test.ts`,
`task-workspace-repositories.test.ts`,
`task-workspace-repositories-trust.test.ts`,
`heartbeat-project-repositories.test.ts` (47 tests). In one parallel run
under heavy machine load, `heartbeat-project-repositories.test.ts` had
timing failures in different tests on each run. The same file passed
alone with this change (47/47) and on master.
- `tsc --noEmit` for `server` passes.

## Risks

- Low risk. The new correction path is narrow and fails closed. Remote
(sandbox) workspaces keep the existing conflict because the control
plane cannot inspect the remote checkout.
- Existing receipts that failed before this change have
`repository_preparation_failed`. They need one more failed admission to
get `repository_ref_not_found` before a correction is accepted.
- No schema change. `failure_code` is free text.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, agentic coding with
tool use (shell, file edit, browser read-only diagnostics).

## 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 18:25:41 -06:00
35d086213e fix(native): explain failed accepted results on the run (#19091)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A native run ends when the server projects the accepted result onto
the run row.
> - Operators read `errorCode` and `error` on a failed run to learn why
it failed.
> - Three native writers can set a run to `failed` without these fields.
#19063 repairs the finalizer retry-exhaustion writer.
> - The two result-projection writers still set `failed` with no cause
when the provider failure is not classified.
> - This pull request makes both projection writers record a cause when
they project `failed`.
> - The benefit is that, with #19063, no native writer can leave a
failed run unexplained.

## Linked Issues or Issue Description

Refs #19063.

**What happened?**

A staging soak found 11 failed native runs with `errorCode` null and
`error` null. In each run the provider turn succeeded. Workspace
copy-back then failed three times, and the finalizer retry-exhaustion
path set the run to `failed`. #19063 fixes that writer. An audit of
every `heartbeat_runs` status writer found two more writers with the
same defect. `finalizeNativeRun` and `projectCommittedRun` project an
accepted result with `runTerminalState: "failed"`. They write the error
fields only when a classified provider failure exists.

**Expected behavior**

A run with status `failed` always has an `errorCode` and an `error`. A
cause that is already recorded stays.

**Steps to reproduce**

1. Accept a native result with `runTerminalState: "failed"` and no
`turn.failed` event.
2. Call `finalizeNativeRun` with `projectRunStatus: true`, or project
the committed run through `projectCommittedRun`.
3. Read the run. On master its status is `failed`, and its `errorCode`
and `error` are null.

## What Changed

- Add `explainFailedRun` in
`server/src/services/native-runtime/native-run-failure-explanation.ts`.
It builds SQL SET expressions. They keep a non-empty existing
`errorCode` or `error`. Otherwise they write `native_run_failed` and a
readable message. By default the expressions leave a run that is already
`cancelled` unchanged, as the status writers do.
- `finalizeNativeRun` uses the helper when it projects `failed`. The
existing provider-failure and recovered-failure values remain the
preferred causes.
- `projectCommittedRun` uses the helper for a `failed` accepted result.
This writer sets status unconditionally, so it does not use the
cancelled branch.
- Writer audit: all legacy and heartbeat callers of `setRunStatus*` that
pass `failed` already set both fields. The other direct native writers
also set both fields.

## Verification

- Red, with the source change stashed: 3 new cases failed with
`errorCode: null`. These were the finalizer projection, recovery
projection with no cause, and recovery projection with an empty error.
The control case with a recorded cause passed.
- Green: `npx vitest run
src/__tests__/native-run-projection-work.test.ts
src/services/native-runtime/native-run-finalizer-telemetry.test.ts
src/__tests__/native-status-arbiter-corpus.test.ts` (225 passed).
- Wider run: all `server/src/__tests__/native-*.test.ts` and
`server/src/services/native-runtime/*.test.ts` (7416 passed). Two
failures come from the local environment. `plugin-runner-probe` needs
the Rust runner source. `native-ingress-bootstrap-retry` needs the
controller-host OpenCode proxy.
- This change was not exercised on staging. It changes only fields that
are written at terminal projection.

## Risks

- Low risk. The change affects only rows that are projected to `failed`.
A recorded cause is never replaced. Succeeded and cancelled projections
are unchanged.
- The staging failures need #19063 too. This PR alone does not change
their writer.
- The cause behind the staging copy-back failures is still unknown. The
sandbox command reports the redacted
`remote_workspace_operation_failed`, which also appears in a separate
signature. This PR does not change that diagnostic.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, through Claude Code
with tool use (shell, file edits, browser reads of staging run records).

## 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: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:40:05 -06:00
DottaandPaperclip b7ef307a65 fix(workspaces): wait for an ended writer's save instead of failing setup (#19074)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agent runs can use a remote task workspace in a sandbox. One writer
run at a time holds that workspace.
> - When a writer run ends, its workspace save and sandbox release
finish a few minutes later. The writer hold stays until then.
> - A new run that started in that interval failed with `setup_failed`:
"The workspace still has an unfinished writer or save operation."
> - A live writer already makes the new run wait (`workspace_busy`,
added in #18691). An ended writer that is still settling did not.
> - This pull request reports the ended writer as the holder too, so the
new run waits instead of failing.
> - The benefit is that work continues automatically after a short
settle interval. Users do not see a failed run and a blocked task.

## Linked Issues or Issue Description

Refs #18691

**What happened?**
A task run failed during workspace setup with `setup_failed` and the
error "The workspace still has an unfinished writer or save operation."
The previous run on the same task had ended a few seconds before. Its
sandbox lease was released 4 to 17 minutes later. A staging soak
recorded 31 of these failures over 14 tasks in about 29 hours.

**What did you expect to happen?**
The new run waits for the workspace, as it does when the previous writer
is still running. It continues when the save and release settle.

**Steps to reproduce**
1. Bind a task to a remote workspace whose authority has `dirty: true`
and a `writerRunId` for a run with status `failed` that ended now.
2. Wake the task's agent.
3. The run fails with `setup_failed`. No retry is scheduled.

## What Changed

- `reserveRemoteWorkspace` now throws `RemoteWorkspaceWriterActiveError`
for an ended writer whose save or sandbox release has not settled.
Before, it threw a plain 409 conflict. The error keeps the same message,
status and `remote_workspace_writer_pending` code. It uses the writer's
`finishedAt` as its last activity and sets `ended: true`.
- Workspace preparation already converts that error to a
`workspace_busy` deferral with the one-hour holder cutoff. It now passes
`holderEnded` to the deferral.
- `WorkspaceBusyDeferral` says "ended, but its workspace save has not
settled" for an ended holder, instead of "is still running".
- A writer that is missing or has no task still gets the plain conflict,
as before.
- `doc/execution-semantics.md` describes the wait and its cutoff.

## Verification

- Red before the fix: the new test "waits for a writer that just ended
while its save or sandbox release settles" failed. The run had `status:
failed`, `errorCode: setup_failed`, `executionRecovery.kind: bootstrap`.
This is the same result as the staging failures.
- Green after the fix:
`cd server && npx vitest run
src/__tests__/heartbeat-remote-workspace-busy.test.ts
src/services/remote-workspace-reservation.test.ts`
  (16 tests pass.)
- Safety cases in the new tests:
- The waiting run does not change the workspace authority. The unsaved
writer's files and hold stay unchanged.
- A writer that ended more than one hour ago still causes the
unfinished-writer setup failure, with no scheduled retry. The wait is
bounded.
- Existing cases still pass: live writer wait with Stop and
reassignment, the concurrent reservation race
(`remote_workspace_writer_pending`), and the unused-reservation
takeover.
- Also run: `remote-workspaces.integration.test.ts`,
`heartbeat-workspace-busy.test.ts`,
`remote-workspace-save-concurrency.test.ts`,
`workspace-recovery.test.ts`. All 6 suites pass (442 tests).
- Not done: a live run on staging at this revision. The fix is not
deployed.

## Risks

- A task whose ended writer never settles now makes up to about one hour
of short `workspace_busy` waits (one scheduled retry every 1 to 2
minutes) before it fails. Before, it failed at once. The end state after
the cutoff is the same.
- Some of the staging failures have other causes that this change does
not fix:
- A previous run that stays `running` with no output for more than one
hour still causes this failure, as intended.
- A dirty workspace whose writer ended without a saved result (for
example a workspace finalization retry that never runs) still blocks the
task after the cutoff. These need separate fixes.
- No schema, API or UI changes.

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with
tool use (shell, browser reads of staging run records) 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: Paperclip <noreply@paperclip.ing>
2026-11-04 17:39:26 -06:00
3634c4a845 fix(chat): log the underlying error when a publication becomes delivery_unknown (#18875)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Chat connectors (Slack and others) publish agent messages to
external conversations through the publication worker
> - When the provider accepts a message but the local commit after the
send fails, the publication becomes `delivery_unknown`
> - A `delivery_unknown` row blocks the later publications in the same
conversation until an operator resolves it
> - The log line for this case contains only the generic reason. It
drops the error that the post-send commit threw
> - Operators cannot see why the commit failed, so they cannot tell a
race from a lease loss or another cause
> - This pull request adds the underlying error, redacted and
length-limited, to the existing warning for this case only
> - The benefit is that operators and maintainers can find the real
cause of stalled chat threads

## Linked Issues or Issue Description

Refs #18874

I searched open and closed issues and pull requests for
`delivery_unknown`, Slack publication failures and "could not confirm
its durable result". I found no duplicate. #18874 describes the bug and
the proposed behavior fix. This pull request does not change behavior.
It only makes the cause visible.

## What Changed

- `server/src/services/chat-channels/publications/failures.ts`: when
`providerAccepted` is true, the `chat publication failed` warning gets a
`cause` field. The field contains the underlying error message, redacted
with `redactSensitiveText` and limited to `MAX_ERROR_TEXT`, like the
existing `error` field.
- The stored `redactedError`, the disposition and the operator
resolution flow do not change.
- `server/src/__tests__/chat-run-milestone-work.test.ts`: the existing
`rechecks resolved prompt ownership after delivery ...` test now spies
on `logger.warn`. It asserts that `delivery_unknown` logs the `cause`,
and that `retry` and `failed` do not log it.

## Verification

- Ran in a `node:24-bookworm-slim` container on this branch:
- `npx vitest run src/__tests__/chat-run-milestone-work.test.ts` (in
`server/`): 72 passed.
  - `pnpm run typecheck` (in `server/`): passed.
- Negative check: I removed only the new `cause` field and ran the test
again. The two `delivery_unknown` cases failed. The `retry` and `failed`
cases passed.

## Risks

- Low risk. The change adds one field to one warning log line, only for
publications that the provider already accepted.
- The new field uses the same redaction and length limit as the existing
`error` field, so it does not add a new path for secrets into the log.

> 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`), 1M context window, in
Claude Code with tool use (shell, file edits, code search). A human
reviewed and requested the change.

## Checklist

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

Co-authored-by: lyuboslavmedarov <285605601+lyuboslavmedarov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:38:39 -06:00
Nicky LeachandClaude Opus 5.5 2c39a2866b feat(plugins): let managed config grant worker variables to a plugin (#19049)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A managed instance gets its plugins from the control plane, and
people do not install them
> - Some of these plugins need deployment values, such as a service
origin and a capability
> - The plugin worker environment is scrubbed, and only a fixed list of
first-party providers gets deployment variables
> - Core also sends lifecycle events and project instance secrets only
to plugins with a saved config row
> - So a provisioned plugin cannot work until a person saves
configuration for it
> - This pull request lets the managed config grant named variables to a
selected plugin
> - The benefit is that a distribution plugin can run with no tenant
configuration, and Core does not need to name it

## Linked Issues or Issue Description

No issue exists. This describes the problem. I searched open pull
requests for related work and found no duplicate.

**What existing behavior does this improve?**

How a plugin provisioned through `plugins.autoInstall` receives its
deployment settings.

**Current behavior**

`buildPluginWorkerEnv` forwards variables only from a fixed list that
names first-party packages. A distribution plugin cannot be on that list
without Core naming it. A plugin with no `pluginConfig` row gets no
resource lifecycle events, and project instance secrets refuse it.

**Proposed behavior**

The managed config can include `plugins.workerEnv`. It maps a catalog
key to variable names. The worker of that plugin receives those
variables from the server process environment. The managed config can
also include `plugins.enabledCompanies`. It maps a catalog key to
company IDs. In those companies, the plugin gets lifecycle events and
can use project instance secrets with no config row.

**Reason and benefit**

The control plane stays the only place that decides which plugin gets
which values. Tenants do not handle these values, and they are not in
the database or in the managed config document.

**Alternatives considered**

- Create a config or enablement row for each company. The control plane
would need a new tenant write API, and a row can stay after the document
removes access or return after a database restore. The document is never
saved, like the other managed settings.
- Give a grant access to every company. This has no per-company opt-in.
- Put the values in the managed config document. The document does not
hold secrets, and this rule stays true because it holds only names.

## What Changed

- `parseManagedConfigEnv` accepts an optional `plugins.workerEnv`
object. Each key must be in `plugins.autoInstall`. Names must be
uppercase variable names, with no duplicates.
- Startup fails if a name looks like a host credential, such as a name
with `KEY`, `TOKEN`, `SECRET`, `PASSWORD`, `DATABASE` or `JWT` as a
word, or if it controls how the worker loads code, such as
`NODE_OPTIONS`.
- `parseManagedConfigEnv` accepts an optional `plugins.enabledCompanies`
object. Each key must be in `plugins.autoInstall`. Values must be
lowercase company UUIDs, with no duplicates.
- `managedPluginWorkerEnvGrant` and `managedPluginEnabledCompanies`
return values by plugin key. A managed instance only starts a selected
plugin from the current image, so the saved package path is not part of
this check. It can be old or null.
- `buildPluginWorkerEnv` forwards granted variables. This does not
depend on the `environment.drivers.register` capability.
- Plugin lifecycle delivery includes a plugin with no config row only
for the companies the document names. It still skips a company that
disabled the plugin. The runtime event listener follows the same rule.
- Project instance secrets accept a plugin with no config row only for a
company the document names.
- A variable grant alone gives no company access.
- `doc/plugins/DISTRIBUTION-PLUGINS.md` describes both sections.

## Verification

- `npx vitest run` in `server/` on `managed-config`,
`managed-plugin-worker-env`, `plugin-lifecycle-delivery`,
`project-instance-secrets`, `plugin-database` and `bundled-plugins`: 201
tests pass.
- The new tests cover a digest-verified distribution plugin, a stale
saved package path, a catalog key named after an inherited property, a
company the document does not name, a company that disabled the plugin,
a named company that does not exist, a grant with no enabled company,
and host credential names.
- `tsc --noEmit` in `server/` shows no errors in the changed files. My
worktree does not have the built `@paperclipai/paperclip-runner`
package, so unrelated files show errors locally. CI runs the full
typecheck.
- I did not run the full test suite locally. CI runs it.
- I did not test this change on a running instance.

## Risks

- **Older builds:** a build without this change refuses a document with
`plugins.workerEnv` or `plugins.enabledCompanies`, because it fails
closed on unknown keys. A control plane must send these sections only to
a build that supports them.
- **Trust:** the granted plugin gets the variable values in its process.
The image owner and the control plane already trust this plugin. The
name check stops a mistake with host credentials, but a name check
cannot prove that a value is not sensitive. Core cannot use an
allowlist, because it must not name the variables of a private plugin.
- **Company scope:** the control plane sets which companies a plugin
serves. A new company needs a new document, and the server reads the
document when it starts. Self-hosted instances do not change.
- **Rotation:** a new value reaches the worker only when the server
restarts. Rotation without a restart is not part of this change.
- **Background calls:** a grant does not add proactive company access. A
plugin that makes background calls with no invocation still needs a
saved config for that company.
- This changes core plugin behavior, and I did not discuss it in `#dev`
first. Maintainers can tell me if they want a discussion before review.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), through Claude Code, with tool
use and code execution.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 15:24:43 -08:00
Devin FoleyandPaperclip c7390bef20 fix(server): stop one slow copy-back from starving workspace retries (#19111)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A native run can accept its result before the workspace copy-back
finishes. A background worker retries a failed copy-back from the
retained sandbox without another provider turn.
> - Until that retry succeeds, the run stays `running` and keeps its
task's execution lock. User answers and wakes for that task stay queued.
> - The worker checked one entry at a time (`batchSize: 1`, no
concurrent checks). A slow sandbox export held every other task's due
retry for the whole export.
> - Work without a saved retry time used a deadline of `0`. Copy-backs
that a restart interrupted always sorted ahead of retries that were due
hours earlier.
> - On a busy server that restarts often, scheduled retries never got to
the front of the queue. Their tasks stayed stuck.
> - This pull request runs up to four checks at once, one per task, and
orders pending work by when it became pending.
> - The benefit is that one slow export cannot stall unrelated tasks,
and an old retry runs before newer work.

## Linked Issues or Issue Description

Refs #17734 (added this worker), #18421 (the same concurrency pattern
for native reply recovery) and #19063 (open; records why finalization
failed a run). This pull request does not change their behavior.

**What happened?**

On a hosted instance, sandbox exports were slow for a few hours. A
harness-state save timed out after about 5.5 minutes in 27 runs, and
worker copy-back attempts took 2 to 34 minutes each. Eleven native runs
then waited in `retryable_failure` (or `workspace_finalizing` with an
expired lease) for more than three hours. Each one had exactly one
copy-back attempt and no retry. The worker's operation receipts show its
attempts ran strictly one after another. After each restart, the worker
first took the copy-backs that the restart interrupted. They have no
`nextAttemptAt`, so their deadline was `0`. The server restarted every
30 to 40 minutes, so the overdue retries never ran. One stuck run held a
task whose user had answered a question card. The answer's wake stayed
queued for the same three hours.

**Expected behavior**

A slow copy-back for one task does not delay due copy-backs for other
tasks. Due work runs oldest first. New work without a saved retry time
does not go ahead of older due retries.

**Steps to reproduce**

1. Create two native runs with accepted results on different tasks. Put
both in `retryable_failure` with an overdue `nextAttemptAt`. The first
is older.
2. Make the copy-back for the older run block.
3. Start the worker. On `master` the second task's copy-back never runs
while the first one blocks.

The new worker test runs these steps against PostgreSQL.

**Paperclip version or commit**

`master` at `69dea878c0b`.

**Deployment mode**

Any server with native runs that use remote sandbox workspaces.

## What Changed

-
`server/src/services/native-runtime/native-workspace-finalization-work.ts`:
- Remove `batchSize: 1`. Add `concurrentChecks: { limit: 4 }` keyed by
`issue:<company>:<issue>`. Runs that share a task keep their copy-backs
serialized. Ownership is still checked per run by the existing advisory
lock and owner receipt.
- Work without a saved retry time uses the coordinator's `updatedAt` as
its deadline, not `0`. The value is clamped to now, because the database
clock can be slightly ahead of the process clock. Saved retry times and
lease expiry behave as before.
- `doc/native-workspace-finalization-recovery.md`: one paragraph on
worker concurrency and ordering.

## Verification

- Failing first: "retries another task while an older copy-back is still
running". On `master` the second task's copy-back never ran while the
older one blocked (the test timed out after 15 s). Now the second task
finalizes while the first is still blocked. After release, the first one
also finalizes.
- Failing first: "orders work without a saved retry behind older overdue
retries". On `master` the interrupted copy-back had deadline `0` and
sorted first. Now it sorts after the older overdue retry, and its
deadline is not in the future.
- `cd server && npx vitest run
src/__tests__/native-workspace-finalization-work.test.ts
src/__tests__/scoped-deadline-work.test.ts
src/__tests__/server-startup-feedback-export.test.ts`: 3 files, 159
passed. After rebasing onto `69dea878c0b`, the first two files: 38
passed.
- `cd server && npx tsc --noEmit -p tsconfig.json`: pass.
- Not verified: the worker on a live instance under slow exports. The
diagnosis comes from that instance's operation receipts and run events.

## Risks

- Up to four copy-backs can now run at once on one server. Each one uses
its own dedicated database connection and sandbox transfer. Live runs
already finalize in parallel, so this is within the existing load
pattern.
- The batch size is now the default (25). A pass ends when its slowest
check ends, but the other lanes keep processing the rest of the batch.
- `updatedAt` changes on any coordinator write. A pending entry with new
writes moves later in the order, but it stays due.
- No schema migration.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) through Claude Code, with tool use
(shell, file edits, test runs, read-only database and log queries). The
runtime does not expose the context window or reasoning configuration.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-04 15:14:39 -08:00
801d9548e3 test(server): cover competing recovery action inserts (#18773)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Recovery owners record one active action for each task source.
> - Two owners can read an empty source before either inserts its
action.
> - PostgreSQL rejects one insert, and Drizzle wraps that conflict.
> - The production fix for wrapped conflicts already landed in #18885.
> - This pull request adds a real database race test for that fix.

## Linked Issues or Issue Description

Refs #18885 and #18544. Related recovery work: #18402 and #18705.

**What happened?**

[CI run
41585908789](https://github.com/paperclipai/paperclip/actions/runs/41585908789/job/125752385968)
failed with `issue_recovery_actions_active_source_uq`. The recovery
worker did not handle the wrapped PostgreSQL conflict. #18885 now
provides the production fix.

**Expected behavior**

Both competing recovery owners return the same active action. The
existing bounded retry handles the exact wrapped unique constraint.

**Steps to reproduce**

1. Create a task in real PostgreSQL.
2. Hold both recovery inserts until both owners read an empty source.
3. Release both writes.
4. Check that the losing transaction records a wrapped `23505` conflict
and both service calls return one action.

## What Changed

- Retain the newer bounded cause-chain handling from #18885.
- Add a deterministic database race test. Check the actual wrapped
conflict, both returned action IDs, the saved action count, and the
attempt count.
- Keep the original real-clock settlement and idle-window assertions.

## Verification

- The two owning suites passed **98/98 tests** after resolving the
conflict: `pnpm exec vitest run
server/src/__tests__/issue-recovery-dependencies.test.ts
server/src/__tests__/issue-recovery-actions.test.ts`.
- Workspace typechecks passed across the root run and a successful
server rerun. The first server attempt could not resolve the local
plugin SDK from provider sources. Restoring that checkout-only SDK link
fixed resolution. The server typecheck rerun passed.
- Full local `pnpm test:run` has not run. The current local full build
stopped after the host exhausted disk space during native compilation.
Generated caches were removed from this checkout; no source or test
setting changed. Fresh CI is complete: 53 successful checks, two
expected skips and one neutral security scan. The branch is mergeable,
and the exact-head Greptile review is 5/5 with zero unresolved threads.
- Greptile scored current head
`dbaee78272a5b0f1db87ba33340a63571ef16948` **5/5** with no actionable
findings.
- Historical red proof: before wrapped-conflict handling, this race
returned one rejection. The losing transaction carried PostgreSQL code
`23505` and constraint `issue_recovery_actions_active_source_uq`.
- Preserve prior CI evidence: an [old serialized
job](https://github.com/paperclipai/paperclip/actions/runs/41649267044/job/125964435813)
hit an agent-audit setup timeout and a heartbeat wait failure. The exact
timing cause remained unproven. Its [single
rerun](https://github.com/paperclipai/paperclip/actions/runs/41649267044/job/125972675510)
passed. This PR changes no waits or assertions in those tests.

## Risks

- Low risk. The final diff adds one database regression test. It changes
no production code, schema, API, permissions, runner routing, or
workflows.

## Model Used

- OpenAI GPT-6.1 Sol (`gpt-6.1-sol`), xhigh reasoning, tool use, and
code execution.

## Checklist

- [x] I have included a thinking path and specified the model used.
- [x] I have searched for related work and linked the production fix.
- [x] I have included only public issue and PR references.
- [x] The changed test passes against real PostgreSQL.
- [x] I have documented verification limits and risks.
- [x] All Paperclip CI gates are green.
- [x] Greptile is 5/5 with no unresolved comments on the final head.
- [x] I will address review comments before requesting merge.

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Devin Foley <devin@paperclip.ing>
2026-11-04 14:47:29 -08:00
c19c4348c5 fix(server): record inspector provenance for dynamic import callback errors (#17844)
<!-- entire-trail-link-start -->
https://entire.com/gh/paperclipai/-/trails/49/changes/paperclip/49
<!-- entire-trail-link-end -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The server sends errors to Sentry when an operator sets a backend
DSN. An unhandled rejection is captured in `strict` mode, so the server
process exits after the capture.
> - A hosted server reported `TypeError
[ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING]: A dynamic import callback was
not specified.` two times, eight days apart, on two unrelated releases.
Each event stopped the process.
> - The stack has no first-party frame. An anonymous `setTimeout`
callback at `<anonymous>:1:N` calls `import()`. The column changed
between the two events, so the source text changed.
> - In local reproduction, only two paths make that frame:
`vm.compileFunction` without a filename, and an expression that a client
evaluates through the Node inspector. No shipped server code or
production dependency uses either path. The current events give no data
about the source.
> - This pull request adds bounded provenance to that one error: the
inspector state when the event is reported (never its URL), allow-listed
Node flag names, the process ids, the main-thread flag and the uptime.
> - The benefit is that the next event gives evidence about the source
of the anonymous code. A `closed` state rules out an expression from a
still-open inspector session. An `open` state needs other evidence
before a debugger is blamed.

## Linked Issues or Issue Description

No public issue exists. This section follows the bug report template.

**What happened?**

A hosted server process stopped two times with an unhandled rejection:
`TypeError [ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING]: A dynamic import
callback was not specified.` The stack is
`importModuleDynamicallyCallback (node:internal/modules/esm/utils:280)`
← `Timeout._onTimeout (<anonymous>:1:20)` (first event) or
`(<anonymous>:1:41)` (second event) ← `listOnTimeout`. The events came 3
minutes and 17 minutes after process start, on two releases that changed
only skill documents and UI files. The Sentry event has no data that
shows where the anonymous code came from.

**Expected behavior**

The error report gives enough data to investigate the source of the
anonymous code: whether an inspector session was open in the process,
and which inspector-related Node flags were set.

**Steps to reproduce**

1. `node -e "require('node:vm').compileFunction(\"setTimeout(() =>
import('node:fs'), 1)\", [])()"` gives the same error and the same
`Timeout._onTimeout (<anonymous>:1:N)` frame.
2. An expression such as `setTimeout(() => import('node:fs'), 1)`,
evaluated through the inspector (`Runtime.evaluate`), gives the same
error and frame.
3. `vm.Script` (`evalmachine.<anonymous>`), `eval` or `new Function` in
a `vm` context (`eval at …`), `new Function`, indirect `eval`, `node
-e`, `node -p` and stdin give a different frame or import successfully.

**Paperclip version or commit**

`master` at `0cb3fc7db591ba23e21fbbc70042bc44ec7d6f20`. The hosted
events came from builds `f96a1e64` and `3e7c3ee9`, on Node v24.22.0 and
v24.22.1.

**Relevant subsystem**

Server Sentry error monitoring (`server/src/sentry.ts`).

## What Changed

- New `server/src/sentry-dynamic-import-provenance.ts`:
`addDynamicImportProvenance(event, hint)` acts only when
`hint.originalException.code` is
`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`. It adds:
- the tag `dynamic_import_inspector` with the value `open`, `closed` or
`unavailable`
- the context `dynamic_import_provenance` with `inspector`, `nodeFlags`,
`mainThread`, `pid`, `ppid` and `uptimeSeconds`
- `nodeFlags` holds only the names of `--inspect`, `--inspect-brk`,
`--inspect-wait`, `--inspect-port`, `--debug-port`, `--disable-sigusr1`
and `--experimental-vm-modules` from `execArgv` and `NODE_OPTIONS`. Flag
values and all other flags are dropped.
- The hook never sends the inspector URL, because the URL contains a
session secret. It loads `node:inspector` lazily, so a Node build
without the inspector still starts and reports `unavailable`. The hook
never throws. Every other event is unchanged.
- The module comment and the doc say that the state is evidence at
report time, not proof of origin.
- `server/src/sentry.ts`: `beforeSend` is now `enrichServerEvent`. It
runs the existing run-link enrichment, then the new hook.
- `doc/observability.md`: the default capture set names the new tag and
context, and says what is never sent.
- `.github/workflows/sentry-contract.yml`: the Real Sentry SDK isolation
job also runs the new test file.
- Tests in
`server/src/__tests__/sentry-dynamic-import-provenance.test.ts`:
  - the real Node error from `vm.compileFunction`
- a real open inspector session: the URL and its secret are absent from
the event. The test closes only the session it opened, and it skips when
a developer already has an inspector open.
  - flag filtering with values and other flags present in `NODE_OPTIONS`
  - other codes and other hint shapes stay unchanged
  - a failed state read keeps the original event
- the real `@sentry/node@10.71.0` SDK sends the original rejection to
the server `beforeSend`, and enriches only the matching event. With
`PAPERCLIP_REQUIRE_SENTRY_TEST_SDK=1`, a missing SDK fails the file.

## Verification

- With the audited SDK on `NODE_PATH` and
`PAPERCLIP_REQUIRE_SENTRY_TEST_SDK=1`, as in the contract job:
`run-failure-sentry-real-sdk.test.ts` and the new test file pass (17
tests).
- Without the SDK: the new test file, `sentry.test.ts` and
`sentry-links.test.ts` pass (52 pass, 6 skipped).
- With `PAPERCLIP_REQUIRE_SENTRY_TEST_SDK=1` and no SDK: the new test
file fails at once, as intended.
- Red: with `beforeSend` set back to `addSentryRunLinks`, the real-SDK
test fails (`expected [] to have a length of 1`). Green with the change.
- Hosted CI on this head: typecheck, build, all test partitions and Real
Sentry SDK isolation pass.
- Not run locally: the full Vitest suite and browser suites. A live
hosted check is not possible before deployment.

## Risks

- Low risk. The hook acts only on one error code. All other events keep
their current content and grouping.
- The process still exits after the capture, as before. This change adds
diagnostics. It does not fix the source of the anonymous code.
- `pid`, `ppid` and uptime describe the server process only. They hold
no tenant, user or request data.
- If a later Node version changes the error code, the hook stops acting
and the events are the same as today.

> 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 the Claude
Agent SDK, with tool use (shell, file edit, code execution, GitHub and
Sentry connectors).

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-04 14:07:26 -08:00
a3313f6944 fix(ui): complete read recovery and enforce query error rendering (#18822)
## Thinking Path

> - Paperclip lets people manage agent work through the board.
> - The board reads task, agent, settings, plugin, and run data from the
server.
> - A deploy can interrupt those reads after content is already visible.
> - Local error branches can replace that content with an error or show
raw codes.
> - This pull request moves the remaining reads to the shared recovery
behavior.
> - The query-error guard prevents new local error patterns from
returning.

## Linked Issues or Issue Description

**What happened?**
Loaded lists, settings, task panels, and manual reads could clear their
content or show errors during a server restart. Some reads also disabled
the shared retry policy.

**Expected behavior**
Keep loaded content during a temporary failure. Show the shared
connection banner. Retry reads after recovery. Replace content after a
real access denial or missing resource, with readable copy.

**Steps to reproduce**
Open a task, settings page, or run log. Return 503 with
`tenant_app_unavailable` from the API. Restore the API and check that
content refreshes. Repeat with 403 and 404 to check the failure state.

Related merged work: Refs #15754, Refs #15772, Refs #17960. This change
covers remaining reads and enforcement. Browser persistence and the
outage browser suite follow separately.

## What Changed

- Repair an existing PostgreSQL deadline fixture at UTC boundaries. A
daily cap defers a heartbeat until both its original interval and the
next UTC day allow it. Keep the counted run in its seeded day and cover
noon, just before midnight and the first second after midnight with
explicit clocks and real PostgreSQL. Production scheduling is unchanged.

- Integrate the saved list, settings, shell, task, and manual-read
recovery changes against current master. Preserve the current task,
Inbox, and run-log structure.
- Add `useResilientFetch` for component-local reads. Preserve loaded
data, cancel stale requests, and retry through the shared connectivity
policy. Keep browser and pairing capabilities in memory.
- Retain run log chunks across stream changes and catch up after
recovery without duplicate chunks or stalled requests.
- Keep genuine authorization and missing-resource failures visible.
Translate known single-word codes such as `forbidden`. Show known skills
and disconnected-app failures before sibling loading states. Keep Retry
bound to its failed request.
- Make the query-error guard enforce by default and run it in the CI
policy job. Document the narrow exceptions and the read recovery
contract.
- Invalidate pending OAuth discovery when the operator leaves setup or
its context changes. Keep reserved-popup cleanup bound to the current
attempt.

## Verification

- Current read head `09d1db29dea` is merge-ready: all 59 check runs are
completed (54 successful, three skipped, two neutral), the legacy Snyk
status is successful, and both required `ci / verify` and `ci / e2e`
gates pass. Greptile Apex reviewed this exact head at 5/5 with no
unresolved threads; the branch has no merge conflicts. The current CI
run is `41748339602`.
- Current read head `09d1db29dea` includes master `683fa4725e9`. The
latest change touches only the existing server deadline test fixture.
Application source remains the previously verified `86376fa751e`
revision: all 60 affected skills/disconnected-app tests and both
application UI typechecks passed, along with token gates and the
enforcing query-error guard. Those UI suites were not repeated for this
test-only repair.
- The fixture repair passes all seven deadline integration cases and the
complete server typecheck, including the server test layer. The old
assertion deterministically fails at 23:59:06.888 UTC; its
one-second-old run also fails the first-second-of-day probe. The
corrected fixture passes noon and both midnight probes. The permanent
three-case coverage uses seeded 2099 clocks.
- Twelve new regressions fail against the previous implementation. Both
skills variants and disconnected-app pages show either request's 403 or
429 while the sibling pauses or remains fetching. Permission failures
stay visible. Retry stays usable for its failed request and recovers
independently. An initial test-fixture query-key typo was corrected
before the passing run. The finding is replied to and resolved. Greptile
reviewed prior application revision `86376fa751e` at 5/5 with no new
actionable findings or unresolved threads. The current test-only head
has now received its own Apex 5/5 review with no unresolved threads, and
its CI is successful as recorded above.
- The preceding OAuth fix passed all 237 setup tests on both application
branches. Its ten regressions prove that Back, Cancel, unmount, or
context changes retire initial discovery without creating a connection,
caching old identities, navigating an abandoned popup, or closing a
newer attempt.
- Earlier master integration `e992f770339` passed all 309 tests across
eight affected Slack, recovery, retry, submission and task files, plus
UI typecheck, token gates, enforcing query-error scan and seven guard
tests. The integration preserves Slack workspace reconnect and shared
recovery, readable execution errors, source-inspection company scoping,
and current submission behavior. Newly inherited retained-file recovery
controls keep cached data through transient failures and hide it after
definitive denial.
- The preceding cache head `97c0bc3966f` passed 1,038 focused
cache/write/editor/account/task/API tests. All 22 built-UI
outage/write/loading cases passed in 4.7 minutes on exact browser head
`b7deaaee5da`, with strict checking of all three specs. The latest
skills/app rendering fix is covered by its affected component suites;
those broader cases were not repeated for this separate read path.
- Historical broader UI, CLI and build results and unresolved aggregate
server/DB/adapter failures remain in the PR discussion. Interrupted CI
runs were audited and recorded separately; they are not counted as
passes. Controlled local outages do not prove hosted authentication or a
real Cloud restart/deploy. Real hosted acceptance remains required after
merge and deployment.

Commands for the latest fix:

```sh
pnpm --filter @paperclipai/ui exec vitest run src/pages/CompanySkills.test.tsx src/pages/apps/AppNotConnected.test.tsx
pnpm --filter @paperclipai/ui typecheck
pnpm check:token-gates
node scripts/check-query-error-rendering.mjs --enforce --summary
```

## Risks

This migration touches many read surfaces. The main risks are initial
loading, request cancellation, retained authorization state, and run-log
paging. The tests cover those states. There are no server, schema,
dependency, or API contract changes. The follow-up browser suite and
hosted deploy acceptance are required before release acceptance.

Recovered commits retain their original authors. If squash-merging,
preserve `Co-Authored-By: Bender (Fable) <bender@agents.paperclip.ing>`
and the Paperclip trailer.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with xhigh reasoning effort, tool
use, and code execution. The context window size is not exposed. Saved
source commits keep their original authorship.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have described the issue in-PR following the bug report template
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change and contains no internal
ticket id
- [x] The affected local tests pass; broader limitations are 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 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: devinfoley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Bender (Fable) <bender@agents.paperclip.ing>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 14:07:18 -08:00
DottaandPaperclip 52b4b3ccd7 test(evals): pin native credential delivery and worker receipt fixes (#19042)
## Thinking Path

> - Paperclip must deliver agent work through ordinary human controls.
> - The latest three-case run retained three failed originals.
> - The personal value was saved, but the native OpenCode child did not
receive the configured task environment.
> - A worker proof also relied on legacy process identity fields outside
that environment contract.
> - Timeline and credential checks had independent review defects that
could reject truthful outcomes or miss unsafe instructions.
> - This PR pins the reviewed fixes together and keeps all prior grades
unchanged.
> - A new AWS measurement must still prove the real outcome.

## Linked Issues or Issue Description

Refs #18963, #18967, #18968, #18971, #18972, #18977, #19027, #19031.
Follows merged #18995. This is the next infrastructure pin, not another
core implementation PR.

The latest launch 2657 retained FAILED originals for timeline,
approved-tool replay and personal secret. The personal journey reached
secure UI save and a worker wake, but the worker reported no credential
and made no endpoint call. These are failed attempts, not new passes.
Six of the selected ten cases have independent passes across prior
attempts. The uniform final ten and the full catalog remain unqualified.
No old result was regraded.

## What Changed

- Pin immutable candidate `c6cc59282a45ed3c79880a220f7f60aa3ac1d6a4` on
`codex/chat-credential-destination-contract-candidate-20261104`.
- Retain the frozen candidate baseline. Add the accepted failed-action
completion feedback hint, canonical configured environment delivery to
OpenCode, actual native worker output receipt checks, timeline
attribution controls, and credential clause safety checks. The feedback
change leaves the existing acceptance gate in place.
- Declare exactly seven oracle-version changes: timeline v5 to v6;
personal v5 to v6; company v4 to v5; secure API key, least-scope
delivery, secret proposal and vault reference each external v1 to v2.
The four external cases have explicit overrides; the shared default is
unchanged.
- Sync the manifest rows, explicit changed-field records, descriptor
snapshot and expected target. Preserve all 244 identities, original
queries, human/native counts, clocks, model, profile, launcher bytes,
privacy limits and the existing 196/244 qualification gate.
- Include the reviewed retry cancellation readiness fixture repair from
`815d47e4b5d7756367d2c5e2da656b640273eac2` in this infrastructure PR
only. It awaits the first scheduled batch before polling its state. Its
production behavior and assertions are unchanged. This test repair is
absent from the model candidate.
- Keep the core PRs, including #19027 and #19031, unmerged. A normal
merge of this pin does not merge those PRs.

## Verification

- Pin-local checks: all 189 existing manifest, TestOps API, adapter,
reconciliation and streaming controls passed. The reviewed 815 fixture
repair passed its owning real-PG control (one selected test). A
temporary delayed-first-batch control failed with the old observer and
passed with the new observer. Batch limits, assertions, drain wait and
overall budget are unchanged. The original CI cause remains unknown.
- Final candidate: 89 credential/receipt consumer controls passed. The
exact Runner TypeScript build, 68 driver controls and one owning
failed-action PG control passed before the later helper-only changes.
Their runtime inputs remain byte-identical; those results are reused
explicitly, not claimed as new executions on every source commit.
- Real pinned OpenCode 1.18.34 executed a canned local-provider bash
call. Synthetic `PUBLISHING_API_KEY` reached its child in a 2.89-second
passing control with zero paid provider calls. This proves
server-to-bash inheritance; driver spawn controls cover the preceding
projection boundary. Actual AWS secure-entry, worker delivery and
privacy acceptance remain pending.
- **The latest local candidate declaration loader is NOT GREEN.** It
exceeded its unchanged 30-second limit in both a combined run and an
isolated run. Five other declaration/contract checks passed. Both
failures remain recorded. No timeout was widened and no further retry
occurred. Descriptor and definition modules match the earlier passing
5aa source; only the transitive credential helper changed. Prior
242-declaration evidence supports unchanged definitions only, not a
claim that these failed executions passed.
- The snapshot matches the candidate and exactly seven oracle-version
leaves differ from source 249. All 244 identities, queries, run/human
counts, clocks, model, profile, launcher and privacy limits remain
unchanged. The SDK guard reports 506 unchanged inputs, unchanged runtime
pins and registry
`efa70621e9fb42aa185f7ee616a1a726aa3eccb367e67e28817cedf26ef2ade8`. The
driver is outside those SDK roots. No SDK renewal occurred.
Reporting-lock and whitespace checks passed.
- Genuine exact-head reviews: source 19e3 is 5/5 with no unresolved
threads; current pin 988 is 5/5 with no unresolved threads. The prior
pin 4/5 and resolved P1 remain historical. Current exact-head normal CI
41917215227 completed successfully: 48 successful jobs and one selector
skip, including final verify job 126888275415. Windows 41917215440
succeeded. The full PR rollup is 56 successes and three intentional
skips, with no pending or failed checks. Older green heads do not
qualify this head. Paperclip has only an earlier-head 5/5 assessment,
which is stale for this head and receives no current assessment credit.
- Six of ten cases have independent passes across prior attempts. Launch
2657 retains three FAILED originals; no grade was changed. The next
measurement is the exact uniform ten, cap eight and zero automatic
retries. No uniform-ten or full-catalog acceptance is claimed.
- The local-tests checkbox below covers only the 189 pin controls and
owning fixture control. It does not claim all candidate or repository
tests are green. Full local repository checks were not repeated for this
infrastructure diff. Core PRs remain unmerged. No model call, eval
dispatch, private archive read, fleet change or merge occurred for this
update.

## Risks

Environment delivery crosses a real provider process boundary, so secret
isolation remains part of the next actual AWS acceptance. A worker
receipt must match an actual successful native run and key-authenticated
endpoint call; a copied boolean does not qualify. Oracle safety and
attribution changes can alter acceptance, so balanced negatives remain
required. No current uniform-ten pass is claimed. Normal CI and genuine
exact-head review must finish before protected infrastructure merge.
Root owns the next TestOps Submit, with the original model, limits, zero
automatic retries and maximum eight concurrent case chains.

## Model Used

OpenAI `gpt-6.1-sol` through Codex. Capabilities used: source analysis,
mechanical integration, local code execution and PR preparation. The
host does not expose the context-window limit. The evaluated model
remains OpenCode/OpenRouter `deepseek/deepseek-v4.1-flash-1017`.

## 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 15:52:12 -06:00
DottaandPaperclip 4740d251b7 fix(onboarding): retain plan approval across follow-up decisions (#19043)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Onboarding asks a person to approve a saved plan before execution
tasks can start.
> - A person can then approve a separate proposal for the first
execution step.
> - The creation gate checks only that latest card for the plan target.
It loses the earlier approval of the unchanged plan.
> - Decomposition and native context also omit checkbox plan
confirmations.
> - This pull request validates the latest execution decision and the
latest plan decision together.
> - The benefit is that approved work can start while human, revision,
and proposal gates remain effective.

## Linked Issues or Issue Description

Refs #17914, #18160, and #18283. The related-PR search found no
duplicate repair.

**What happened?**

After a person accepted a checkbox plan card and a later execution
confirmation, native task creation and REST child creation returned 403.
Accepted-plan decomposition returned 422 because its lookup recognized
only standard confirmations. Native task context returned no accepted
plan for the checkbox decision.

**Expected behavior**

The latest execution proposal can use an independently approved,
unchanged plan. A pending or rejected latest proposal must still stop
execution. The current plan must retain valid human approval of its
exact revision.

**Steps to reproduce**

1. Save a plan on a server-created onboarding task.
2. Create a revision-bound checkbox confirmation and accept it as a
human.
3. Create and accept a separate, untargeted confirmation for the first
execution task.
4. Create the child through the native tool or REST API.
5. Separately, try accepted-plan decomposition or read native task
context after step 2.

**Paperclip version or commit**

The workspace's registered hosted release is Core
`09c05c55050fede448196637cbaa543a154da76e`. The three regressions also
failed against local source `3cdb367369f29692b81cf19b514a2abdba90734f`.

**Deployment mode**

Hosted native Runner with ACPX Claude. Local regression proof uses
actual PostgreSQL, authenticated HTTP routes, native tool authority, and
synthetic task data on macOS arm64. No customer data or credentials are
in the fixtures.

## What Changed

- Validate the newest ordinary proposal and the newest plan proposal
separately. Keep the existing human-card and conversational provenance
checks for each.
- Require the accepted plan target to match the current document and
revision. A newer pending, rejected, invalid, or foreign plan decision
cannot fall back to an older approval.
- Share persisted confirmation parsing across admission, decomposition,
and native context. Validate checkbox selections against the saved
options and limits.
- Return `acceptedPlan.approvedOptions` with the user's saved selected
IDs and option text on later wakes, including revision-pinned context.
Keep unchecked defaults out of scope.
- Add behavioral and safety regressions. Document the approval rule.

## Verification

- Three behavioral regressions failed before the repair: child creation
returned 403, decomposition returned 422, and native context returned
`acceptedPlan: null`. All three now pass.
- Two scope-retention regressions failed before the review fix: native
context omitted the selected option after a later approval, with and
without a pinned plan revision. Both now pass. Unchecked defaults stay
excluded.
- Current admission, native-tool, and accepted-confirmation replay
suites: 131 tests passed. Existing first-task completion safety checks:
17 passed, including the atomic decision recheck.
- Safety cases cover missing and rejected plan approval, agent-only
decisions, foreign source runs, changed revisions, invalid checkbox
selections, newer pending or rejected proposals, and task-creation
replay.
- Full repository `pnpm -r typecheck` and `pnpm build` passed locally on
final head `7c0e8ab784`.
- [Full final-head
CI](https://github.com/paperclipai/paperclip/actions/runs/41911580531)
passed: all Vitest shards, Runner checks, eight browser E2E shards,
build, release registry, and canary dry run. The required `verify` and
`e2e` aggregates passed. Storybook checks were skipped for this backend
change; the file-selection job was skipped because full CI ran.
- Earlier local typecheck attempts exposed Copilot verification during
build preparation, stale declarations across the rebase, and two
test-fixture typing errors. The assets were verified, declarations
rebuilt, and fixtures corrected before the complete final-head pass.
- Full local Vitest attempts were interrupted for the rebase and review
repair. The duplicate final local run was stopped after full Linux CI
passed. No complete macOS Vitest-suite pass is claimed.
- A fresh Claude-provider acceptance run remains unverified: the local
Claude account is signed out and no Anthropic API credential is
available. The actual native tool and HTTP paths are verified with real
PostgreSQL persistence. This PR does not deploy or recover the reported
production task.
- Greptile: 5/5 on `7c0e8ab784`, with the review thread resolved.

## Risks

A later acceptance must not revive rejected or changed scope. Admission
validates both current decisions and retains source, company, human
provenance, and document locks. The extra plan-proposal lookup is
bounded and limited to accepted, untargeted first-task proposals.
Ordinary delegation adds no new proof lookup. The native context adds an
optional `approvedOptions` projection. Revision-pinned context now
performs the existing scoped approval read to recover that selection. No
database schema, request, provider, or background worker changes are
included.

## Model Used

OpenAI Codex, exact model `gpt-6.1-sol`, max reasoning effort, with
repository, shell, browser, and GitHub tools. The current session
metadata confirms the model and effort. The context window size 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-11-04 15:24:58 -06:00
Nicky LeachandClaude Opus 5.5 be20d4ad97 fix(projects): keep projects on the storage chosen at creation (#19036)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A project stores its work in Core/local storage or in a storage
plugin
> - Today, Core finds the installed default each time it reads a project
> - So installing a default storage plugin moves existing projects,
without a saved change
> - Agents that cannot reach the new storage then lose access to new
workspaces
> - This pull request makes Core apply the default once, when it creates
a project
> - The benefit is that a project stays on its storage until a person
moves it

## Linked Issues or Issue Description

No issue exists. This describes the problem. I searched open pull
requests for related work and found no duplicate.

**What existing behavior does this improve?**

How Core chooses the storage for a project when a plugin claims
`defaultForProjects`.

**Current behavior**

Installing such a plugin changes the storage of every existing project
with no explicit choice. Disabling the plugin changes it back. Nothing
is saved, so the project has no fixed owner. Execution environments and
storage are separate contracts, so an agent can lose access to new
workspaces on a project that it used before.

**Proposed behavior**

Core saves the storage choice on the project when it creates the
project. A project with no saved choice uses Core/local storage. A
plugin-backed project stays attached to its plugin. A Core/local project
can move to a plugin.

**Reason and benefit**

A project stays on its storage until a person moves it, so installing or
disabling a plugin does not change where work goes.

**Alternatives considered**

- Keep the default at read time and add a process that saves a choice on
existing projects when a plugin arrives. This adds a background step and
a race with new projects.
- Add a new database column. The existing policy field already holds the
choice, so a migration is not necessary.

## What Changed

- `resolveProjectWorkspaceProvider` reads only the saved choice. It no
longer applies an installed default.
- `withDefaultProjectStorage` applies the default when Core creates a
project, including a personal private project. With more than one
default, a project with no explicit choice is refused. This is the
existing conflict rule, now applied at creation.
- `projectService.update` refuses a change of storage on a plugin-backed
project. An update that omits the field keeps the attachment.
- Plugin lifecycle delivery sends project events to a storage provider
only for projects attached to it, and for deleted projects. Core
acknowledges the other events for that provider. Other plugins continue
to receive all project events.
- The project settings control has no "Inherit" option. It is locked on
a plugin-backed project.
- `ProjectWorkspaceProviderConfiguration.source` no longer has the
`"default"` value.
- A project with no repository or folder ignores its saved storage
choice when a task runs. A storage plugin cannot manage this type of
project, so its tasks use a plain task directory. Without this change,
the second run of a task reset the agent session, and remote agents
could not use an empty task directory.
- The plugin SDK README and the plugin authoring guide describe the new
rules.

## Verification

- `npx vitest run
server/src/__tests__/project-workspace-provider.integration.test.ts
server/src/__tests__/plugin-lifecycle-delivery.test.ts
server/src/__tests__/plugin-workspaces.integration.test.ts
packages/plugins/sdk/tests/workspace-provider.test.ts`: 93 tests pass.
- `npx vitest run src/components/ProjectProperties.concurrency.test.tsx`
in `ui/`: 9 tests pass.
- `pnpm --filter @paperclipai/server typecheck` and `pnpm --filter
@paperclipai/ui typecheck` pass.
- `npx vitest run` on `heartbeat-session-preparation`,
`workspace-preparation`, `remote-workspace-source`,
`project-workspace-provider.integration`, and
`plugin-workspaces.integration` in `server/`: 231 tests pass.
- I did not run the full test suite locally. CI runs it.
- I did not test this change on a running instance.

## Risks

- **Behavior change at upgrade:** an existing project that uses a plugin
only through the installed default, with no saved choice, moves to
Core/local storage after upgrade. Its plugin files stay in place, but
new work does not use them until an operator sets
`storageProviderPluginId` on the project. This pull request has no
automatic backfill.
- **Missed lifecycle events:** a provider does not receive events for a
Core/local project. If the project moves to that provider later, the
update event starts its reconcile.
- **One-way move:** a person cannot move a plugin-backed project back
through the API or the UI. This is intended. If a plugin is disabled,
the project fails closed, as it does today for an explicit choice.
- This pull request does not stop a person from assigning an agent to a
project with storage that the agent cannot reach. That is follow-up
work.
- This changes core plugin behavior, and I did not discuss it in `#dev`
first. Maintainers can tell me if they want a discussion before review.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), through Claude Code, with tool
use and code execution.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:07:29 -08:00
4ea53f9eb5 fix(server): resolve issue identifiers in file-resources routes (#16352)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents and the board read task workspace files through the
`/api/issues/:issueId/file-resources/*` routes
> - Clients can identify a task by its UUID or by its human identifier,
for example `ABC-230`
> - The issues router changes identifiers to UUIDs, but the
file-resources router does not
> - Thus an identifier goes into a `uuid` comparison, Postgres rejects
the query, and the route returns 500
> - This pull request resolves identifiers in the file-resources router
and returns 404 for malformed ids
> - The benefit is that identifier links work, and bad ids give a
correct 404 and no server error

## Linked Issues or Issue Description

**What happened?**
A request to `GET /api/issues/ABC-230/file-resources/list` returned 500.
The server log showed `invalid input syntax for type uuid` from
`workspaceFileResourceService.getIssue`. Sentry recorded this error on
hosted staging.

**Steps to reproduce**
1. Create a task that has an identifier, for example `ABC-1`.
2. Send `GET /api/issues/ABC-1/file-resources/list?workspace=project`.
3. The server returns 500. The same request with the task UUID returns
200.

**Expected behavior**
The route accepts the identifier, as the other `/api/issues/:id` routes
do. An unknown or malformed id returns 404.

## What Changed

- Added `resolveIssueId` to `WorkspaceFileResourceService`. It changes a
task identifier to the task UUID. It does not check visibility.
`getIssue` still does the privacy-scoped read.
- Added a `router.param("issueId")` handler to the file-resources
router. It calls `resolveIssueId`, the same as the issues router does
for its routes.
- `getIssue` now returns 404 before the query when the id is not a UUID.
This stops the Postgres cast error for all callers.
- Added a regression test for identifier, lowercase identifier, private
task, unknown identifier, and malformed id on the `content`, `list`, and
`availability` routes.
- Updated the test service fakes with the new method.

## Verification

- `pnpm vitest run src/__tests__/file-resources.test.ts
src/__tests__/file-resources-git-scan-load.test.ts` in `server/`: 55
passed.
- Without the source change, the new test fails with `expected 200 "OK",
got 500 "Internal Server Error"`. This reproduces the production error.
- I did not run a full local `tsc` because the sandbox did not have
sufficient memory. CI runs the typecheck.

## Risks

- Low risk. The change adds one indexed lookup on `issues.identifier`
(unique index) only when the path value has the identifier format.
- The identifier lookup does not check visibility. `getIssue` then
applies the privacy-scoped read, so a private or unknown task gives the
same 404 as before. The route does not show if a task exists.

> 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, used through Claude
Code with tool use (shell, file edit, test runs).

## Checklist

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

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

---------

Co-authored-by: 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-11-04 12:30:26 -08:00
DottaandPaperclip 22735cfcea fix(native): preserve governed terminal receipts during observer settlement (#19025)
## Thinking Path

> - Paperclip manages work done by AI agents.
> - A native turn can yield to a saved human question or approval.
> - The provider must stop, and its terminal event must be saved.
> - The old drain waits for both the event transaction and its later
observers.
> - A slow observer can make a saved terminal look unproved.
> - This change separates the committed receipt from owned observer
work.
> - Checkpoint and result acceptance still wait for that work to settle.

## Linked Issues or Issue Description

**What happened?**

A native governed wait can fail with
`governed_wait_provider_terminal_unproven` after its exact interrupted
terminal is already in PostgreSQL. The event port waits for postcommit
accounting, output and projection callbacks before it returns the
receipt. Those callbacks can cross the provider drain deadline.

**What did you expect to happen?**

A committed terminal proves the event transaction. Its observers remain
separate work. A checkpoint or result must not be accepted until those
observers settle. A pending human-only question must keep its audience
and continuation policy.

**Steps to reproduce**

Run the real PostgreSQL control-plane regression. Commit a same-turn
interrupted terminal. Hold its postcommit observer beyond the existing
provider drain. The parent code reports an unproved provider terminal.
The fixed code retains the committed receipt and joins the observer
before checkpoint or result acceptance.

No overlapping repair was found in the current master or open-PR search.
Related [#18977](https://github.com/paperclipai/paperclip/pull/18977)
covers final conversation-response selection. It does not change this
receipt boundary. The source reproduction is consistent with the
observed failure. The exact hosted provider code is unavailable, so it
does not prove that every observed failure used this branch.

## What Changed

- Add an explicit terminal-only option to the internal event port.
Ordinary appends keep their existing behavior.
- Return that receipt only after the normal transaction and company,
agent, run, session, source and revocation checks.
- Retain the full observer promise. Join it before checkpoint and result
writes. Keep its original rejection and observe it after an aborted
join.
- Reuse the existing provider drain and checkpoint deadlines. Preserve
duplicate and restart replay without another model turn.
- Document the receipt and duplicate-observer replay boundaries. Keep
human-only and no-continuation cards unchanged.
- Assert the actual Codex facade requests deferral only for its
interrupted terminal. Isolate empty skill inventory in monitor-only
process fixtures while retaining their real scheduler, lifecycle,
selection snapshot and three-second deadline.

## Verification

- Meaningful parent red: actual native runtime and real PostgreSQL event
port. The terminal row commits, but a held observer causes
`NativeProviderTerminalFailure` with
`governed_wait_provider_terminal_unproven`.
- Current candidate `0db24ae74ee42fdf92035d8407fc095e9f73cc6a`: full
runtime suite, 203 passed. Real PostgreSQL control-plane, timing and
monitor suites, 103 passed. Runner TypeScript and full layered server
typechecks passed on the unchanged candidate source tree. The controls
cover uncommitted and foreign evidence, explicit false and failed
terminals, held and rejecting observers, abort and controller
revocation, duplicate receipts, and new-port recovery. The real pending
human-only card stays unanswered with continuation policy `none`.
Recovery does not call `startTurn` again.
- `pnpm -r typecheck`, the production server build (including the real
Runner release binary), module boundaries and vendor dependency
verification passed on `6d75`; the follow-up changes only tests and
documentation. Diff checks pass on the current candidate. The prior
`399ad3dc2b` repository-wide `test:run` was stopped as obsolete during
the general-server phase after preserving its progress (exit 130,
incomplete); its repository build was not reached. Its six completed
chat shards passed 1,904 cases, but this is prior-head evidence and not
a full-suite pass. Full current-head CI is required before merge.
- CI attempt 1 on `6d75` failed one real three-second monitor-settlement
assertion; the aggregate verify gate consequently failed. Other jobs
passed. The actual CI merge tree was
`3037bf6db162da3b0a9172046360401f8449ebc5` (`6d75` into `89703c3`). An
unchanged full-file replay reproduced three timeouts. Original-deadline
inspection found the legacy process still preparing; local phase timing
measured cold skill inventory at 6.629 seconds. The original CI did not
retain that phase detail, so its exact subcause remains unproved. The
fixture correction supplies empty skill entries for these skill-free
process tests and still invokes the selection-snapshot callback. It
preserves all assertions and the three-second deadline, with no new
drain wait. All 21 cases pass; deliberately restoring the old
blocked-status rejection still fails the real blocked-check case with
409. No CI retry was issued.
- The prior current-head native review was 4/5 with no production
correctness finding. Its requested direct facade-options assertion and
duplicate-observer wording correction are included. Current-head CI is
green (55 success, 3 skipped), including required verify and E2E;
current-head Greptile is 5/5 with no actionable findings. The old native
advisory remains tied to `6d75`, and its requested findings are
addressed. No new native assessment is claimed or required by
protection.
- The first draft broke three existing transaction-start timing
controls. The no-debt path now keeps the original synchronous start, and
all three pass. Two draft type errors were corrected. A further real-PG
red found an extra default-append assertion after commit. Removing it
restores the original committed receipt after a late abort; the same
aborted signal still cannot write a checkpoint or result. Original setup
and assertion failures remain in private verification receipts.
- Normal merge of current master
`3a2249dd5768313f04b2aa3860dfd363492eda16` has no conflicts. The
affected production source blobs did not change in that baseline merge.
- Local proof uses Node 24.22.2 on Darwin arm64, a real PostgreSQL
instance and deterministic provider-boundary fixtures. It makes no model
call. No deployed replay or original-task recovery is claimed.

## Risks

Deferred observers must remain owned. A missed join could accept
incomplete accounting or output. A rejected or stalled observer must
prevent acceptance, and an abort must prevent late checkpoint/result
writes. The tests exercise these boundaries. The port keeps the existing
best-effort trace-publication handling. This change does not turn a PRP
terminal into physical-stop or accounting-completion proof.

## Model Used

OpenAI `gpt-6.1-sol`, with code and shell tools. The host does not
expose the reasoning mode 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 14:11:03 -06:00
DottaandPaperclip d180ad8c77 fix(heartbeat): retry chat execution authorization contention (#19017)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A task Retry must use the current task and chat authority.
> - The request and queue paths already retain a Retry through brief
lock contention.
> - Execution checks that authority twice, including after native input
preparation.
> - A busy task row or chat identity could fail the admitted run at
either check.
> - This pull request retries only the rolled-back authorization
transaction.
> - The same run can proceed after the lock clears. The final dispatch
gate still rejects changed ownership.

## Linked Issues or Issue Description

**What happened?**

A failed chat task can accept a normal Retry and then fail during
execution setup when another PostgreSQL transaction holds the task row
or chat identity lock. The late check can fail after native input and
workspace preparation. The exception can then enter the workspace
preservation path.

**Expected behavior**

Keep the same admitted run and its frozen input. Retry a recognized,
rolled-back authorization conflict within the existing bound. Recheck
current authority before each attempt. Reject Stop, paused agents,
revoked chat access, or a changed task owner before the execution
callback.

**Steps to reproduce**

1. Admit a normal failed-chat task Retry with the existing PostgreSQL
fixture.
2. On a second connection, hold the task row or the chat identity
advisory lock when execution authorization starts.
3. Release the lock after the real authorizer reports contention.
4. Observe that the original code fails the run before the authorized
callback. Repeat at the late native check after input preparation.

**Paperclip version or commit**

Reproduced on master `039e0dad2c51c80251e1302a19c5566e039c357a`. The
branch is rebased on `8f06bd4e1`.

**Deployment mode**

Source checkout with real PostgreSQL and the local HTTP Retry route.
Provider and chat transport responses use controlled test fixtures.

Refs: #18895 and #18896 cover request and queue admission. This change
covers execution authorization. #18898 and #18989 cover predecessor
cleanup, a separate boundary. The roadmap and public PR search show no
duplicate execution-authorization repair.

## What Changed

- Recognize the existing typed chat-presentation contention error in the
bounded admission retry helper. Keep its existing PostgreSQL
lock-unavailable handling.
- Use that helper at both execution authorization checks. Each attempt
checks cancellation and the applicable legacy controller lease, then
opens a fresh transaction. The loop contains no provider or workspace
callback.
- Include failed-chat Retry in the existing final dispatch ownership
gate. This preserves current task ownership and native recovery owner
checks after a delay.
- Add real PostgreSQL contention, late prepared-input, Stop, pause,
revocation, owner replacement, native completion, and ordinary legacy
completion controls. Document the retry and dispatch boundaries.

## Verification

- The unchanged base failed real task-row and chat-identity contention
cases. A separate late native case failed after frozen input
preparation. A safety control then proved that a replaced task owner
could still reach the backend; the final dispatch gate fixes that path.
- All 11 PostgreSQL cases and all four helper controls pass. The tests
keep exact frozen model and trust input, reject changed authority, and
do not replay a provider callback.
- A real local HTTP Retry reaches the native executor, one controlled
provider turn, one accepted result, a committed finalizer, and one
published chat reply. Reprocessing the canonical request does not start
another turn.
- A separate real HTTP ordinary legacy Retry runs one controlled adapter
callback, completes its task through the normal service, and does not
replay the canonical request.
- Independent review found no material issue in the two production
files. It checked transaction scope, lock order, current ownership,
native lease expiry, and import cycles.
- The full workspace typecheck and full `pnpm build` pass on this head.
Full `pnpm test:run` passed all 1,916 chat integration cases. Its
broader server group reported the existing workspace-runtime case
`returns a bounded structured conflict and exposes only same-company
workspace references` as failed after 46 ms. The unchanged focused case
and the complete 179-case file both pass on rerun. The active broad run
was then stopped for this finite handoff; its child exited 130 and the
command exited 1. The detailed assertion was buffered and was not
retained, so its exact cause remains unknown. This is a partial local
run, not a full local pass; later local groups did not run. All three CI
chat shards pass. [Greptile reviewed this exact head at 5/5 with no
findings](https://github.com/paperclipai/paperclip/pull/19017#issuecomment-6638521119).
- Retained setup and fixture failures are not counted as product
regressions or passes. An incomplete copied Copilot runtime failed the
first typecheck; the supported installer repaired that isolated
dependency, then typecheck passed. Early acceptance fixtures used a
wrong publication field or did not complete their task; the final
controls use the real field and completion service.
- The first CI run failed [workspace shard
1](https://github.com/paperclipai/paperclip/actions/runs/41885475363/job/126769239992)
on child-server startup and a five-second dense ZIP test deadline.
[Server shard
2](https://github.com/paperclipai/paperclip/actions/runs/41885475363/job/126769239908)
failed an existing local runtime readiness probe. All 18 cases in the
two CLI files and the runtime case then passed unchanged locally on this
head. The CLI files also match an earlier independently verified head
byte for byte. One failed-job rerun was requested. Its [server
job](https://github.com/paperclipai/paperclip/actions/runs/41885475363/job/126777070120)
was cancelled after the runner received a shutdown signal. It produced
no test verdict. After that workflow ended, only the interrupted server
job and its required dependents were rescheduled once. The [complete
workspace rerun
passed](https://github.com/paperclipai/paperclip/actions/runs/41885475363/job/126777070125).
The [rescheduled server
job](https://github.com/paperclipai/paperclip/actions/runs/41885475363/job/126781960353)
and its aggregate passed. The [complete current-head workflow is
green](https://github.com/paperclipai/paperclip/actions/runs/41885475363).
All required current-head CI and security gates pass, including the
Ready-triggered scans; three conditional checks are skipped. The
complete unchanged idle-history file also passes all 75 cases locally.
No timeout or assertion changed; all attempts remain part of this
result.
- These local controls do not claim hosted-provider or Linux sandbox
acceptance. The original task-row failure did not retain a SQLSTATE; the
PostgreSQL reproduction proves the local gap without assigning that
original cause.

## Risks

- The existing bound allows 50 backoffs of 100 ms. It limits backoff
time, not total database or lease wall time.
- Native ownership is checked at the final dispatch gate. The
per-attempt controller lease assertion applies to legacy execution.
- A persistent lock or changed authority still fails closed. Unknown
prior provider effects and accepted work retain their existing guards.
- No migration or model, account, trust, credential, or permission
policy change is included.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, tool use, code editing, and
PostgreSQL verification. The host does not expose the exact serving
model 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 within the stated scope:
relevant regression and acceptance controls, and the complete chat,
idle-history, and workspace-runtime files. The broad local run remains
partial as 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-11-04 13:07:50 -06:00
Devin FoleyandPaperclip edb9d29942 fix(recovery): preserve Retry authority and reopen cross-task cases (#18997)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Tasks keep execution identity and saved workspace files separate
from control-plane updates.
> - A scanner can have permission to reopen a case while its run cannot
restore that case's files.
> - Retry can also follow a preparation failure that has no
recovery-action record.
> - Both paths can accept valid work but leave the target unable to run.
> - This change restores files under the target executor and saves
admitted Retry authority on the canonical receipt.
> - Existing workspace, user, source and successor checks still apply.

## Linked Issues or Issue Description

**What happened?**

An authorized agent updates another closed task. The route tries to
restore the target workspace under the caller's run identity and returns
`403 Task workspace run identity is unavailable`.

A separate failure occurs when a normal board Retry follows a
pre-provider preparation failure. Admission has no recovery-action row
on which to save continuation authority. Dispatch then fails with
`continuation_user_authorization_missing`.

**Expected behavior**

Accept the authorized task update. Restore its terminal workspace when
the target's admitted executor starts. An admitted Retry must retain its
exact dispatch authority even when the predecessor has no
recovery-action record.

**Steps to reproduce**

1. Complete a task with an isolated workspace. From a different running
task, use an authorized comment or PATCH with `resume: true` on that
closed task.
2. Separately, fail native preparation before provider work starts, with
no recovery-action row. Submit board Retry, admit its successor, and
build the execution continuation.
3. Before this change, the first path returns 403 and the second rejects
its admitted authority.

**Paperclip version or commit**

Reproduced against `e968131efa7e63d321f3bcca8d47a62513c2efb4`. Rebased
onto `afbc5a885b5`.

**Deployment mode**

Server-managed native task execution. Tests use real PostgreSQL and
temporary workspace files.

Related PRs: #16927 covers terminal workspace races. #17916 retains
exact native export sources. Neither covers these route and Retry
receipt failures. A GitHub search found no duplicate. ROADMAP.md was
checked; this is a recovery bug fix.

## What Changed

- Defer workspace restoration only for authorized cross-task comments
and updates from a live run. Checkout keeps its existing workspace
identity checks.
- Restore the target's selected terminal workspace under its own
admitted run. Preserve its saved files and existing source checks. Leave
manually archived workspaces unchanged.
- Store server-created continuation authority on the canonical admitted
Retry receipt when no source recovery-action record exists. Existing
action records remain authoritative. Read the fallback only after
matching the company, task, agent, user, source, generation and
successor.
- Add route and PostgreSQL regression tests. Document both paths.

## Verification

- Red: both cross-task route tests returned the observed 403 before the
route fix.
- Red: the target executor could not reuse its archived terminal
workspace before the restoration fix.
- Red: an admitted preparation Retry with no recovery-action row failed
with `continuation_user_authorization_missing` before the receipt fix.
- Green: 419 recovery and continuation tests pass, plus 183 focused
route, environment and Retry tests. The assembled heartbeat test starts
the agent in the same terminal worktree and verifies both its unpushed
commit and uncommitted file bytes. It fails before the restoration fix.
- Negative cases reject wrong Retry runs, users, source runs,
generations, unadmitted receipts and missing authority. The original
failed run stays unchanged. Workspace tests verify saved file bytes.
- Full `pnpm build`, `pnpm -r typecheck`, `pnpm check:module-boundaries`
and `git diff --check` pass.
- The full suite found an existing assembled chat authority regression:
clearing a recovery-action record could be bypassed by the new fallback.
The fix now writes the fallback only when that source has no action
record. That assembled regression passes with the original test
unchanged.
- Full `pnpm test:run` is running again. Its first attempt also hit an
unrelated ancestor skill-root lookup for a missing Slack skill. The
rerun uses a temporary untracked link to the repository's own bundled
skills. This link is excluded from the PR. Full-suite completion and
hosted CI are pending.
- Hosted CI has multiple runner interruptions. E2e shard 1/8, server
shards 3/12, 8/12 and 12/12, and workspaces-a shard 1/2 show runner
shutdown signals. E2e shard 5/8 and server shards 1/12 and 2/12 have
GitHub annotations reporting lost runner communication; their logs
return 404. The `ci / verify` assertion failed because the general-test
matrix was cancelled. GitHub rejected job and failed-workflow reruns
while this workflow is still running. Rerun the interrupted checks after
it settles.
- Greptile reviewed exact head
`a1e3929aedb1ee3bfcd001f4ebb1510106bf4070` at 2026-11-04 17:37 UTC: 5/5,
with no new blocking findings and no unresolved review threads.

## Risks

- A deferred workspace restore can still fail at target admission. The
run keeps the existing workspace failure path; this change does not
bypass file recovery or provider stop checks.
- Retry receipts now store server-created authority. Existing action
receipts remain valid. No schema migration is required.
- Local regression tests establish the software behavior. The new
revision has not been deployed to a hosted instance.

## Model Used

OpenAI `gpt-6.1-sol` through the Codex harness in T3 Code, with xhigh
reasoning, repository inspection, tool use and code execution. The
context-window size is not exposed by the runtime.

## Checklist

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

> - Paperclip manages agent work through governed product workflows.
> - Product E2E tests check those workflows through the real app and
runner.
> - The saved Product regression plan has 20 imported case identities.
> - The plan has no live executor, so historical baseline rows do not
qualify its current behavior.
> - The existing paid full-stack workflow already owns all 20 required
journeys.
> - This change binds TestOps selection and reporting to that workflow
without changing the product oracle or paid admission.
> - Maintainers can run the plan and inspect current case verdicts and
retained evidence.

## Linked Issues or Issue Description

**What happened?**

The standing Product regression plan has no executor. Its dashboard
points at a historical 355-result snapshot across several suites and
revisions.

**Expected behavior**

A TestOps plan run must select the intended existing full-stack cells,
execute them on GitHub, and report their original case IDs, assertions
and source revision. Missing evidence must remain a visible failure.

**Steps to reproduce**

Open the Product regression plan and inspect Executors. There is no live
job. The existing full-stack workflow accepts exact cell IDs but has no
TestOps selection or reporting path.

**Paperclip version or commit**

The branch includes reviewed master correction 04a24f4e06. This change
does not alter scenario prompts, models, deadlines or assertions.

Related public work: #18542 establishes the separate chat-catalog lane.
#18666 repairs its workflow URL binding. These changes do not implement
this Product plan.

## What Changed

- Add optional TestOps service inputs and a manual bootstrap selector to
the existing full-stack workflow.
- Map the original 20 case IDs and full names to existing native
Codex/local cells, with the existing native OpenCode/local
task-workspaces case.
- Keep reporting tokens in trusted default-branch selection and
publication jobs. Keep the existing paid provider authorization and
protected environments.
- Bind retained results to the workflow, job run, launch, source
revision and selection hash. Preserve missing-result failures and
earlier attempt verdict receipts.
- Verify uploaded identities, coverage and verdicts. Document full-plan
and subset reporting limits.
- Keep the outstanding-decision reservation live while its existing
deletion-fence test asserts the original 409 guard. Release the test
provider in finally. Production deletion and accounting code stay
unchanged.

## Verification

- `node --test tests/runner-e2e/product-regression-testops.test.mjs`: 13
positive and negative controls pass, including the separated CLI
handoff.
- Existing paid-workflow security regression passes.
- `actionlint -shellcheck= .github/workflows/runner-full-stack-e2e.yml`:
passes.
- The Product E2E catalog compatibility test confirms all 20 exact cells
exist: passes.
- `pnpm test:e2e:runner:typecheck`: passes.
- Fresh Greptile review of final head 61ef7a6e is 5/5 (11 files, no new
findings); both initial reporting findings are repaired and resolved.
- `server/src/__tests__/decision-models.test.ts`: all 37 controls pass
on a disposable official PostgreSQL 18 container in 22.02 seconds. The
unchanged focused case passed once and all five local samples; the
original Linux CI failure remains retained. This is not a claimed
deterministic local red reproduction. The new gate explicitly preserves
the outstanding-reservation precondition.
- The existing master readiness-fixture correction passes all three
retained-workspace controls on the same database in 25.79 seconds. It is
incorporated from master, not duplicated in this PR's diff.
- Final-head Linux CI also passed all 37 decision-model controls in
7.047 seconds; the server shard passed 120 files and 2,004 tests. See
[the retained successful
shard](https://github.com/paperclipai/paperclip/actions/runs/41870214517/job/126715358759).
- Previous CI failures are retained: [three stale readiness-fixture
cases](https://github.com/paperclipai/paperclip/actions/runs/41864061890/job/126694812885)
and [the reservation fixture
race](https://github.com/paperclipai/paperclip/actions/runs/41864061890/job/126694812894).
The separate [Canary Dry
Run](https://github.com/paperclipai/paperclip/actions/runs/41864061890/job/126694811605)
recorded a lost self-hosted runner connection before any step. The
fixture repairs are verified on changed source; the separate
Spot-interrupted CI attempt and its bounded retry are recorded below.
- Actual one-cell GitHub canary on the unchanged product source:
https://github.com/paperclipai/paperclip/actions/runs/41860189801 . The
native Codex/local message-marker case passed all six assertions and
cleanup in 35.24 seconds. The persisted issue is Done and its one run
succeeded. This is supporting evidence; it is not TestOps acceptance.
- Final-head CI attempt 1 was interrupted: three failed jobs have EC2
`Server.SpotInstanceTermination` receipts; six cancelled jobs show the
same simultaneous shutdown. Original logs are retained. One [failed-job
retry](https://github.com/paperclipai/paperclip/actions/runs/41870214517/attempts/2)
passed all nine interrupted checks while preserving prior successful
jobs.
- Final-head full repository CI passes, including both required `ci /
verify` and `ci / e2e` gates. GitHub still requires a non-author
code-owner approval for the workflow change; normal auto-merge is
queued. A fresh TestOps-triggered subset/full-plan acceptance remains
pending. Local projection checks do not establish a live Product pass.

## Risks

- The first bootstrap upload must establish the official TestOps job
before the saved plan can use it.
- This lane tests one declared local native configuration per case. It
does not qualify the historical 355-row matrix.
- Upload failures remain visible. A missing retained report produces
broken case results and cannot pass.
- The existing non-TestOps campaign path must keep its selection and
scheduling behavior; paid-workflow regression coverage remains in place.

## Model Used

OpenAI Codex with reasoning, code execution and tool use. The host does
not expose the exact model ID or context window 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-11-04 11:17:39 -06:00
DottaandPaperclip 6364fa7327 fix(heartbeat): wait for retry predecessor cleanup (#18989)
## Thinking Path

> - Paperclip manages agent work and preserves its task ownership.
> - Heartbeat admission controls when a queued run can start.
> - A failed run can still own workspace saving and environment cleanup.
> - Automatic recovery can transfer the task execution lock to its
queued retry before that cleanup finishes.
> - The old queue check then sees the retry itself and misses the
predecessor's remaining work.
> - This pull request checks the exact retry predecessor before claiming
the queued run.
> - Existing cleanup notifications can then resume that same run through
normal admission.

## Linked Issues or Issue Description

Refs #18898 and #18691. The first repair covers canonical connected-task
retries with a native source. The second covers live remote workspace
writers. This change covers ordinary automatic continuation when the
predecessor failed before native runtime selection. The search found no
open repair for that path.

**What happened?**

A preparation failure saved a terminal legacy run row while its remote
workspace save and environment cleanup were still active. Generic
continuation created one queued successor and moved the task execution
lock to it. The queue checked that current lock but did not check the
successor's predecessor. The new run started and failed at the existing
unfinished-writer guard.

**Expected behavior**

Keep that same successor queued until its exact predecessor finishes
execution and cleanup. Check company, agent and task identity before
using retry provenance as a wait condition. Preserve Stop, reassignment,
failed cleanup and independent recovery gates.

**Steps to reproduce**

1. Save a failed legacy preparation run with an active environment
lease.
2. Queue its ordinary automatic continuation and bind the task execution
lock to the successor.
3. Claim the queued run before the predecessor finishes cleanup.
4. The old code starts the successor. The fixed code retains it in the
queue.
5. Commit successful cleanup through the existing cleanup owner. The
ordinary dispatcher starts that same run once.

**Paperclip version or commit**

The causal regression fails on master
`86814c6550f52e99ede150a883bdc773cbb540e3`.

**Deployment mode**

Source checkout on macOS arm64 with disposable PostgreSQL, the assembled
heartbeat service and the production work coordinator. The adapter
boundary is controlled. No hosted provider replay or deployment is
claimed.

## What Changed

- Check the current task execution owner and the retry's exact
predecessor under the existing task claim lock.
- Use the saved native task binding, ordinary task binding and
historical context fallback in that order. Ignore unrelated retry
provenance.
- Reuse the existing live-executor and pending-cleanup checks. Reuse
committed cleanup notifications and the existing dispatcher.
- Add real PostgreSQL claim controls and assembled cleanup-event
acceptance. Keep the original source failure, saved files and current
task ownership intact.
- Document the queued predecessor-cleanup rule.

## Verification

- RED: the unchanged queue claimed the successor and wrote a new
controller lease while its legacy predecessor retained an active remote
environment lease. The regression expected the successor to remain
queued.
- GREEN: the same regression passes after the change. The complete queue
suite passes all 71 tests.
- The assembled heartbeat suite passes all six tests. The added cases
cover one continuation after committed cleanup, saved-file reuse, final
task lock release, board Stop, reassignment and failed cleanup.
- Scope controls cover a deleted source after scan, another company,
another task, another agent, canonical native task binding and the
existing current-owner hold. A failed cleanup and a still-live local
executor retain the wait.
- Node 24.19.0 and pnpm 9.15.4. Module boundaries and `git diff --check`
pass.
- Initial fixture attempts failed on database constraints and observer
timing. The final fixtures use valid immutable bindings, the board Stop
attribution and a completed real dispatcher call after reading the exact
committed lease state. Production ownership guards were retained
throughout.
- Full low-memory repository typecheck passed at `5cddb49322`. The sync
to master `04a24f4e06` adds only the independent retained-allocation
test fixture from #18984; production code is unchanged.
- Full `pnpm build` passes on current head
`c7c2bcf08ffd05be5ce714683ae1dcfe6f5b49f1`.
- The post-sync run executes all 80 behavior assertions successfully
across the queue, assembled cleanup and updated upstream retention
suites. Its queue `afterAll` then exceeds the existing 30-second
database shutdown hook. That failed suite result is retained. The
controlled post-build queue rerun passes all 71 tests with normal
teardown. The earlier shutdown cause remains undetermined. Its owned
queue-test PostgreSQL process has exited. The separate local default
`pnpm test:run` was intentionally stopped after full unfiltered CI
passed on the exact head. Four local chat partitions completed before
that stop; this is not a local full-suite pass. The verified owned
process group and its PostgreSQL fixture have exited. Logs and retained
fixture data remain available. The timeout has not been increased.
- Full unfiltered CI passes on current head
`c7c2bcf08ffd05be5ce714683ae1dcfe6f5b49f1`: all 12 server shards, all
six internal chat partitions, all nine serialized server shards, the
workspace groups, native Runner lanes, typecheck, build and browser E2E.
Both required [ci /
verify](https://github.com/paperclipai/paperclip/actions/runs/41866478617/job/126711606613)
and [ci /
e2e](https://github.com/paperclipai/paperclip/actions/runs/41866478617/job/126711263146)
pass. The file selector is skipped, so this is the full CI test matrix.
The head has 55 successful checks and three expected skipped checks. The
ready-for-review contributor trust and security scan also pass.
- Current-head Greptile is 5/5, its check is successful, and no inline
review threads remain. The ordinary review check also passes. No merge,
deployment or original task retry was performed.

## Risks

- A predecessor with unfinished cleanup keeps its successor queued. Its
existing cleanup owner remains responsible for settlement.
- A task claim reads at most one additional predecessor row. A matching
source also uses the existing ownership and cleanup queries. The change
adds no timer, schema, credential, provider or model policy.
- A committed cleanup signal only requests normal admission. Current
ownership and independent execution gates still apply.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, code editing, tool use and
PostgreSQL verification. The session does not expose a more specific
serving model ID or context-window limit.

## 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 pass; the
separate local full-suite attempt is explicitly incomplete 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-11-04 11:16:54 -06:00
DottaandPaperclip 04a24f4e06 test(recovery): align retained allocation fixture with readiness (#18984)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Restart recovery must preserve the original remote allocation and
its files.
> - #18934 readies that same retained allocation before preparation.
> - The older #18936 failure fixture rejects every readiness call.
> - It stops before the intended preparation-failure boundary.
> - This change updates only that fixture and checks the original
allocation identity.

## Linked Issues or Issue Description

**What happened?**

The three remote preparation retention cases fail after #18934. Their
driver stub throws `Reattachment must not resume the allocation`. The
production code now calls readiness on the same validated lease before
preparation, so the test never reaches its intended execution failure.

**Expected behavior**

The fixture should allow readiness of the original allocation. It must
reject replacement acquisition and retain its preparation-error,
ownership, saved-file and retention checks.

**Steps to reproduce**

Run `src/__tests__/heartbeat-native-remote-failure-retention.test.ts` on
master `86814c6550`. All three cases stop at the stale readiness stub.

Refs #18934 and #18936. This one-file baseline correction is split from
#18970 so it can unblock the shared failure also observed on #18977. It
carries no core API change or evaluation source pin.

## What Changed

- Return the original lease from the fixture readiness stub.
- Assert exactly one readiness call for the original company,
environment, run, workspace, provider allocation and remote path.
- Assert that no new allocation is acquired. Keep all existing
preparation failure, ownership and retained-work assertions.

## Verification

- Before #18934, the three existing cases passed locally. On the actual
merged #18934 contract, they reproduced the three CI failures in 18.06
seconds.
- With this fixture correction, all three passed in 11.83 seconds.
Production runtime code and the original 60-second case deadline are
unchanged.
- The corrected test blob and its relevant production/helper blobs match
the already-tested merged-source proof. Current master still has the
same stale test blob; this isolated PR applies only the identical
fixture correction.
- Command from `server/`: `node ../node_modules/vitest/vitest.mjs run
--config vitest.config.ts
src/__tests__/heartbeat-native-remote-failure-retention.test.ts`.
- `git diff --check` passes. Full repository typecheck/build were not
rerun for this one-file fixture split; fresh normal CI and review are
pending.
- [Original failing CI
job](https://github.com/paperclipai/paperclip/actions/runs/41855440307/job/126665077718).
It remains failed; no remote retry was used as proof.

## Risks

- Low scope: test fixture only. A readiness call for a replacement or
wrong allocation fails the identity assertions.
- This does not alter production recovery, permissions, cleanup policy,
evaluation deadlines or grading.

## Model Used

OpenAI `gpt-6.1-sol`, with reasoning, repository inspection, code
editing and tool execution. The host did not expose the model
context-window limit.

## 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-11-04 10:15:31 -06:00
DottaandPaperclip 86814c6550 fix(recovery): ready retained sandboxes before preparation (#18934)
## Thinking Path

> - Paperclip manages work performed by AI agents.
> - Restart recovery must preserve each run's workspace and execution
owner.
> - A retained remote lease can refer to an archived sandbox.
> - The reattachment path returned that lease without making it ready.
> - A short project-authentication command then had to start the
sandbox.
> - This PR resumes and verifies the exact retained allocation before
preparation.
> - Recovery keeps the recorded workspace and preserves saved work if
resume fails.

## Linked Issues or Issue Description

**What happened?**

A native run resumed after a controller restart. Its saved sandbox had
been archived. Reattachment returned its lease without a provider
lifecycle resume. The next project-authentication probe tried to start
that sandbox with its 15-second command budget. The start failed before
the probe could inspect project settings. The heartbeat reported a
configuration conflict and stopped work.

**What did you expect to happen?**

Make the exact retained sandbox ready through the existing bounded
lifecycle operation. Verify its allocation before running short
preparation commands. Keep the validated lease and recorded working
directory. Never acquire a replacement in this path.

**Steps to reproduce**

Hold the provider's exact-lease resume pending and call remote
reattachment for an active ephemeral lease. Before this change,
acquisition returns immediately. The added tests hold acquisition until
resume completes and reject missing, changed or expired allocation
evidence. Provider compatibility cases retain the recorded workspace
when resume omits directory metadata or returns the allocation root.

**Deployment mode**

Native Runner in a sandbox with a retained workspace and managed AI
connection. The observed provider was Daytona.

Refs #18910, which restores selection of retries using current remote
workspace storage. This change repairs the next preparation boundary.
Related #17421 and #18559 change project-file inspection; they do not
make retained sandbox leases ready.

## What Changed

- Resume the exact validated sandbox lease before returning it to
preparation.
- Require the receipt to confirm the original provider allocation
without reporting expiration.
- Keep the original database lease and recorded directory. Resume
metadata can describe the allocation root.
- Pass the existing typed resume-directory input through the runtime
facade.
- Preserve retained E2B sandboxes when resume timeout setup or workspace
inspection fails. Keep new-allocation cleanup and explicit
release/destroy behavior.
- Preserve plugin-task reattachment and lease identity checks.
- Update the Stop-accounting fixture to confirm exact-allocation resume
before cleanup, with its budget, ownership and no-paid-work assertions
intact.
- Document the ordering and add readiness, receipt, failure and
ownership regressions.

## Verification

- Red: seven initial retained-lease cases failed before the readiness
change. Two receipt-compatibility cases and four E2B preservation cases
failed before their fixes.
- Green: all four runtime, workspace-preparation, heartbeat-recovery and
orchestrator suites pass: **442 tests**.
- The Stop-accounting CI failure reproduced locally. All **five**
integration cases pass with the exact-allocation resume fixture.
- All **22 E2B tests**, the standalone plugin typecheck and its build
pass.
- **13 Daytona safety cases** pass, including recorded directories,
allocation sentinel rejection, saved-file reads and no replacement
creation. The SDK boundary is simulated; the saved-file case uses real
files and a POSIX shell.
- These focused checks cover **482 passing cases**.
- Full recursive typecheck, final server typecheck and full workspace
build pass on macOS arm64 with Node 24.22.2 and PostgreSQL 18.1.
- Greptile is **5/5** on head
`32f915a475275680bcf2a2aa43ae0c72f4e4b1db`. Both original review threads
are resolved.
- All current-head CI gates pass. The check rollup has 54 successful
checks, three skipped checks and one neutral check.
- The separate Paperclip Review check remains queued. Its public
response says work is paused and the request is saved. The review task
is waiting for saved-file recovery, so no Paperclip assessment of this
head is claimed. The branch-required gate has passed.
- A read-only deployed trace showed an archived sandbox start fail at
the short probe's 15-second deadline. Actual provider reattachment with
this revision has not been exercised. Live recovery remains unverified
until rollout.

## Risks

Recovery now makes one bounded lifecycle call before short preparation
commands. A missing resume capability or a missing, changed or expired
allocation receipt holds recovery. The existing runtime and provider
retain allocation and sentinel verification. The path does not acquire,
release, replace or reseed the lease. Warm providers can return their
existing allocation. E2B retains the existing sandbox if resume setup
fails; explicit lifecycle cleanup still owns its removal. No background
polling, credential-policy change, migration, workflow change or
lockfile change is added.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, source inspection, tool use
and code execution. The exact backend snapshot and context window were
not exposed.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (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 09:04:13 -06:00
DottaandPaperclip 84e9c8f364 fix(retry): wait for admitted source cleanup before dispatch (#18898)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Connected-task Retry keeps the original request and its current
authority.
> - One durable Retry can already own a queued successor.
> - The old native environment can still have pending cleanup before
that successor starts.
> - The task execution lock points to the successor, so the old cleanup
can escape the queue claim check.
> - Dispatch then refuses the source state and fails the new run before
provider work.
> - This pull request keeps that same run queued and wakes dispatch when
cleanup changes.

## Linked Issues or Issue Description

Refs #18845, #18853, #18860, #18877, #18882 and #18932. These repairs
cover preservation binding, interrupted preparation, old native
ownership, manual native holds or other setup guards. This change covers
an already admitted connected-task Retry whose native predecessor still
owes cleanup. It uses the shared cleanup predicate so the manual-hold
repair remains with its existing owner. Search found no duplicate
queued-source cleanup repair.

**What happened?**

A board Retry saved its exact chat request and admitted one queued
successor. The source lease was still pending cleanup when that
successor reached dispatch. A retained stop receipt did not settle the
full cleanup contract. The queue checked the task's current execution
owner, which was already the successor. The native authorization guard
then rejected the predecessor state and failed the new run before
provider dispatch.

**Expected behavior**

Keep the same canonical Retry and queued run while the exact source
cleanup is pending. After authoritative settlement, submit that run
through normal dispatch once. Do not infer safe execution from a
stop-and-retain receipt alone.

**Steps to reproduce**

1. Retry an authorized connected request from a failed native run.
2. Admit one successor while capacity keeps it queued.
3. Leave or restore the source lease to pending cleanup with a matching
retained stop receipt.
4. Release capacity and resume the ordinary queue.
5. The old claim path starts the successor and the native source guard
refuses it.

**Paperclip version or commit**

Reproduced against master at `f77499283270d2e966aabd58493f9d35c6bbdf5f`.

**Deployment mode**

The regression uses the real HTTP route, chat authorization, queue
claims, disposable PostgreSQL and the production work coordinator on
macOS arm64. A controlled adapter supplies the final result. No hosted
provider replay or deployment is claimed.

## What Changed

- Validate the exact canonical Retry, chat action, company, task, agent,
source and successor before testing source cleanup.
- Keep the successor queued under the existing issue claim lock while
the shared cleanup predicate remains pending.
- Wake only the scoped queued-dispatch worker after cleanup or exact
workspace-owner release commits. Add no timer or background scan.
- Verify pending cleanup at intake, later cleanup invalidation, one
final execution, idempotency, Stop, changed input, unknown stop
receipts, a different live controller and signed sandbox absence
followed by owner release.
- Document the queued-wait contract. No schema, endpoint, credential,
model or provider policy changes are required.
- Align five native preparation acceptance fixtures with the existing
workspace publication contract. Use the real reservation and publication
owner. Retain a raw-root refusal case with no provider launch and
preserved files.

## Verification

- Latest master sync at `e26cad76b8f20ba823d702ed60e7b7294f8f5a7c`
includes master `396b7fc62a0a4b1d8aadbda8221c05879d54177c`. The shared
test helper preserves #18944's canonical retained-directory default, the
real publisher for the five fresh-workspace cases, and the explicit
raw-root refusal. Independent remerge review found no dropped upstream
behavior, guard or assertion.
- The latest head passes the focused six cases, all 580 complete
workspace/continuation tests, all 38 selected HTTP Retry cases, fresh
full recursive Node 24.22.2 types and full build. No affected tests
skipped. An initial cached test-layer attempt read old budget
declarations; regenerating them resolved all seven errors without
test/mock or production-export changes.
- All current-head CI is green at
`e26cad76b8f20ba823d702ed60e7b7294f8f5a7c`, including required [ci /
verify](https://github.com/paperclipai/paperclip/actions/runs/41846304017/job/126636751314)
and [ci /
e2e](https://github.com/paperclipai/paperclip/actions/runs/41846304017/job/126636638086).
Greptile and Paperclip Review both rate this exact head 5/5, with no
findings or open review threads.
- Master sync at `3e4f266c2da172a84afd0b9cb4a91ea3b95f3c0d` merges
master `b891e69bd23d6dab8ce536569e15bc4a7a31c35c`. The only conflicts
were adjacent queue imports and two HTTP Retry test matrices. Both
imports and both complete matrices remain unchanged. Independent review
found no relaxed guard or assertion.
- Node 24.22.2 and pnpm 9.15.4: all 38 selected real HTTP Retry cases,
194 queue/cleanup/finalizer cases, module boundaries, recursive types
and build pass at `3e4f266c2da172a84afd0b9cb4a91ea3b95f3c0d`.
- That sync head's [server
check](https://github.com/paperclipai/paperclip/actions/runs/41838741852/job/126604742761)
failed five native acceptance cases in CI and locally with
`remote_workspace_published_path_invalid`. The test fixture supplied a
raw directory instead of a fresh publisher-generated workspace. The
guard from #18932 remains unchanged. The same five cases passed earlier
Linux CI before that guard was present.
- The test-only alignment at `dcdee1bb6c2e2e6dd494382499d17c23a3ce71a8`
obtains the saved authority, path and run reference from real
`reserveRemoteWorkspace` and `prepareRemoteWorkspace`. All five original
cases and a raw-root no-launch/no-result/files-preserved control pass.
Their native result, file, catalog and single-execution assertions
remain intact. An initial setup attempt was refused for a noncanonical
allocation root; the fixture now resolves that owned root before
publication.
- At previous fixture head `dcdee1bb6c2e2e6dd494382499d17c23a3ce71a8`,
the generated server test-layer typecheck passed. All 580 tests in the
complete workspace/continuation family pass, with no skips. That
test-only commit left production files byte-identical to the sync head.
- Previous published-head CI was green at
`dcdee1bb6c2e2e6dd494382499d17c23a3ce71a8`, including required `ci /
verify` and `ci / e2e`. The [Linux Node 24.22.2 server
shard](https://github.com/paperclipai/paperclip/actions/runs/41842665765/job/126618334638)
executed all five original acceptance cases and the new raw-root refusal
case successfully. Its whole workspace recovery file passed all 245
tests.
- Greptile reviewed previous published head
`dcdee1bb6c2e2e6dd494382499d17c23a3ce71a8` at 5/5, with no findings or
open review threads. No unchanged-job rerun was used for the reproduced
sync-head failure.
- Causal red: the real HTTP regression started and failed the successor
at the native source-state guard while cleanup remained pending. A
second red proved that signed sandbox absence followed by copyback-owner
release lacked a queued-dispatch wake.
- Green: seven HTTP/PostgreSQL cleanup cases, the exact confirmed-owner
release notification control and seven existing retry lineage controls
pass. The real coordinator resumes from committed cleanup or
workspace-owner release, without a manual queue restart. One controlled
adapter execution produces the result after settlement.
- Earlier branch verification passed full repository `pnpm -r
typecheck`, `pnpm build`, module boundaries and `git diff --check`.
- The full stale-queue file control passed all 64 cases with unchanged
observer/hook limits. The initial broader affected run had 192 passes
and one non-assignee native-owner case miss its existing five-second
adapter observer while already running with no error. The isolated case
passed; the unchanged base passed all 64 cases but hit its existing
30-second teardown timeout. Those original results are retained. The new
queue hint has no dispatch consumer in that fixture.
- Earlier-head CI was green: 54 success, three skipped and two neutral
checks at `6adb9f1ba682282c512d5e751ba2aa663f614ca2`. One chat job was
cancelled by an external runner shutdown; its single unchanged-job rerun
passed. No source was changed to obtain that result.
- Greptile and Paperclip Review both rated earlier head
`6adb9f1ba682282c512d5e751ba2aa663f614ca2` 5/5 with no findings or
unresolved threads. These results do not qualify the master-sync head.
- At initial verification, a complete local `pnpm test:run` was not
repeated while the related setup repair's full local run was active. The
full earlier-head CI suites passed. A full local suite has not been
repeated for the mechanical master sync. Local verification uses the
affected HTTP, producer, lineage and queue suites. No full local-suite
pass is claimed.
- The fresh clone's missing plugin SDK links were repaired with the
repository's own postinstall helper before verification. The embedded
PostgreSQL package's own symlink helper was also required.
- No staging task was submitted again with this repair, and no
deployment is claimed.

## Risks

- A pending source cleanup can keep the queued run waiting. Its cleanup
owner still owns reconciliation and its existing limits.
- Stop, changed source, unknown termination and live ownership retain
their existing authority checks. A cleanup notification grants no
execution authority.
- A new cleanup change after claim still faces the existing final
dispatch guard.
- Cleanup notifications can change dispatch timing in a running
controller. The real coordinator cases verify commit-bound wakeups; the
broader current queue-file control passed all 64 cases. The earlier
loaded-run and baseline teardown failures are retained in verification.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, code editing, tool use,
PostgreSQL and browser verification. This session does not expose a more
specific deployment model 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-11-04 08:33:39 -06:00
DottaandPaperclip 5d226ca08a perf(chat): reuse bound-run authorization query templates (#18966)
This PR depends on #18969. Merge that fixture-only prerequisite first,
then retarget this PR to master. No merge or deployment is requested in
this work. Its base is `codex/fix-recovery-published-workspace-fixture`;
the six-line recovery fixture repair is excluded from this diff.

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - External chat authorization binds each run to current messages and
conversation policy.
> - Review wait and attention-retention paths repeat the same four-table
read.
> - Each call rebuilt the Drizzle query even when its SQL shape stayed
the same.
> - This pull request reuses the compiled template for the exact
database handle and lock mode.
> - Fresh parameters and current database reads preserve authorization
while reducing Node CPU work.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The `authorizeChatConversationForBoundRun` links read used by external
chat waits and attachment reuse.

**Current behavior**

At baseline `e54f9fbf828e06e385e0c62dc381ccb46190aa30`, each call
rebuilt and serialized a four-table query. The observed staging shape
accounted for 16.2% of sampled SQL serialization CPU across 32 samples.
This is a share of query compilation CPU, not total server CPU.

**Proposed behavior**

Compile each read, blocking-lock or NOWAIT variant once per exact live
database or transaction object. Bind new company, issue, agent, provider
and comment IDs on every execution. Continue all current database reads
and authorization checks.

**Reason and benefit**

Repeated authorized service calls used 26.91% less Node process CPU in
the matched local fixture. All seven CPU pairs improved. This reduces
repeated compilation on the shared service path.

Related: #18896 retains task Retry intent through identity contention.
It changes a different workflow and does not reuse this query template.
A public issue and PR search found no duplicate template change.
ROADMAP.md has no conflicting planned feature.

## What Changed

- Keep compiled queries in a `WeakMap` keyed by the exact supplied
database or transaction object. Create only the requested lock variant.
Never cache a result or authority decision.
- Bind the comment list as `ANY(uuid[])`. This supports changing list
sizes with only three templates per live handle. Keep all joins,
selected fields, policy checks and lock clauses.
- Add real PostgreSQL regression tests, a reusable fixture, an actual
service benchmark and a dated performance note.

## Verification

- Passed 17 real PostgreSQL tests: `node node_modules/vitest/vitest.mjs
run --project @paperclipai/server
server/src/__tests__/prepared-chat-authorization.integration.test.ts
server/src/services/native-runtime/chat-attachment-reuse.test.ts`.
- After rebasing, seven selected existing external-chat-wait integration
cases passed (266 deselected). They cover small current review waits,
answered-chat identity advisory locks, connection-disable serialization
and contended endpoint proof retry.
- New tests cover changed parameters and list sizes, duplicate/empty
inputs, invalid UUID errors, company/agent/provider binding, current
setup generation and credential fingerprints, root/transaction/savepoint
isolation, rollback, read mode during a held row lock, blocking timeout
and NOWAIT contention.
- Actual service benchmark: `node --import
./server/node_modules/tsx/dist/loader.mjs
server/scripts/benchmark-bound-chat-authorization.ts
--baseline-ref=e54f9fbf828e06e385e0c62dc381ccb46190aa30`. Node 24.22.2,
macOS arm64, real PostgreSQL, 500 warmup calls per implementation, seven
alternating pairs of 500 calls. Every result matched.
- Pinned measured treatment `8386eb78d118faaf6c2fd7f7471b38c0f4b2ef54`.
Its source is unchanged after rebasing onto master. Median Node CPU per
500 calls: 379.033 ms to 277.048 ms, down 26.91%. Every CPU pair
improved by 13.54–33.52%. Median wall time: 742.109 ms to 648.005 ms.
One wall pair increased by 0.51%.
- A separate diagnostic after timing found five SQL compilations per
baseline call and four per warm treatment call. All five database reads
still run.
- The final four-test PostgreSQL authorization suite passed again after
rebasing and strengthening the lock proof. It holds only a message-link
row, so principal advisory contention cannot mask a missing joined-query
lock.
- Independent source review found no correctness issue in exact-handle
ownership, fresh binding or the three lock variants.
- Preserve setup failures: the first test attempt lacked plugin SDK
build output. Shared and SDK builds fixed it. Later attempts could not
start embedded PostgreSQL because its local dependency directory lacked
bundled native-library aliases. `DYLD_LIBRARY_PATH` did not fix it.
Restoring package-local aliases fixed the environment; the final 17
tests then passed. No dependency or lockfile change is included.
- The first full typecheck found new test-only errors: PostgreSQL
transaction handles lack the root database `$client` type, and runtime
setup test data includes fields outside the saved setup interface.
Commit `40a4645750` uses the existing explicit transaction test cast and
SQL JSON fixtures. No production contract changed. The full check also
found local plugin SDK link prerequisites, which the coordinator is
repairing separately.
- Preserve the first Linux CI failure: [server shard
9](https://github.com/paperclipai/paperclip/actions/runs/41837775564/job/126602280304)
failed the new setup-fence fixture's allowed case. The fixture supplied
a client `processedAt` while PostgreSQL supplied default `receivedAt`;
this could violate the required processing order. The test now uses
explicit ordered timestamps and no wall-clock window. The four-test
PostgreSQL suite passed again in 19.93 seconds. Production guards were
not weakened.
- [Server shard
2](https://github.com/paperclipai/paperclip/actions/runs/41837775564/job/126602280291)
also failed five workspace-recovery cases with
`remote_workspace_published_path_invalid`. Those failures reproduced on
both pure master and the exact CI merge with the chat optimization
removed. #18969 fixes the canonical path in that separate fixture, with
five failing cases before and five passing after. This PR is stacked on
that independent repair for final CI.
- The additional Paperclip review reported a node-postgres
statement-name collision and requested explicit pool cleanup. This
repository uses postgres-js; locked Drizzle 0.45.2 accepts the Drizzle
label but does not use or forward it. [The review
response](https://github.com/paperclipai/paperclip/pull/18966#issuecomment-6632666599)
links primary driver source and the database factory. Commit
`c5d0d84f36` adds explicit pool teardown in `try/finally` and a real
same-transaction `read → blocking → nonblocking → read` case. All five
authorization tests passed in 6.47 seconds. The original separate
row-lock test remains. Production SQL and code did not change.
- Final combined local validation at `4ab58ff70b` passed full `pnpm -r
typecheck` (39.864 seconds) and 35 real PostgreSQL cases across the
chat, GitHub and shared prepared-query suites (11.378 seconds). Full
`pnpm build` passed earlier with byte-identical production source; later
changes affect tests only. The production file SHA matches the benchmark
and this PR. The original full `pnpm test:run` was interrupted after all
six chat shards passed and general tests had partly run, so it is not a
full local pass. Final current-head CI provides the broader coverage. A
sandbox retry failed with EPERM before assertions; the identical
escalated commands then passed. Final head passed CI in [workflow
41843806060](https://github.com/paperclipai/paperclip/actions/runs/41843806060),
including all build, typecheck, general, serialized and e2e test gates.
The previous head `40a464575009dfc3cce7afa1eb4fffa5540dcaf6` passed
Greptile 5/5, security review, typecheck, build and canary validation.
The prior stacked head `74cb36370162750f8e135ed091603d3629d23b0d` passed
full CI, Greptile 5/5, policy and security checks. Final head
`c5d0d84f369d897a66d9b6441bab10cb79f593b9` passed a fresh Greptile 5/5
review with no actionable findings, security and PR policy checks. Its
complete [current-head CI workflow
passed](https://github.com/paperclipai/paperclip/actions/runs/41843806060).
All 54 required CI, Greptile, policy and security checks passed. The
optional Paperclip Review check is queued for this head; no current-head
score is claimed. Production source SHA-256 remains
`72ce9d2317e5c97b36e299e24e30fc1aa63020d8c266e495fc45dbfdd5331e93`,
identical to the measured code.

## Risks

- `ANY(uuid[])` relies on postgres-js array binding and UUID casting.
Real PostgreSQL tests cover single and multiple valid values and invalid
UUID errors.
- A new transaction or savepoint pays its first compilation cost. The
weak cache cannot reuse a root session for it. This benchmark covers
repeated calls on one root handle.
- The team paused other work during the benchmark. Unrelated Rust
compilation was active elsewhere on the host. Process CPU excludes those
processes, but host contention can affect caches, scheduling and wall
time. This source-mode local result does not prove production request
latency or whole-server CPU savings.
- No migration or API contract changes. No deployment or merge is
included. Monitor authorization failures and lock-contention behavior
after an authorized rollout; revert the narrow service change if those
regress.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with reasoning, repository editing,
tool use and test execution. The host did not expose a context-window
size or deployment snapshot ID.

## 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 08:32:31 -06:00
DottaandPaperclip 2cbfc35a07 perf(github): reuse the inherited review wait query template (#18965)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - External review waits check current GitHub review and publication
bindings.
> - The GitHub service rebuilds the same seven-table Drizzle query on
each check.
> - A sampled production window attributed 6.3% of SQL serialization CPU
to this query shape.
> - This pull request prepares that query once per exact database or
transaction object.
> - Each call still reads current provenance, review, connection and
publication state.
> - The local service benchmark reduces median client CPU per read by
24.35%.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Repeated `readInheritedGitHubReviewWait` calls from external review wait
reads and attention retention.

**Subsystem affected**

Server GitHub chat review waits.

**Current behavior**

Every call constructs and serializes the same joined SELECT. The
database driver already supports prepared statements. That setting does
not reuse the JavaScript query template.

**Proposed behavior**

Compile the existing selector once for each supplied database,
transaction or savepoint object. Bind company, issue, agent and comment
IDs on each execution. Keep every current database read and check.

**Reason and benefit**

Remove repeated query construction and serialization CPU. Do not cache
rows or authorization decisions. Exact open-PR searches found no
duplicate query-template change.

## What Changed

- Add an exact-handle `WeakMap` for the GitHub review selector and
execute it with fresh placeholders.
- Preserve the SQL projection, joins, JSON types, latest-review order
and all current authority checks.
- Extend real PostgreSQL tests for new bindings, warm-cache revocation,
latest heads and transaction/savepoint rollback.
- Capture prepared executions in the existing test helper.
- Add an actual-service benchmark and a report with every timing sample
and setup failure.

## Verification

- `github-review-wait.test.ts`: 16 tests passed on real PostgreSQL.
- `external-chat-wait.integration.test.ts -t 'reads small current
external review waits without saved prompts'`: four affected cases
passed; 269 unrelated cases skipped.
- Pinned baseline: `e54f9fbf828e06e385e0c62dc381ccb46190aa30`. Final
source SHA256:
`fe647e7fe2aed82609e8f1d66a0109e7a10ee28313ae2ef218ba3ab39b5cd105`.
- Exclusive-window benchmark: 500 warmups per version, nine alternating
pairs of 100 complete service reads on disposable PostgreSQL. Median
client CPU: 1.00336 → 0.75908 ms/read (24.35% less). Median wall time:
2.87089 → 2.38731 ms/read (16.84% less). CPU excludes the PostgreSQL
process.
- Every generated SQL statement, parameter, query count and complete
result matched. Warm joined-selector compilations per ten service reads:
10 → 0.
- See `doc/performance/2026-11-04-prepared-github-review-wait.md` for
reproduction, all samples and measurement limits.
- Setup record: loopback required execution outside the sandbox. The
fresh embedded-Postgres dependency lacked macOS dylib aliases; aliases
to existing identical bytes restored the fixture. The SDK import needed
a current workspace SDK/shared build. A broad external-chat suite run
was interrupted and replaced by the affected test filter. Those attempts
are not counted as passing suites.
- Greptile: 5/5 on current head
`d2e6ae8814c3a56c380bd5d60c47e6f98642f085`, with no actionable issues or
inline comments.
- Current-head CI is green at
`d2e6ae8814c3a56c380bd5d60c47e6f98642f085`: 52 successful checks, three
intentional skips and the successful Snyk status. This includes
typecheck, build, the full test matrix, end-to-end shards and the
aggregate gate.
- Combined local validation at `4ab58ff70b` includes both measured
production templates and final test fixes. Full recursive `pnpm -r
typecheck` passed in 39.864 seconds. Three real PostgreSQL files passed
35/35 tests (five chat cases, 16 GitHub review cases and 14 shared
prepared-query cases). The combined build passed earlier with the same
production template bytes.
- The initial combined typecheck failed on a separate chat test typing
error and build setup. The later passing run resolves that attempt. The
original full local `pnpm test:run` was deliberately interrupted after
six chat shards passed and partial general tests ran; it is not a
passing full local suite. Each PR has independent current-head CI for
broad coverage. The initial loopback sandbox restriction was resolved by
an escalated run of the same source.
- Local benchmark/test dependencies were locked at the baseline; the
rebase adds the upstream lockfile refresh and UI paste fix only.

## Risks

- A prepared query captures its session. The exact supplied handle is
the cache key so transactions and savepoints do not borrow the root
session.
- Placeholder mistakes could cross bindings. Tests alternate independent
company/issue/agent/comment fixtures on the same handle.
- The benchmark has one synthetic eligible fixture with warm sequential
reads. It does not predict production endpoint latency or whole-server
CPU savings.
- No schema, index, database driver setting or public API changes.

## Model Used

OpenAI `gpt-6.1-sol` through Codex. The context-window size is not
exposed by the session. Used reasoning, tool calls, source editing and
code execution. Independent Codex review found no correctness issues.

## 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 08:29:59 -06:00
DottaandPaperclip 0eaabe5c0b refactor(accounting): remove company serialization from paid settlement (#18917)
Settle paid operations through operation-scoped transactions. Preserve exact ledger evidence and replay while moving aggregate budget decisions to durable deferred work.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-11-04 07:29:28 -06:00
DottaandPaperclip 669538534c fix(recovery): preserve failed remote reattach workspaces (#18936)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Native runs can edit files in a remote execution workspace.
> - A failed reattach can leave an observed coordinator and unpublished
files.
> - The failure handler recognizes the older workspace-sync descriptor
but misses the modern workspace reference.
> - The released task lock then prevents ordinary session recovery from
claiming that old run.
> - This pull request retains the files through the existing Stop,
cleanup and workspace recovery owners.
> - Exact ownership and Stop evidence permit file preservation while
unknown provider effects remain under manual reconciliation.

## Linked Issues or Issue Description

Related: #18877 protects native manual admission holds. #18879 handles
scheduled native retry lock reclaim. #18918 fixes remote workspace
row-lock order. Those changes do not record the missing modern
failure-retention obligation.

**What happened?**

A native reattach failure can leave a failed run, a dirty modern remote
workspace, an expired observed coordinator and a free task execution
lock. The run has no accepted result. The failure handler skips file
retention because it checks only the older descriptor. Recovery cannot
export while the old coordinator remains owned. A mismatched workspace
generation can also make orphan cleanup miss the existing file claim.

**Expected behavior**

Retain the exact original allocation and files. Stop the allocation
through the existing lifecycle owner before retiring the stale
coordinator. Keep checkpoints and unknown external effects available for
manual reconciliation. Unsafe or changed source authority must prevent
physical cleanup.

**Steps to reproduce**

1. Prepare a modern remote workspace for a native run.
2. Record a provider checkpoint and an active interruption incident.
3. Fail reattach preparation and release the task execution lock.
4. Let the coordinator lease expire.
5. Run the existing orphan, environment cleanup and workspace recovery
workers.
6. Verify the original files from a cold archive read and verify that no
new native session starts.

## What Changed

- Include modern prepared references in the existing heartbeat
failure-retention producer.
- Add a file-only historical intake for an exact failed observed run in
the normal expired reattach state.
- Require an untouched active ephemeral lease, a free blocked task,
matching workspace writer and generation, no accepted result and
complete single-owner allocation history.
- Persist the existing StopAndRetain intent before provider I/O. Use
nonblocking row locks and classify busy ownership only after rollback.
- Use the existing native environment settlement owner after exact Stop
and retention receipts arrive. Keep its single scoped discovery query.
- Preserve source and coordinator fingerprints across the handoff. Keep
original checkpoints, errors and manual unknown-effect incidents.
- Keep stale or malformed modern workspace claims held when preservation
intake refuses them.
- Add actual PostgreSQL, heartbeat, allocator, worker restart, archive
and safety controls. Document the narrow recovery contract.

## Verification

Current published head `b1f49691187a398415cf9b16b723e3ab473f0f91`: all
required CI checks pass (54 success, 3 skipped, 1 neutral); Greptile is
5/5 with no open threads. The separate Paperclip assessment is queued
and its current response says work is paused. That assessment is
unavailable; no additional review nudge or hosted retry was issued.

All local commands use Node 24.22.2 on macOS arm64.

- Exact published head: `b1f49691187a398415cf9b16b723e3ab473f0f91`.
Required CI passes (54 success, 3 skipped, 1 neutral); Greptile is 5/5
with zero open threads. The optional Paperclip assessment remains
queued/paused without a completed score.
- Final recursive `pnpm -r typecheck`: passed.
- Final `pnpm build`: passed.
- Final three native producer, containment and scheduling files: **67/67
passed**. These include the real coordinator and worker restart, the
production heartbeat recovery callback, exact Stop receipts, cold
archive restore, and zero native backend, new allocation or destructive
release calls.
- The plugin allocator cases use the actual selector with `reuseLease:
true`. Active and released ephemeral leases receive a fresh allocation.
- The assembled reattach producer checks that the task and coordinator
claim commit before physical resume. A historical pin refuses that
current owner. A later claim behind a committed pin remains held.
- The safety controls cover reusable allocations, manual and unknown
states, cleanup in flight, accepted but unbound results, late allocation
owners, wrong or malformed workspace authority, changed
generation/checkpoint, lost Stop acknowledgement and a real
cleanup-producer lock conflict.
- Untouched base `e7549ab6131555f655736e6b544892a3b1e323cb`: four
expected behavioral failures and one passing legacy-descriptor control.
The failures cover the modern producer, original provider history,
historical retention and wrong-epoch cleanup. Fixed production files
were restored byte-for-byte after this comparison.
- The first affected six-file run completed **562 passed / 1 failed**.
The failure was an existing HTTP case with `socket hang up`. That exact
case passes separately on both untouched base and this branch. The
original connection failure remains unexplained. At `0e92de8c`, the
complete workspace family passes **188/188** with unchanged assertions.
The environment cleanup owner family also passes all 23 cases. The later
guard changes are qualified by the complete 67-case native owner group.
A full repository test run was not repeated locally; the requested
full-ci workflow runs the complete repository suites.
- Published `0e92de8c` fails both new explicit-null Stop/export marker
controls because it creates a new retention pin. The follow-up uses the
shared presence-aware Stop guard. Both controls now preserve the marker
and record no new retention pin, Stop or destructive release.
- Paperclip's review found that malformed workspace authority could
throw and abort an orphan page. Published `5f6cd4f5` fails all three new
controls: intake, the actual page and after-Stop settlement. The same
strict schema now uses a safe parse, and the existing OBSERVED hold runs
before the prepared-source lookup. The malformed source stays held while
the next valid source receives its retention pin.
- Two existing scheduling tests caught the draft's second global
discovery query. The final single-query repair passes all 36 unchanged
scheduling tests.
- An earlier restarted archive assertion stopped after 20 seconds. Its
recorded Stop was valid, and the coordinator had settled. The existing
evidence recheck deadline was still 13 seconds ahead. The final test
observes the unchanged 24–36 second policy within its existing 60 second
case limit.

The external fake provider Stop and file transport are controlled test
boundaries. Database ownership, lifecycle workers, archive bytes,
encrypted local storage and cold restore are real. This does not claim a
live cloud sandbox, S3 or model run. No hosted task was changed for this
PR.

## Risks

- Historical intake is deliberately narrow. Reusable, already released,
mismatched or competing allocations remain held.
- File preservation does not settle unknown provider effects or
authorize a repeated provider turn. The task can still require manual
reconciliation.
- A busy or changed owner delays preservation. The original files and
claims remain retained.
- This does not repair the underlying sandbox readiness failure or
establish the cause of a separate archive-capture failure.
- Deployment and recovery of original hosted tasks remain unverified.

## Model Used

OpenAI Codex, GPT-6 family, with repository inspection, code execution
and GitHub tools. The runtime does not expose the exact serving model ID
or context window size. A separate Codex agent and the coordinator
performed read-only source review.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` /
`Closes: #` / `Refs: #` OR (b) described the issue in-PR following the
relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change 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:19:03 -06:00
DottaandPaperclip b6352578c5 fix(task-retry): retain chat Retry during identity contention (#18896)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task Retry must preserve a person's request and enforce the current
chat authority.
> - Chat retry preparation takes nonblocking source locks before it can
authorize the saved source.
> - Sustained lock contention could discard the Retry request before it
was saved.
> - This pull request saves one unadmitted request in the existing chat
retry queue.
> - The worker checks the source and current access before normal
admission. The task shows a scheduled retry while it waits.

## Linked Issues or Issue Description

**What happened?**

Task Retry returned HTTP 500 when another transaction held the saved
chat principal's identity lock or the endpoint authorization row. The
source preparation error rolled back the canonical Retry intent. The
same gap existed when Retry had to restore a certified input batch that
had never reached a provider. With the bounded synchronous retries in
#18895, sustained contention returns 409 without a saved intent.

**Expected behavior**

HTTP 202 retains one Retry request. Acceptance does not grant source
authority or mean that a provider started. The existing worker prepares
the request if the source locks become available before its saved
deadline. It must reject changed input, revoked access, Stop, or
superseding work before input adoption, publication, or provider work.

**Steps to reproduce**

1. Prepare a failed task with its original admitted GitHub chat source.
2. Hold that principal's `chat-identity` advisory lock or the endpoint
row on a second PostgreSQL connection.
3. Send the normal task Retry HTTP request. Check the response and
persisted intent.
4. Release the lock. Process the existing chat retry queue and check
that one authorized successor is queued.

**Paperclip version or commit**

Reproduced on master `8771606fd87f69ed4057779e3dce3a9bca8088e6`.

**Deployment mode**

Source checkout, real Express Retry route and embedded PostgreSQL.
External chat transport is a test fixture. The live hosted fix has not
been deployed or verified.

Related: #18895 handles brief admission contention with bounded
synchronous attempts and returns 409 if it remains busy. This change
retains a durable request through sustained identity contention. It uses
the existing background retry owner and adds real HTTP, restored-input,
Stop, publication, restart, and task-presentation coverage.

## What Changed

- Preserve a strict pending-source action only when the identity
try-lock reports contention or the endpoint authorization query reports
lock-unavailable through an explicit Retry callback. Bind it to the
canonical task Retry, company, task, agent, endpoint, conversation, and
principal. Other database and authorization errors keep their existing
behavior.
- Revalidate the canonical intent, current board permission, saved
source, and latest execution before the existing worker can prepare that
action. Ordinary authorization rejects an unprepared action.
- Keep certified input restoration inside source preparation. Recheck
the saved deadline before the prepared payload write. Expiry, a nested
savepoint, or outer rollback cannot commit partial restoration.
- Give source authorization contention one saved 60-second deadline and
a separate counter. Keep its waiting phase until current source
authorization succeeds or the action terminates. It does not spend the
eight delivery attempts. Duplicate clicks, worker restart, and
background sweeps do not renew either limit. Reuse the existing work
notifications and outbox deadlines.
- Persist completion of the authorization wait under the current claim
before delivery. A restart revalidates current authority. Generic
delivery failure preserves the stored phase even when a commit
acknowledgment is lost; it does not grant a wake or renew the saved
deadline, counters, or claim.
- Project pending preparation and prepared but unsettled actions as the
existing scheduled-retry state with the actual outbox deadline. Keep the
exact task, intent, agent, and source binding. Active execution keeps
its normal working state. No UI or API enum change is needed.
- Add regressions and document the admission boundary.

## Verification

- RED: the original real HTTP route returned 500 under a held PostgreSQL
identity lock. The restored-input case also returned 500 while its 60
sibling source and budget cases passed. The current endpoint-row case
returned 409 without a saved intent under the bounded synchronous
fallback.
- RED: the worker failed a valid Retry during an actual nine-second
PostgreSQL lock. Removing only the latest-run guard makes the new
queued-run negative fail. Source preparation could also commit restored
input after its deadline, and an already prepared action could process
after a late lock release. The prepared but unsettled task view lost its
scheduled state.
- GREEN: all 91 selected real PostgreSQL integration cases passed.
Coverage includes the 62-case saved-input matrix; coalescing; restart;
one real heartbeat successor; source, actor, Stop, newer-work and
publication refusals; a real nine-second lock; fixed-deadline
exhaustion; the separate eight delivery attempts; certified restoration
rollback on expiry; prepared-action early and late release; a real
conversation-row wait that crosses the deadline during current
authorization; endpoint-row contention; unknown source; an
already-revoked actor; unchanged unrelated ingress errors; durable-work
idle settlement; actual outbox presentation deadlines; and recovery
after authorization commits, with Stop and a lost commit acknowledgment.
- GREEN: all 75 cases in `task-retry.test.ts`,
`task-interruption-presentation.test.ts`,
`chat-failed-run-retry-boundary.test.ts`, and `chat-action-work.test.ts`
passed without a name filter.
- RED → GREEN: stopping after source authorization committed left a
stale waiting flag, so a fresh worker failed a previously authorized
Retry after the deadline. The fix saves that phase in the same
transaction. A real PostgreSQL commit followed by an injected lost
acknowledgment also reproduced generic settlement restoring the stale
flag. The corrected settlement preserves the stored phase. No wake is
sent on the ambiguous response. Recovery queues once after current
checks; Stop still prevents a successor. The deadline, contention count,
original input, failed run, and publications stay unchanged.
- Module boundary checks pass. Full workspace typecheck and the full
build passed at `d8a4a1a739aa6825714b04691d4166a00991808b`. At this
exact head, both required checks (`ci / verify` and `ci / e2e`) pass.
The inventory has 53 successful, three skipped, and one neutral check,
plus the automatic Paperclip Review in progress. The legacy Snyk status
passes. Greptile is 5/5 on this head, with no new comments or unresolved
threads. A current Paperclip assessment has not been published yet.
- The full local `pnpm test:run` did not pass: its second chat shard hit
a 15-second timeout in the HTTP/restart test after 348 and 293 passing
cases. One bounded diagnostic of the same 642-case shard pair passed,
with that case completing in 606 ms. The matching Linux CI pair passed
too. The original timeout remains unexplained and nonreproduced. No
timeout changed. The earlier stopped run was incomplete, and both
conflicting documentation additions are retained.
- CI chat 3/3 exposed a separate fixture assumption: restoration can
commit before a durable wake receipt exists. One known pre-receipt
delivery failure reproduces the same missing-run error. The corrected
fixture drives the existing worker until the exact canonical intent and
wake agree. It proves one real wake, one successor, exact source/actor
binding, and one restoration. The exact failed shard pair now passes
locally: 635 cases. Workspace typecheck also passes at test-only head
`94d691c9df61b05019b96900d5800a8f1c484e9a`. Production source is
unchanged from the previous verified build. That head completed all
registered checks: 53 successful, two neutral, three skipped, plus the
successful legacy status. Greptile scored 5/5. The subsequent Paperclip
4/5 persistence finding is addressed by the source repair above; its new
exact-head review is pending.
- Compatibility: 111 selected cases passed with #18895 head
`9b5d877161ffcbf16c0fb6920e8155a3620c85b0` in a disposable worktree. The
branch is now synchronized with master `4a83ba4050`, which includes that
change.
- Preserve the old CI failure: at head
`37954951fb66f3b50d0f2a3bec78d6993c479e87`, server shard 6/12 initially
failed the empty organization-audience replacement versus member-removal
assertion in `tool-access-service.test.ts`. The exact race family passed
on unchanged base and head; all 501 file tests and two 30-case controls
passed on base. The one Linux replay passed 119 files and 2,833 cases,
with three skipped. No guard or assertion changed. The initial failure
remains unexplained and nonreproduced; this PR does not claim to fix it.
- The prior external Superagent scan could not start because its
Security Graph returned HTTP 503. Its neutral check was not a completed
security assessment.
- These tests exercise the real Express route, PostgreSQL transactions,
and heartbeat queue. External chat transport is a fixture. They do not
run a live model or prove recovery of an original hosted task.

## Risks

- A still-pending source authorization wait fails after its fixed
60-second contention deadline. A completed wait is saved, but new
contention keeps the original deadline. A healthy lock can exceed that
limit. Other delivery errors retain the eight-attempt budget. Current
authority checks remain required before admission. Inspection does not
reset either limit.
- The new pending phase must never be treated as an authorized chat
source. Normal authorization rejects it until the worker prepares and
binds the exact source.
- The task projection reads the existing linked outbox deadline. It adds
a scoped join and does not create a timer or change a business deadline.
- No schema migration, provider identity change, or new delivery queue
is included.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository editing, tool
use, and test execution. The host did not expose an exact deployment
snapshot 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] Required Paperclip CI gates are green (`ci / verify` and `ci /
e2e`; automatic review progress is recorded above)
- [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:48 -06:00
DottaandPaperclip c5251b4c0e fix(chat): retry fully recorded setup failures before dispatch (#18882)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Connected tasks retain their exact external request across failures.
> - Task Retry must preserve that request and prove that old execution
cannot continue.
> - A workspace setup failure can settle before any provider dispatch.
> - The chat retry guard accepted one specific validation failure but
rejected other fully recorded setup failures.
> - This pull request reuses the same strict server settlement proof for
those failures.
> - The benefit is one safe continuation through the existing queue,
with all source and execution gates intact.

## Linked Issues or Issue Description

Refs #18803, #18845, #18852, #18853 and #18860. These changes preserve
workspace writers, bind completed preservation, or repair other
preparation and native continuation paths. This change repairs the
separate connected-chat guard for fully recorded legacy setup failures.
Search also found #18876, which repairs issue scope during deferred wake
promotion and does not repair this guard.

**What happened?**

A connected task failed during workspace reservation before provider
dispatch. Its source remained authorized. Task Retry saved an intent and
chat action, but the chat worker rejected that request as
`failed_chat_retry_not_authorized`. No successor was created. The old
failure had a bootstrap marker, a dispatch-only Runner profile, the
matching server error and durable measured settlement events, and no
runtime, process, output, result or environment lease.

**Expected behavior**

A deliberate board Retry can continue the exact saved input after the
complete never-dispatched proof passes. An unfinished earlier writer,
changed source, accepted result or unknown execution state must still
prevent execution.

**Steps to reproduce**

1. Admit an authorized request through a restricted chat endpoint.
2. Settle setup before provider dispatch, for example while an earlier
workspace writer is unfinished.
3. Keep the exact server error and durable measurement events, a
dispatch-only Runner profile, and no execution resources.
4. Press task Retry after the independent writer and ordinary admission
gates permit work.
5. The original guard saves the Retry action but refuses to create a
successor.

**Paperclip version or commit**

Reproduced on master at `a00109aa35`. The branch is rebased on current
master.

**Deployment mode**

Hosted task diagnosis. Local proof uses real Express HTTP routes, chat
authorization, heartbeat queue admission and disposable embedded
PostgreSQL on macOS arm64, Node 26.4.0. Provider work is deliberately
prevented by occupied fixture capacity.

## What Changed

- Extend only canonical task Retry's existing strict pre-provider
preparation proof to generic server-settled setup failures.
- Keep exact result shape, dispatch profile, controller, runtime,
process, output, event and resource checks. No diagnostic message
allowlist is added.
- Exercise GitHub and Telegram HTTP Retry with the original sender and
saved input. Keep the failed run unchanged and create at most one queued
successor.
- Cover provider events, retained native state/results, modified source,
unknown result metadata, live controllers, process identity, source
leases and an independent unfinished writer.
- Document the guarded contract and correct the obsolete access-map
sentence. No new endpoint or agent permission is added.

## Verification

- Red before repair: both setup causes failed through the real
HTTP/chat/queue path because the durable Retry had no successor. An
initial retained-native negative fixture had an invalid foreign-key
setup; that fixture was corrected before the final run.
- Green: 37 focused integration cases pass, including 22 new
GitHub/Telegram cases and 15 existing pre-provider source and execution
guards.
- Current head `1bf69d20e02a049248ec378f087505563daeb9a0`: all 37
focused integration cases and full repository `pnpm -r typecheck` pass
after the master rebase. The full `pnpm build` also passes.
- Map structure, module boundaries and `git diff --check` pass.
- Full local `pnpm test:run` is running; all six chat shards passed and
the remaining server suites are progressing. This is not a full-suite
pass claim.
- Greptile and Paperclip Review both reviewed this exact head at 5/5
with no actionable findings or review threads.
- Initial CI passed all jobs except the unchanged Runner package suite.
Its `runtime-host.test.ts` approve-reads case failed before its intended
assertions because a managed credential home could not acquire its
loopback-port quorum. The exact case passes locally. A wider 121-case
check hit the same lease error in a different Pi case (120 passed); all
121 pass with one worker. The exact competing port owner is unknown. One
unchanged retry of only failed CI jobs passed, including the Runner
package suite and aggregate `ci / verify`. All current-head required CI
gates are green; the original failure remains in workflow history.
- The dependency setup initially missed the embedded PostgreSQL
package's dylib symlinks. Running its own postinstall helper fixed the
local setup before assertions. This was not a product test failure.
- This change has not been deployed. The original hosted task has not
been replayed with the fix.

## Risks

- Never-dispatched proof must remain strict. A bootstrap marker or empty
process fields alone cannot authorize continuation. The regression cases
retain contrary-state holds.
- The inherited session display hint remains history and supplies no
resume authority.
- Current connection, principal, source integrity, pause, budget,
approval, dependency and workspace-owner gates remain in their existing
owners.
- No schema, provider profile, trust policy, model or credential
permission changes are required.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, code editing, tool use and
PostgreSQL/browser verification. This session does not expose a more
specific deployment model 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-11-04 07:18:40 -06:00
DottaandPaperclip e54f9fbf82 fix(secrets): expose secure human entry in native discovery (#18927)
## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - Some tasks need a company credential or the current person's
credential.
> - Paperclip already has secure human entry forms for these values.
> - Native connection guidance did not distinguish storing a credential
from connecting an external service.
> - Native API search also omitted the existing secure entry route from
secret operation help.
> - This change makes that route discoverable while preserving human and
owner authority.

## Linked Issues or Issue Description

Refs #18780. Related #18921 corrects a separate observer false positive.
Neither PR owns this production guidance change.

**What happened?**

A request to store an existing company or personal credential could lead
to an unnecessary external provider question. Native API search did not
explain the existing human entry surface.

**Expected behavior**

An agent can find the existing secure form, direct the human to the
matching entry, and verify the result through its authorized interfaces.
The agent must not ask for values in chat or act as the human.

**Steps to reproduce**

Search the native API for a company credential or personal publishing
credential. The result should name the supported Secrets settings route
and its human authority boundary. Open a known personal definition link
and confirm that the existing Set my value action appears.

## What Changed

- Distinguish Paperclip credential storage from external connection
discovery in shared native guidance.
- Describe the existing Secrets settings route, secure entry actions and
current-person boundary in OpenAPI help. Qualify definition detail links
as admin-only; other members use My secrets and Set my value.
- Exercise discovery through the actual OpenCode MCP bridge and native
API catalog.
- Retain agent denials for personal definition and value routes.
Exercise the real Secrets UI definition link.

## Verification

- Focused local checks: four suites, 78 tests passed. These cover native
discovery, secret-route permissions, shared guidance and the Secrets UI.
- The new discovery test fails against unchanged master OpenAPI help. It
passes with this change.
- Token gates and diff checks passed.
- Full workspace typecheck passed. The first attempt found missing SDK
links after script-free installation; the existing root postinstall
helper restored them. An unprivileged retry also hit the local tsx IPC
sandbox restriction. The corrected owned setup passed without source
changes.
- Full local workspace build passed. The full local test run stopped in
chat shard 2: one Discord receiver-lease test failed during initial
configuration. That shard passed 263 tests; the first shard passed 310.
The test and owning lease/configuration sources are unchanged from
master. One focused isolation passed without code changes. Its cause
remains unknown; this is not a complete local test pass. No unrelated
Discord code was changed. [Normal hosted
CI](https://github.com/paperclipai/paperclip/actions/runs/41773266687)
passed on exact head `0abb8b722779368109b9639f68122d7df02a8ab1`.
- Fresh Greptile review reports 5/5 on the exact current head; all
review threads are resolved. Paperclip Review remains in progress. The
optional Superagent scan is neutral/inconclusive: Security Graph context
retrieval returned HTTP 503 and assessment did not start. It supplies no
security PASS credit.
- A retained representative native attempt with this guidance produced
the correct definition-scoped secure-entry link. Its observer then
rejected an explicit warning against chat entry. That failed result
remains unchanged. Human entry, saving the value and worker delivery
were not exercised. This PR claims no complete product-eval pass.

## Risks

This changes agent guidance and search ranking. It grants no new
permission and changes no secret resolution or owner checks. Broader
service-connection discovery still applies when a task needs a
connection. The secure human forms are unchanged.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with repository tools, code
execution and test analysis. The host does not expose the context-window
limit.

## 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 — the 78 owning controls
pass; the broad local failure remains disclosed 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 — normal CI passed; optional
review states are disclosed above
- [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:01:57 -06:00
DottaandPaperclip 5b50c6b32c fix(teams): return saved resource links after catalog installation (#18923)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Catalog installation creates agents, projects, and routines in one
company.
> - The install result includes saved IDs but no links to those
resources.
> - A chat agent can describe a partial installation without giving the
operator a way to inspect it.
> - This change returns canonical links for saved resources and
documents them for API discovery.
> - The operator can use the returned links to inspect the actual
installation.

## Linked Issues or Issue Description

**What happened?**

A catalog install can create the intended resources and report skill
warnings, while its response leaves callers to construct board URLs from
IDs. A native chat attempt described the resources and warned about an
incomplete skill, but omitted resource links in both its initial and
repeat replies.

**Expected behavior**

The installation response should provide canonical company-scoped links
for its saved agents, projects, and routines. It must preserve warnings
and omit links for rows without saved IDs.

**Steps to reproduce**

Install a catalog team through the API. Inspect its saved resource rows
and response. Before this change, the response has IDs and names but no
resource links.

**Paperclip version or commit**

Parent `e7549ab613` from master. Related #16495 changes comment
authorization metadata in the same OpenAPI file; it does not address
team installation responses. No duplicate team-link PR was found.

## What Changed

- Add typed `resourceLinks` entries with resource kind, saved ID, name,
and canonical company-scoped href.
- Read the company prefix with one bounded primary-key query before
import. Encode URL segments and use saved IDs, including IDs returned
for reused resources.
- Preserve the portability import result, skill preparations, warnings,
permissions, and collision behavior.
- Document the response and add service, native API wire, OpenAPI, and
real database regression coverage.

## Verification

- Parent service regression: 1 failed, 28 passed. The new failure was
the missing resource-link response.
- Owning service/native wire, route authorization, and OpenAPI suites:
72 passed.
- Real PostgreSQL catalog-install suite: 4 passed. Returned links match
persisted agent IDs and the company prefix.
- Shared and server TypeScript checks passed. Fresh shared/SDK builds
and the owning Runner TypeScript prerequisite passed.
- UI typecheck, Storybook catalog check, 30 catalog/map controls, and
token gates passed.
- Actual CI found an omitted required `resourceLinks` field in the
existing Storybook install fixture; that fixture is now synchronized.
The earlier response-status review correction is also retained: install
schema and wire control use actual HTTP 201. Final head
`0bca72cf1f7641fd842a04c5a75cae4bcc274f05` passed all 48 normal CI jobs,
including `ci / verify`: [run
41773413940](https://github.com/paperclipai/paperclip/actions/runs/41773413940).
Greptile is 5/5 with zero unresolved review threads. The optional
Paperclip review remains queued and its comment says work is paused. The
Superagent security scan is neutral; neither supplies completed
assessment credit.
- `git diff --check` passed.
- Setup failures were retained: incorrect test working directory,
sandbox listener denial, missing built SDK/Runner declarations, and
native library aliases not hydrated from the package directory.
Corrected setup used the owning build and shipped hydration script. No
assertions or deadlines were relaxed.
- Repository-wide tests/build were not run locally; the full normal CI
suite passed. A new native chat attempt has not run for this change. The
earlier chat attempt remains failed for missing links. Local response
coverage does not prove that a model will include the links in its next
reply.

## Risks

- This is an additive JSON response field. Manually constructed
TypeScript install results must include the new typed field.
- The query adds one company-prefix lookup per installation. It selects
one scalar by company primary key and runs before mutation.
- Supplying links reduces URL construction work but does not guarantee
model use. Native qualification is still required.

## Model Used

OpenAI `gpt-6.1-sol`, with reasoning, repository inspection, code
editing, and tool execution. The host did not expose the model
context-window limit.

## 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 required normal CI gates are green; optional
incomplete/neutral assessments are disclosed above
- [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:01:08 -06:00
DottaandPaperclip 84aa989bf9 Preserve native sessions through legacy workspace enrollment (#18943)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A native session retains its workspace and provider session across
turns.
> - An ephemeral web server must restore those files without its
predecessor's disk.
> - Legacy enrollment changes the workspace authority and can change
both workspace addresses.
> - The retained provider backup checks those addresses as part of its
identity.
> - This pull request preserves the exact addresses in a verified
migration receipt.
> - The benefit is that the next turn can use the original session after
enrollment.

## Linked Issues or Issue Description

Refs #18932, merged as `612d9931a6`. This pull request builds on that
all-Runner durability change. Related: #18918 fixes concurrent workspace
preservation locks. This change covers identity preservation during
offline enrollment.

## What Changed

- The native workspace migration owner records the selected session
bindings in the successful workspace migration operation.
- Runtime selection retains the original logical address only for the
exact agent, task and session.
- Remote workspace preparation restores the original provider address.
It refuses conflicting targets, forged receipts and occupied unowned
roots.
- The environment owner includes the migration operation in the reusable
lease fingerprint. Warm policy stays warm and receives a fresh
allocation through normal lease cleanup.
- Exact migration evidence permits the existing verified provider backup
to restore into that fresh warm allocation.
- Preserve verified provider allocation files through a normalized,
lease-bound digest receipt. Unknown files and altered markers remain
refused; interrupted publication is retryable.
- Retain legacy managed-instruction session fingerprints using the exact
imported content hash and recorded paths, preserving all other
configuration and credential checks.
- Migrate existing local skill archive/manifest objects through upload
claims, readback verification and receipt compare-and-swap; retain
original objects and IDs.
- Add explicit two-turn browser qualification for every released native
Daytona profile, with warm and per-turn variants. The production
maintenance entry runs offline. The old home is deleted only after a
verified export and successor readiness. The oracle checks provider
identity, native session, addresses, reference bytes, question and final
document.

## Verification

- Full repository typecheck passed at `b12ecd823e`. The production
session fix passed 198 session/credential tests. Full repository build
passed at `94bb917947`. The final Rust-only provider fix passed 57 ACPX
provider tests and the exact Linux release build.
- Workspace and transport checks passed: 117 transport/workspace tests,
six final real PostgreSQL migration boundary cases, 178 environment
tests, 539 Daytona tests (eight skips), and the native warm-backup
admission control.
- Local skill migration passed eight PostgreSQL/S3 tests (one existing
opt-in skip). Failed readback retains the source. Successful readback
still works after deletion of the old home.
- Latest Product E2E typecheck and 38 focused harness tests pass.
Earlier catalog/admission/report checks passed. Twelve unrelated
support-test failures reproduce on unchanged base `1a39dea9aa`; these
are not reported as passing.
- Live Codex per-turn at `2d5bb9a6d1` retained the native and provider
sessions, both workspace paths and exact reference bytes after deletion
of the old home. It completed the task and delivered its saved document
and original reference. Its original failure was an output-count grading
defect. The corrected grader passes all 21 checks against retained,
hashed evidence; the original result remains unchanged.
- Live OpenCode per-turn and Codex warm at harness `94bb917947` pass all
21 product checks. Their launcher cleanup failed because a read-only
diagnostic observer kept its disposable database open. The observer now
closes before ordinary database cleanup; a no-provider reproduction
confirms the fix. All owned sandboxes were independently verified
absent, child keys revoked and buckets removed. Original failed campaign
dispositions remain unchanged.
- Claude at `94bb917947` kept its sessions and reference bytes but
omitted the final clickable document link. That user-delivery failure
remains recorded. The fixture now explicitly asks for the link and
records migration evidence before checking navigation. The fresh Claude
attempt at `b12ecd823e` passed both strict migration checks: exact
provider/native session, both paths, original file bytes and
independently downloaded attachment. Its full browser workflow failed
again: it requested unnecessary confirmation, used the answer sentence
as the reference, and omitted the clickable document link. This is
retained as a failed user-workflow result, not a full pass.
- Earlier live failures exposed and reproduced the skill-object,
fresh-allocation marker and managed-instruction fingerprint bugs. The
fixes retain original identity checks. No provider session reset or
accounting bypass was used.
- The real Grok continuation at `b12ecd823e` failed before provider
execution because its immutable-instruction attach policy rejected
relocated registered file copies. A no-provider regression reproduced
the exact rejection. `b05ba3728a` selects the existing strict
authenticated asset-path policy for Grok; content digests, unknown
context fields, session/workspace identity and same-run mutations remain
enforced. The regression and all 57 ACPX provider controls pass. The
Grok browser migration now passes all 21 checks with the b05 Linux
Runner, including provider identity, exact reference bytes and final
user output; the launcher also reports successful cleanup.
- `24fabf1d81` applies the same policy to released ACPX Codex after a
second no-provider reproduction. All 58 ACPX provider controls and the
final Linux release build pass. Its live browser migration passes all 21
checks on the final binary, including exact original session/file
preservation and completed user output.
- The full native-session executor suite passes all 1,121 tests after
updating its existing lease database fixture for the migration-receipt
join. The prior CI failure was this unsupported test-double method; the
production guard still rejects ordinary unproven recovery. CI and
Greptile 5/5 passed at `ca1e0d5fe6`. The first CI attempt lost several
workers and had one rotated-recovery transport failure; its seven
descriptor cases passed locally, and the single unchanged retry passed.
The original failure remains recorded. Staging enrollment has not
started. Initial offline enrollment and subsequent rolling availability
are separate checks.

- The final review follow-up adds real runtime-selection PostgreSQL
cases for exact, different-session and absent receipts, plus
executor-level warm restoration that copies actual backup files and
verifies provider identity and the new lease stamp. Both complete
affected suites pass (1,172 tests), and full repository typecheck
passes. Removing either production migration connection independently
makes its new positive case fail; both source files were restored
byte-for-byte after these negative controls.

- Final commit `0971d373a3` passes all 63 checks (57 successes, five
expected skips, one neutral); Greptile is 5/5 and every review thread is
resolved.

## Risks

- An identity conflict blocks enrollment and keeps the original files.
It does not silently reset the session.
- A new migration generation retires a reusable lease through its
existing owner. The original sandbox must finish normal cleanup before
reuse proceeds.
- Initial enrollment has an offline step. The separate rolling suites
prove uninterrupted deploy behavior after enrollment.
- This adds no schema migration and changes no provider credentials or
accounting gate.

## Model Used

OpenAI Codex, GPT-6 family. The exact serving model build and context
window are not exposed in this session. The agent used reasoning,
terminal tools, code edits, PostgreSQL/filesystem tests and isolated
live browser qualification.

## 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 04:43:56 -06:00
DottaandPaperclip 612d9931a6 Support per-turn Runner sessions on ephemeral web servers (#18932)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The web server controls native Runner sessions in remote sandboxes.
> - An ephemeral web server must recover without its previous local
filesystem.
> - The existing admission rule accepts warm sessions. Managed Claude,
Grok, and OpenCode sessions resolve to per-turn execution.
> - Per-turn execution also needs durable provider-session bytes after
the sandbox is retired.
> - This pull request adds encrypted backup publication and removes the
warm-only admission rule.
> - Both lifecycle modes can use the existing native ownership and
checkpoint contracts during a web deployment.

## Linked Issues or Issue Description

Refs: #18518, #18529, #18826, #18835, #18841, #18864, #18887.

**What existing behavior does this improve?**

Native Runner execution on web servers with
`PAPERCLIP_EPHEMERAL_SERVER=true`.

**Current behavior**

The admission rule requires a warm Runner lifecycle. Managed provider
credentials can resolve an agent to per-turn execution, so enabling the
ephemeral deployment profile blocks those turns. A database controller
checkpoint does not preserve provider files after the old web home and
per-turn sandbox are removed.

**Proposed behavior**

Admit native execution in either resolved lifecycle. Keep provider and
credential choices unchanged. Publish encrypted provider state to shared
storage before sandbox cleanup. Restore it from a fresh web home for the
next turn. Use the existing controller lease and generation checks for
active takeover.

**Reason and benefit**

Operators can remove the web server volume without excluding per-turn
providers or forcing managed accounts into warm sessions. New native
Daytona profiles automatically enter both rolling-deployment
qualification lanes.

**Breaking changes**

The additive migration creates a backup publication ledger. Existing
volume-backed local-disk installations retain their local backup path.
S3 installations publish encrypted backups and require a stable secrets
master key. Existing-volume enrollment must import surviving session
data before detaching the volume.

## What Changed

- Admit native Runner execution independently of its warm or per-turn
lifecycle. Keep host-owned legacy execution excluded from ephemeral
deployments.
- Store private provider state as encrypted objects. Journal uploads
before I/O, read them back, and fence publication against the current
native owner. Keep the previous published backup when storage or
ownership checks fail.
- Restore exact provider bytes into a private cache. Require an exact
durable backup before sandbox cleanup. Extend the existing bounded
retention pass to retire stored objects without adding idle polling.
- Import selected legacy controller and provider state in the existing
offline maintenance process. Preserve queued work, task pointers,
pending recovery, and original files. Prefer canonical database
checkpoints.
- Preserve already-failed legacy controllers with missing original
artifact proof as encrypted archives. These archives do not become
execution authority. Successful retained sessions still require proof.
Report archival and executable imports separately.
- Verify unmanaged Grok API keys through the existing xAI account
validator. The live test exposed a readiness gap that kept remote Grok
agents paused after platform checks. Preserve failed credentials and the
separate runtime warning.
- Ingest Grok whole-prompt usage at the owning ACP boundary. Bind the
receipt to the exact outbound request/session; reject replay, conflicts,
and incomplete authority. Separate turn cost from cumulative session
cost in the sidecar schema and Rust normalization. Preserve managed
billing identity and identify direct xAI API credentials.
- Preserve published workspace directory names across cold allocations,
including destroyed-allocation cleanup. Keep unpublished epoch/lease
directories isolated and refuse to overwrite an occupied preparation.
This fixes the cold-session identity regression exposed by #18922.
- Increase bounded checkpoint-header inspection to 1 MiB. Retain the
aggregate inspection limit.
- Register all nine native Daytona profiles in per-turn and warm
rolling-deployment suites. Delete the entire departing server home,
exercise ordinary reads and writes, require active ownership transfer,
then check a later turn on another fresh server. Permit only exact
notification replay after reconnect.

Ownership stays with the existing modules: Runner validates its protocol
state; Core validates run scope and controller ownership; storage owns
object I/O; the maintenance process owns offline conversion; the E2E
wrapper owns process replacement and probes. No provider protocol is
added to the shared admission rule. Grok receipt parsing stays in its
provider module. The shared sidecar gains an explicit cost basis because
its existing price field means session-cumulative spend, while Grok
reports prompt totals. Existing consumers retain cumulative behavior
when the new field is absent.

## Verification

- Focused native admission, archive validation, publication fencing,
cleanup, readiness, legacy import, and E2E observer tests pass. Real
PostgreSQL cases verify that queued intent and historical recovery rows
stay unchanged.
- Full typecheck and build pass on the rebased branch. The review
follow-up passes server typecheck, 29 focused
PostgreSQL/archive/readiness tests, and module-boundary checks. Revision
fb8d39a7a1 reached 55 successful CI checks and Greptile 5/5 with no
unresolved threads. The Grok follow-up passes Runner/server typechecks,
238 ACP runtime/projection/usage tests, 16 sidecar-schema tests, the
Rust cost regression, and Core billing regression. The e9aae9cf revision
passed all 57 successful checks, with 5 skips and 2 neutral checks, and
exact-head Greptile 5/5. The latest 9cbec30a4f follow-up changes only
E2E catalog/admission controls; its 291 affected tests pass, all 57
check contexts succeeded (5 skips, 2 neutral), and exact-head Greptile
is 5/5 with no open review threads.
- Migration numbering and safety checks pass.
- A local real PostgreSQL/S3 roundtrip preserved 2,870,101,591 payload
bytes across 89,100 archive entries after deleting the source directory.
Restored SHA-256 matched; upload/readback took 56.5 seconds, restore
74.8 seconds, process peak RSS was 1.40 GB, and all fixture resources
were removed.
- Archives preserve contained relative links, reject link
chains/traversal, and enforce independent byte/entry/object limits.
Offline verification streams hashes. Orphaned deleted-run backups stay
on the original volume. Obsolete object retention preserves the selected
backup, live lease references, and pending uploads with owners.
- Isolated API-key Codex deployment: original active runner/session
transferred to the replacement and committed. All 567 reads, 57 writes,
and 57 readbacks succeeded. A second replacement completed a
same-session continuation with all 287 reads and 29 writes succeeding.
The strict cell failed two agent-authored shell commands; this result is
retained as failed.
- Isolated API-key Claude deployment: original active runner/session
transferred and committed. All 584 reads, 59 writes, and 59 readbacks
succeeded. A second replacement completed a same-provider-session
continuation, with 211 reads and 22 writes succeeding. The strict cell
failed an initial completion call that omitted a registered attachment
citation; the agent corrected it. This result is retained as failed.
- Isolated API-key OpenCode deployment: the same active runner/session
transferred and committed. All 481 reads, 49 writes, and 49 readbacks
succeeded. A second replacement retained the provider session and
completed useful work, with 156 reads and 16 writes succeeding. The
strict cell failed agent tool calls: a denied external-directory write,
malformed artifact hashes, and a second completion after the first
result was committed. There was one submitted/completed turn and one
committed result; reconnect notification replay was identical. This
result is retained as failed.
- Grok's real first turn now completes with settled usage and provider
cost ($0.41397); the next turn is admitted normally. That attempt then
exposed a shared workspace-path identity regression before deployment.
Three cold-restore cases reproduced it before the fix; 106
workspace/transport tests and four focused cold-restore cases pass
afterward, including destroyed allocation and occupied-directory
preservation. Server typecheck and build pass. At b5b0dbb03f both
controller switches passed the read/write probe without failure, and the
active runner retained its process while the controller generation
advanced. The third turn exposed an invalid reasoning-token subset
check; ad07e681ba fixes it with the actual receipt regression. Two later
live attempts failed with an upstream Grok rate limit before deployment.
A tiny preflight prompt succeeded before the second attempt, but the
full workload was still rate-limited. The final live gate remains
incomplete. No accounting or identity guard was bypassed. Earlier failed
attempts and their exact resource-cleanup receipts remain recorded.
- Earlier Codex/Claude/OpenCode receipts predate #18922; they establish
their tested revisions. Current-head live qualification remains pending;
#18943 separately preserves pre-enrollment logical/provider workspace
addresses and adds actual legacy migration proof. Its first Codex
migration attempt stopped safely at skill-version verification.
- Earlier attempts remain recorded: missing local provider-pack setup
blocked one Claude launch, and the observer rejected identical reconnect
replay in another. Setup and the observer were corrected before the
latest run.
- Named staging enrollment has not started. Its serving revision and
post-deployment journeys remain separate delivery gates.

- Broader E2E harness validation exposed deployment-catalog and
installed-lane coverage drift; the affected 291 controls now pass.
Twelve other failures reproduce on unchanged base 1a39dea9aa. They
concern cleanup call expectations, dated operating-overhead fixtures, a
Pi profile-version expectation and workflow-security expectations. No
baseline failure is reported as passing.

## Risks

- Provider archives can contain credentials. Objects use authenticated
encryption with scoped associated data and a wrapped data key. The
original stable master key remains required.
- Storage failure prevents publication and sandbox cleanup. Operators
must keep shared storage available and preserve the previous volume
during enrollment.
- Legacy state without verified identity cannot become executable
recovery authority. Archival preserves evidence and existing failures;
it does not recover already-lost provider files or clear blocked tasks.
- Local overlap probes establish the tested application behavior. They
do not establish Railway routing behavior or live managed-subscription
qualification.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, tool use, code execution,
and live integration tests. The exact runtime variant 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
- [ ] 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 02:12:51 -06:00
Devin FoleyandPaperclip 1a39dea9aa fix(heartbeat): keep a routine claim queued while its slot is held (#18933)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A routine creates one task per tick. Only one open task of a routine
can hold an execution lock (`issues_open_routine_execution_uq`).
> - Since #18879, a failed native run that waits for a scheduled resume
keeps its task's lock. Routine dispatch counts only queued, running or
scheduled runs as live, so it can queue a newer task of the same routine
meanwhile.
> - When that newer run is claimed, stamping its execution lock violates
the unique index. The raw database error is not a deferrable claim
rejection, so it escapes the queue loop. The queue code notes that this
can stop the agent's queue and startup recovery.
> - This pull request turns that exact violation into a 409 claim
rejection, so the run stays queued until the slot is free.
> - The benefit is that a busy routine slot delays one run instead of
stopping the queue or startup.

## Linked Issues or Issue Description

Refs #18879 (keeps a failed run's lock while its resume is pending),
#18916 (defers the same unique-index conflict in restart recovery's lock
reclaim) and #18931 (open; keeps one run's restart-recovery failure from
stopping startup recovery). This is the same index conflict on a
different path: the heartbeat queue claim, not restart recovery. It does
not change #18916 or #18931. Its doc paragraph is separate from the one
#18931 edits.

**What happened?**

While auditing the lock paths from #18879 for this index, I found that a
queued run of a newer routine task can be claimed while an older task of
the same routine still holds the slot through a failed run with a
pending resume. The claim's lock stamp then raises `duplicate key value
violates unique constraint "issues_open_routine_execution_uq"`.
`isDeferrableClaimRejection` accepts only `HttpError` 4xx, so the error
escapes `startNextQueuedRunForAgent`.

**Expected behavior**

The claim is rejected as a conflict and rolls back. The run stays queued
and is claimed after the older task releases the slot. Other errors
behave as before.

**Steps to reproduce**

1. Create two open tasks of the same routine (same origin and
fingerprint).
2. Give the older task an execution lock held by a failed run.
3. Queue a run for the newer task and claim it. On `master` the claim
throws the unique violation.

The new queue test runs these steps against PostgreSQL.

**Paperclip version or commit**

`master` at `0dedbf23c35`.

**Deployment mode**

Any server with routines whose tasks run on native runners.

## What Changed

- `bindClaimedIssueExecution` in
`server/src/services/heartbeat/queue.ts`: if stamping the execution lock
violates `issues_open_routine_execution_uq`, it throws
`conflict("Another execution for this routine is already in progress")`.
The claim transaction rolls back, and the queue loop keeps the run
queued as it does for other 4xx claim rejections. Every other error is
rethrown unchanged. The issue checkout route already maps this violation
to the same 409.
- `doc/execution-semantics.md`: one paragraph on the behavior.

## Verification

- Failing first: `queue.test.ts` "keeps a claim queued while another
task of its routine holds the execution slot". On `master` the claim
raised the `issues_open_routine_execution_uq` violation. Now it rejects
with 409, the run stays `queued`, neither task's lock changes, and
nothing executes.
- Failing first, review round 1:
`heartbeat-queued-run-claim-isolation.test.ts` "keeps a routine run
queued while another task holds the routine slot, then runs it once the
slot is free" goes through `resumeQueuedRuns`. On `master` it rejected
with the unique violation. Now the routine run stays queued with no
error code while another queued run for the same agent executes. After
the slot is released, the routine run executes exactly once (executions
are counted per run, because the finished task can queue its own
follow-up run).
- `cd server && npx vitest run src/services/heartbeat/queue.test.ts
src/__tests__/heartbeat-queued-run-claim-isolation.test.ts`: 70 passed.
- `npx vitest run src/services/heartbeat/recovery.test.ts
src/__tests__/routines*.test.ts`: 4 files, 205 passed.
- `pnpm --filter @paperclipai/server typecheck`: pass.
- Not verified: the queue loop end to end on a real instance. The loop's
handling of a 4xx claim rejection is existing behavior.

## Risks

- While the older task holds the slot, the newer run stays queued and
the loop logs "queued heartbeat run claim was rejected" on each pass. It
is claimed once the older task's resume finishes or its lock is
released.
- Only this one constraint is mapped. Other database errors still escape
as before.
- No schema migration.

## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-03 21:33:00 -08:00
DottaandPaperclip 652cd76026 fix(ui): expose Stop for failed native retries (#18899)
## Thinking Path

> - Paperclip manages agents and their tasks.
> - A native run can fail while it still owns a same-run retry.
> - The server supports Stop for that pending execution.
> - Run details show Cancel only for queued or running records.
> - A failed pending retry therefore has no ordinary cleanup control.
> - This pull request exposes the existing Stop eligibility and uses the
existing cancellation endpoint.

## Linked Issues or Issue Description

Refs #14743 and #14733 for correlated native Stop. Those changes cover
backend cancellation and protocol qualification. They do not add the
failed-run control to run details. Searches of current master and open
PRs found no matching UI repair.

**What happened?**

A native run can show failed while its coordinator still owns a
retryable failure with no accepted result. The backend can cancel that
retry. Both run-detail implementations hide the cancellation button
because the record is terminal.

**Expected behavior**

The selected run offers Stop when the server confirms that pending
native retry. Unknown, legacy, terminal and accepted-result states do
not gain this control. Existing queued and running cancellation stays
available.

**Steps to reproduce**

Render either run-detail implementation for a failed native run with an
eligible retry. The original UI has no Stop button. The original batched
execution projection has no eligibility field. The regression tests
exercise both real components and the actual PostgreSQL projection
query.

**Paperclip version or commit**

The missing control and projection field were reproduced on
44a4ca1c61. Fresh master 8fd917aedf has
identical affected production blobs before this repair.

**Deployment mode**

Board run details for a native Runner task. Local verification uses
rendered React components and PostgreSQL. No hosted task mutation or
provider retry was performed.

## What Changed

- Add optional `execution.canStop` to the existing batched projection.
Read the matching retryable coordinator and absence of an accepted
result. The cancellation endpoint remains the authority owner. Add no
database queries.
- Exclude Grok failed retries from the new hint and both consumers. The
existing Grok Stop endpoint cannot accept a failed retry before
assignment. Keep this limitation explicit and preserve its active-work
authority checks.
- Show Stop for eligible failed runs in both run-detail consumers. Keep
the existing cancellation endpoint and its company, actor and row-lock
checks.
- Use the existing request-correlation contract for failed-native Stop.
Persist one scoped identity before sending and recover it after remount
or reload. Read a matching canonical claim only for the same board
actor, company and run. Use the existing UUID fallback on ordinary HTTP.
Never retry Stop automatically. Refresh the selected run and history
after either outcome.
- Report a stale conflict or a true no-op200 response. Accept a matching
durable pending cancellation intent without requiring terminal
acknowledgement. Keep running, queued and Grok calls unchanged.
- Add actual rendered interaction tests, real batched-query controls and
a short execution contract. Keep maps unchanged for this bug fix.

## Verification

- Untouched UI and real Postgres producer reds cover missing Stop and
missing eligibility. A valid Grok review found an unsupported failed-run
action; actual broker, projection and rendered controls reproduce it
before exclusion. No backend authority was expanded.
- The original60-case focused suite passed, including26 rendered cases,
the batched producer, and actual Grok broker/assignment fences. The
related57-case scope passed before the Grok correction; it is not
presented as final-head proof.
- A fresh native4/5 review found a stale200 no-op. Both actual rendered
consumers failed that new assertion. The real authenticated
HTTP/Postgres fixture proves `canStop=true`, coordinator revocation,
uncorrelated unchanged200 with a cancellation activity, and existing
correlated409 with no new activity or run mutation.
- The correlated repair passed82 affected cases before master
reconciliation. Exact `74c60c9ffe11c05bc85f3d0fa5a136f7e9d445cd`
passed80 rendered/API, HTTP and native-correlation cases. The render
scope verifies request bodies, stable identity after an unknown outcome,
no-op200 error, pending intent acceptance, wrong-run/request denial and
existing active/Grok behavior.
- Subsequent current-head reviews found remount identity loss and
unavailable `crypto.randomUUID` on ordinary HTTP. The corrected helper
passed 52 rendered cases across both consumers, including an unknown
response, initial scoped storage persistence, a prior browser record
with a unique never-mounted run identity, exact ID reuse, canonical
claim recovery, actor switch, wrong run and HTTP UUID fallback. A real
local-board identity mismatch was reproduced with two additional reds
before using the actual server-projected actor ID.
- Real authenticated HTTP and existing native-correlation suites passed
45 cases for canonical claim recovery and actor fences. Production UI,
server and vendored Runner builds and all server/UI type layers passed
at the prior reviewed helper head; exact final
`e0f723b1c86f1529029bf141943323764de28c22` UI types, token gates and
production build passed.
- Fixture failures are retained separately: a missing local SDK build
dependency, and a missing coordinator FK teardown in the new HTTP
fixture. Neither is a behavior red. Both fixtures were corrected and the
affected tests passed.
- Previous-head3d CI required one diagnostic rerun after a Runner
shutdown and a real account-list migration assertion. Two
untouched157-test runs did not reproduce the assertion. Complete
original row equality is unavailable. The assertion remains unresolved;
no test was sorted, skipped or changed to green CI.
- Previous-headf12 CI lost eight child runners. Its one authorized
failed-job retry also lost e2e5 to an explicit shutdown. No second retry
was issued. Those failures remain recorded. Current-head CI and both
substantive reviews are required; older checks and grades are not
credited.
- Previous-head74c CI found a real monitor scheduler timeout. An
untouched local replay and diagnostic-only replay reproduced it: the
original manual-monitor run was still running at the unchanged
three-second deadline. The cause remains unresolved; temporary
diagnostics were removed, and no assertion, timeout or production
monitor policy was changed. No CI retry was issued.
- Exact local `906af5c942ee0963eb4b64552160b57a18bcd2d1` integrates
normal master after #18930 merged. All reviewed Stop production and test
blobs remain unchanged from `e0f723b1c86f1529029bf141943323764de28c22`.
The four rendered, projection, HTTP and correlation files passed 99
cases after integration. All UI/server type layers, token gates and
production UI/server/Runner builds passed at that exact head; the server
build stamp records it.
- Previous-head e0 CI reported a Discord ownership error. That original
failure is preserved. Separate #18930 proves and repairs receiver
construction/renewal races with the same error; the original CI
scheduling order remains unproved. This branch now contains that
ordinary master merge and requires fresh CI rather than rerunning the
old failed attempt.
- Full repository checks were not run locally for this narrow slice. No
hosted task, provider execution or Stop receipt was created by this
work.

## Risks

Grok failed retries remain unsupported by this UI change. The existing
Grok running and queued Stop action, revoke path and assignment fences
are unchanged. A valid initial review caught the overbroad hint; actual
broker, projection and rendered regressions reproduce it before
correction. No backend cancellation authority was widened.

The optional hint can become stale. It grants no permission. The
existing cancellation path rechecks current authority and can reject the
request. Run details show that rejection. Older servers omit the hint,
so failed runs keep their existing behavior until the server updates. No
retry, task hold, accepted-result decision or provider receipt is
changed.

## Model Used

OpenAI GPT-6.1 Sol (`gpt-6.1-sol`) in Codex, with source editing,
terminal execution and tool use. The host did not report 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
- [ ] 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-11-03 23:18:08 -06:00
DottaandPaperclip 0dedbf23c3 fix(chat): retain receiver ownership during runtime construction (#18930)
## Thinking Path

> - Paperclip manages agents and their work.
> - Chat receivers hold a durable lease before they accept messages.
> - Runtime construction can outlast a lease renewal interval.
> - Renewal previously required an attached runtime before construction
had returned.
> - That check could revoke the same receiver that was still starting.
> - This change uses the exact pending initialization as proof during
construction.
> - The existing durable token and endpoint checks still fence every
renewal.

## Linked Issues or Issue Description

Related: #18899 exposed the existing receiver failure in its chat CI
shard. This is a separate lifecycle repair. #18795 provides the relevant
production and sleep fixture context. Searches found no overlapping open
initialization repair.

**What happened?**

The receiver acquired its lease and started its renewal timer. The
service then awaited credentials, command preparation and runtime
construction before attaching the runtime to its context. A renewal
during that interval treated the missing attachment as lost ownership.
Configuration later failed with a Gateway ownership error, although its
original durable token was still current.

**Expected behavior**

The exact pending initialization can retain its own lease while
construction runs. Missing attachment alone must grant no ownership. A
changed token, paused or removed endpoint, changed generation or
credentials, and shutdown must still fence the receiver.

**Steps to reproduce**

1. Use the real configure and PostgreSQL ownership producers with a
60-second lease and 50-millisecond renewal.
2. Hold the actual runtime construction call for 200 milliseconds.
3. Release construction. Untouched code returns the Gateway ownership
error.
4. With the same construction delay and a five-second renewal control,
configuration succeeds.

**Paperclip version or commit**

The causal regression fails on unchanged production source at
`e7549ab613`. The original CI error was observed in the chat shard of
#18899. That CI run did not record the precise setup and renewal order,
so this local proof does not establish its historical scheduling.

## What Changed

- Store the runtime context identity in the existing pending
initialization entry.
- Allow pre-attachment renewal only for that exact tracked
initialization and current local ownership.
- Recheck local ownership inside the existing renewal transaction.
Preserve company, endpoint, generation, credential and original-token
checks.
- Before SDK listener initialization, wait for any older renewal,
recheck local ownership, then obtain fresh durable proof. Preserve the
existing checks after initialization.
- Keep attached runtime identity checks, lease deadlines, renewal
intervals, shutdown and listener retirement unchanged.
- Add actual configure and database race controls. Document the shared
Discord and Photon receiver contract.

## Verification

- Untouched production red: the actual construction gate crosses a
renewal and returns the same Gateway ownership error. The otherwise
identical slower-renewal control passes. No lease, exception or provider
outcome is injected in these positive cases.
- A subsequent review found a second real race: final runtime removal
did not prove that SDK initialization had never started. Four actual
initialization-hook controls failed before the pre-initialize fence. The
final matrix asserts zero hook calls for lost or changed authority,
including a foreign-token control with no periodic renewal due. An
attempted cross-company endpoint move is rejected by the existing
database foreign key; the valid original initialization still succeeds.
- A third actual-producer red holds completion of a real committed
renewal, changes the durable token, and proves that joining that old
promise could incorrectly accept configuration. The corrected startup
check waits for that renewal and then obtains fresh proof; it cannot
credit the earlier result alone.
- Final relevant full Discord registration group, takeover/callback
cases, buffered host-pause recovery, failed construction,
credential-fenced pause and shared Photon consumers: 34 passed across
two files; 1,840 other cases were deselected. Four executed cases cover
Photon transport recovery, failed authentication, duplicate allocation
ownership and checkpoint fencing. The tests use the real
configure/database producers with fake provider transports and observe
SDK initialization; they do not prove physical network behavior.
- All server type layers, the module-boundary gate, and production
server/vendored Runner build pass at exact final
`38cf82ec114bd12c0aa7bbc125ed72b1ae941b4e`. The production build stamp
records this exact commit.
- Earlier fixture mistakes are preserved separately: a test initially
changed every lease key, including the credential lease, and another
attempted an invalid cross-company row move. Neither is counted as a
production behavior red. The corrected controls target the Gateway key
and assert the existing company constraint.
- An initial isolated dependency graph resolved an older shared
telemetry build. Correct workspace links restored the all-layer
typecheck without changing telemetry source.
- Full repository checks were not run locally for this narrow slice.
Fresh current-head CI and substantive reviews remain required. No hosted
task, provider work, fleet action or deployment was performed.

## Risks

The ownership helper is shared by Discord and Photon. The selected tests
cover both consumers. The pending entry grants only permission to renew
the original token; it grants no message, callback or publication
authority and adopts no listener. Missing or changed initialization
identity fails closed. There is no schema change, historical backfill,
new worker or additional idle scan.

## Model Used

OpenAI `gpt-6.1-sol` in Codex, with reasoning, repository inspection,
source editing, shell execution and test tools. The host did not report
the context-window size. A separate coordinator reviews the source
before delivery.

## 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-11-03 22:57:39 -06:00
DottaandPaperclip f211d44c00 fix(heartbeat): preserve queued coalesced comments at claim (#18913)
## Thinking Path

> - Paperclip manages agents and their work.
> - The wake queue records task comments before an agent starts.
> - A queued run can accept another comment through coalescing.
> - Claim rebuilds that run's input from its primary wake.
> - Coalescing updated the run and its receipt but left the primary
batch unchanged.
> - This change saves the accepted batch atomically in the owning
writer.
> - The benefit is complete input at claim without repeating old work.

## Linked Issues or Issue Description

Related: #18887 preserves offline queued turns. #13948 addresses
equivalent automatic wake deduplication. This change repairs the
accepted comment batch at the existing claim boundary.

**What happened?**

A queued run started with comment A. Admission accepted comment B, saved
`[A, B]` in the run context, and finished B's coalesced receipt. The
primary wake still held `[A]`. Actual claim rebuilt input from that wake
and dropped B.

**Expected behavior**

Claim must retain every accepted live comment in order. Stop, changed
source authority, deleted comments, and company boundaries must still
apply. Already claimed wakes must retain their existing recovery source.

**Steps to reproduce**

1. Queue a task run whose primary wake has an authoritative comment
batch `[A]`.
2. Admit comment B through `createHeartbeatQueue.enqueueWakeup` and the
real Postgres admission writer.
3. Confirm that admission accepts `[A, B]` and records B's coalesced
receipt.
4. Claim the original queued run. Before this fix, the claimed input
contains only A.

**Paperclip version or commit**

The untouched producer regression fails on
`d7283e875772ca8c684dc86e471e6b48d543b7b1` with both process and native
Runner/Codex adapter selection. Provider execution is disabled in the
fixture.

## What Changed

- Save the accepted comment batch in the unclaimed primary wake, run
context, and coalesced receipt in one transaction.
- Lock the primary wake before the run. Recheck the expected run status
and same-company, same-agent and run bindings; apply the existing actor
equality fence only to durable receipts before mutation.
- Preserve incoming input as normal deferred work when the locked source
changed. Preserve receipt identity and actor fields.
- Preserve the original primary actor and trigger comment while updating
its accepted batch. Each incoming receipt retains its actual actor. Keep
claimed source payloads and legacy envelopes on their existing recovery
paths.
- Safely defer a same-named different agent without rewriting the
original agent's run, wake or receipt. Missing and foreign-company
targets remain rejected. Do not rebuild input from historical receipts.
- Add actual process/native producer, real database rollback, Stop,
deletion, foreign-source, and fallback controls. Update three direct
writer fixtures for the required expected-status contract.

## Verification

- The untouched process and native producer red on
`d7283e875772ca8c684dc86e471e6b48d543b7b1` loses accepted comment B at
actual claim. The initial incomplete mock is retained separately and
excluded from behavioral proof.
- Current process/native matrix: 22 passed, including mixed authors,
system primary, same-named different agents, changed status, claimed
sources, Stop, deletion and foreign-source boundaries. Queue, Postgres
admission and use-case suites passed 234 cases before the
source-preserving serializer follow-up; its 22 actual producer cases
passed again afterward.
- CI on the previous head found a real ordinary mixed-author
workspace-copyback regression. The earlier 13-case selection omitted
this test; that coverage gap is preserved. The first correction exposed
a second real source-provenance/copyback failure. After preserving the
primary trigger and actor while updating only its batch, the complete
workspace recovery file passed all 188 cases, including ordinary
mixed-author input and native copyback.
- Real HTTP queued-comment consumption, discard/reorder and Stop
boundaries: 101 passed before these review corrections. Existing chat
and Slack consumers each passed their selected real writer case.
- Previous-head CI also timed out in the CLI child-server startup
fixture and a ZIP transfer case. The full untouched transfer file passed
17 cases locally. Startup remains unclassified; its intentionally
reserved port is part of the fixture and is not diagnosed as the cause.
No CI retry was issued.
- An earlier queue run passed its 231 assertions but timed out during
teardown; its preserved replay passed all 60 queue cases. This is
distinct from the behavioral reds.
- Exact `ab59d944895774d89f950248e356e93af00f4832` server type layers
and production server/Runner build passed. The unchanged-default
serializer file passed all 11 cases. Current-head CI and both
substantive reviews remain required; previous grades and checks do not
qualify this head.
- No hosted task was retried or changed. Local tests do not establish
any hosted deferred event's cause, provider execution, native
acknowledgement, physical cleanup, or serving revision.

## Risks

- Admission now performs bounded row reads and locks for each coalesced
wake. It adds no idle scan or remote request. The existing task
transaction owns the operation; lock order follows queued claim.
- Claimed-wake recovery uses source identity. Its payload remains
unchanged, and the existing actual native copyback tests cover that
boundary.
- A source that changes before the lock can defer the new input. This
preserves work instead of consuming it into a stale run. No migration or
historical backfill is required.

## Model Used

OpenAI `gpt-6.1-sol`, with reasoning, repository inspection, shell
execution, and test tools. The runtime does not report the
context-window size. A separate OpenAI coordinator reviewed the source.

## 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-11-03 22:09:58 -06:00
DottaandPaperclip 0884e73b46 test(e2e): verify saved assigned skill use (#18783)
## Thinking Path

> - Paperclip lets people manage agent work and reusable company skills.
> - A saved assigned skill must affect a task's actual execution.
> - Existing QA coverage creates and edits skills.
> - The daily target needs coverage for ordinary saved-skill use.
> - The task receives its receipt data only through one saved skill.
> - The admitted immutable version, native read and saved document
establish the outcome.
> - This PR adds one bounded journey for that gap.
> - A shared lease test also needs a stable clock to assert its exact
timeout.

## Linked Issues or Issue Description

Refs: #18305.

Supporting repair #18885 merged as
`057898530c702ec14c1e14d163e96dfb045cd8c0`. It closes the
recovery-conflict prerequisite. This branch does not duplicate that
production patch.

**What existing behavior does this improve?**

Product E2E coverage for ordinary assigned company skill use on an
existing deployment target.

**Current behavior**

The deployment journey catalog can create and edit a company skill. It
has no separate case that requires a native task to load its assigned
saved skill and use data available only inside that source.

**Proposed behavior**

Create one synthetic managed company skill through the public API.
Assign its canonical key with the normal Latest selection. Create the
task through the production browser composer. Require one native work
run, the exact created immutable version in its admitted skill-version
map, a successful native source read or load, and an agent-authored
saved receipt. Reload and verify the result, revision and original
source.

## What Changed

- Add `saved-skill-use` with one native work run and seven required
checks. The first check is `saved-skill-assignment-bound`.
- Use ordinary `desiredSkills: [canonicalKey]` assignment. Require the
returned stored assignment to have `versionId: null`. This works with
Beta skills off and reads or changes no experimental setting.
- Bind the creation receipt, pre-dispatch source read and version file
inventory. Require a managed skill with the exact company key. Require
the run's authoritative `skillSelectionSnapshot: true` and admitted
version map to match the created immutable version.
- Keep the random receipt reference and numeric inputs only in the saved
source. The task prompt names the skill without supplying its answer.
- Require exact company, task, agent, run, turn and runner bindings for
a complete successful native source receipt. Accept the deployed
OpenCode read wrapper only when both the saved assignment and native
execution input bind its runtime directory to the created immutable
skill version. Also accept the first-party assigned-skill read with a
provider-independent canonical key/version binding from both the saved
API assignment and admitted native input. Require the exact full source,
checksum, byte count and complete page. Keep the materialized
directory-name guard specific to the OpenCode filesystem path. Reject
claims, shell output, foreign or incomplete reads and wrong skill loads.
- Add a narrow Codex reader for the retained controller
`semantic_tool.input` and `semantic_tool.result` pair. Require the
actual native provider, runner, session, call, turn and item; the
admitted immutable key/version; the read-only `read_assigned_skill`
operation; and its successful active-task receipt. Recompute the input
digest and the exact complete seven-field read-result digest from the
saved source. Raw dynamic or MCP names grant no authority. Keep the
builtin OpenCode/filesystem reader body unchanged.
- Require authoritative native commit before qualifying the saved
receipt. Independently check its value and same-run document revision
authorship. Reload and verify the result and unchanged source. Retain
those existing readback receipts in the private checkpoint for
independent verification.
- Keep the exact raw campaign ID in the journal, API name, frontmatter
and saved name. A lost successful creation response remains recoverable
by name.
- Register discovery, source fingerprints, closed checks and existing
feature associations. Update the case and fixture docs. Feature-map
files are unchanged.

- Stabilize one existing shared sandbox lease regression with a scoped
`Date.now` spy and `finally` restoration. Keep its exact timeout,
no-destroy, no-replacement and recorded lease assertions. Change no
production service code.

## Verification

- **Genuine QA staging qualification passed on this exact
`2fb654f7be0bac3bfeb4ec6b7a2eb7a06b8de7e4` head**:
[workflow41763421118](https://github.com/paperclipai/paperclip-cloud/actions/runs/41763421118),
[TestOps
launch2257/result4034](https://paperclip.testops.cloud/launch/2257/tree/4034?treeId=0),
[sanitized
report](https://pages.paperclip.ing/evals-qa-smoke-41763421118-1/). All
seven required checks and all five phases passed, with exactly one
configured OpenCode/DeepSeek native run succeeded and committed. Owned
cleanup closed. A separate source-pinned replay verified25
original-evidence controls and16 rejection controls without altering
originals. Automatic #testops PASS was independently read back. Both
earlier failed QA grades remain unchanged.

- Current-head CI `41758727042` attempt 2 passed with 48 successful jobs
and one skip. The coordinator authorized one unchanged-source
failed-jobs diagnostic. Start and end timestamps prove three fresh
executions (Runner package, server shard 7 and aggregate verification),
45 retained successful executions and one skipped non-execution. The
prepared-context test passed in 2,534 ms. All 28 repository rehome tests
passed. The original failed attempt remains sealed. Its timeout causes
are still unknown. No source, timeout or assertion changed, and no
reliability fix is claimed.
- Fresh exact-head Greptile review is 5/5 with zero unresolved threads.
All 58 current check-runs and the status rollup are terminal and clean.
The native Paperclip review ended neutral and incomplete, so it is not a
validated assessment. The PR remains mergeable after the dependency case
merged in #18901. The source remains frozen. A coordinator-only
qualification draft is prepared with approval false. A genuine QA
staging pass is still required before daily enrollment.
- Current head `2fb654f7be0bac3bfeb4ec6b7a2eb7a06b8de7e4`: 269 focused
tests across eight files passed. These include 42 Codex producer,
converter and false-positive controls. The canonical E2E typecheck
passed with its own declaration prerequisites. Credential-free discovery
lists 24 journeys. Fresh exact-head Greptile review passed at 5/5 with
zero unresolved threads. CI `41758727042` attempt 1 failed. It completed
45 successful jobs, one skip and three failed jobs. The primary failures
were one existing OpenCode prepared-context test at its unchanged
5-second timeout and three existing repository rehome tests at their
unchanged 15-second timeouts. The third failed job aggregates those
results. The timeout causes are unknown. The original attempt remains
sealed. A direct passing twin ran the prepared-context test in 2,533 ms.
All package, script, CLI, UI, workflow and root inputs match between the
actual tested merges. Three server files differ, including
`task-retry.ts` inside the broader rehome import graph; no full-server
equality or causal fix is claimed. The later single diagnostic pass is
recorded above. The native Paperclip review ended neutral with an
incomplete execution, so it is not a validated assessment. No live QA
pass is claimed.
- The valid review on `e1a474` found that Codex displays its first-party
tool as dynamic. Source inspection confirmed that its raw name has no
authority. The retained controller semantic input/result pair supplies
the real operation authority. Core
`09162fe893b464dd2ccdafd14bd3ef1611202a86` and current source have
identical relevant envelope, digest, result-unwrapping and
immutable-read implementation regions.
- Three real-producer positive controls failed against the old grader.
All 42 now pass. The tests call the actual immutable-file reader,
production protocol digest builders, Codex display converter and API
redaction. They validate the event schema and cover omitted defaults,
the exact runtime alias and Unicode bytes. Missing, duplicate,
reordered, failed, foreign, MCP, unbound, partial-page, wrong-digest,
wrong-key/version and nonce-only evidence fails. Generic dynamic output
fails even with copied full text and invented read-only markers.
- The original builtin reader body and source-only fixture function are
byte-identical to `e1a474`. All seven checks, the one-run limit,
500-cent guard, ordinary Latest assignment and Beta-off behavior remain
unchanged. The independent verifier is pinned to this new head. Its
synthetic semantic receipt passes 24 negative controls and its
filesystem receipt passes 16. Older helpers and both original failed QA
seals remain unchanged.
- Earlier integrated head `e1a47438b1c7e51a5bb2896a4ec9b6ed6560318b`:
376 focused checks across eleven files passed, including Saved, settings
and Chat overlap controls. All seven canonical server type layers and
exact-head E2E types passed. All four lease controls passed.
Credential-free discovery lists 24 catalog journeys. No new staging run
has occurred.
- Normal integrations consume actual master
`fbb7055b9020e505cd1bf903b1b1c301ba8f3eef` and actual Chat squash
`d7283e875772ca8c684dc86e471e6b48d543b7b1`. Preserve all five Saved
case/parser files byte-for-byte and retain settings and Chat
registrations. The only manual resolutions were two additive
handler/type anchors. All seven checks, one-run limit and 500-cent guard
remain unchanged. The independent verifier is pinned to this exact head.
- Earlier dbdf CI passed after one authorized failed-jobs recheck of a
confirmed Spot-terminated Build runner. Timestamp comparison proves two
fresh executions (Build and verify), 46 retained successful executions
and one skip. The original failed attempt remains sealed. Its native
Paperclip Review completed neutral with an incomplete execution and is
not a validated assessment.
- New master entries made the earlier dbdf PR conflict. The frozen
qualification guard correctly blocked dispatch before any pin or
receipt. Preserve its unapproved draft as history; it did not authorize
a QA run. This normal integration resolves the observed conflict. Fresh
current-head CI/review and one genuine staging pass remain required.
- Earlier merged head `dbdf1f3cbeaacc7cf9a2a457618035c945bd09f0`: 227
focused checks passed across seven files. All seven canonical server
type layers passed. The E2E typecheck passed using the declarations
built in this same checkout on this exact head. All four existing clock
and lease controls passed. Credential-free discovery lists 22 catalog
journeys, including both saved-skill use and task lifecycle. These are
local checks, not a live staging qualification.
- Normal merge of master `8771606fd87f69ed4057779e3dce3a9bca8088e6`
retains the reviewed task-lifecycle case and its Cursor fixture repair
from #18782. Resolve six shared journey registrations by preserving both
cases. The five saved-skill case files remain byte-for-byte equal to
reviewed `ff3814`. All seven required checks, one-run limit, 500-cent
bound and ordinary Beta-off Latest assignment remain unchanged. No
feature-map delta was added.
- The independent sealed-run verifier is now pinned to this exact merged
head. Synthetic semantic evidence passed with 24 negative controls.
Synthetic filesystem evidence passed with 16 negative controls. Foreign
document authorship and missing reopen/source readback still fail. The
old `ff3814` verifier and both original failed QA seals remain
unchanged.
- Common generated outputs were copied only after all 4,225 package,
script and root inputs matched, installed lock/TypeScript hashes
matched, and all 4,955 source output paths and hashes passed before and
after copy. No server declarations or editable source/module links were
copied. The server declarations were rebuilt locally. A full frozen
workspace install disabled lifecycle scripts and used the same locked
packages. No Rust build ran locally.
- Previous `ff3814` CI `41733864320` exposed a real wrapped PostgreSQL
uniqueness error in a recovery server test. That production defect is
independently fixed and tested in #18885. The later supporting PR merged
separately. This branch does not duplicate its production patch or
regrade the original failure. Preserve the original failed result and
the separate Spot interruption. New full CI and a fresh exact-head 5/5
review remain required.
- Previously, **225 tests passed across six files** in the focused
evidence, flow, contract, catalog and report set after the semantic-read
correction. The prior 181-test proof and all original failed attempts
remain retained. This correction changes only the saved-skill evidence
helper, flow argument, their tests and case documentation. The shared
clock repair remains unchanged.
- E2E TypeScript check passed on Node **24.22.2**.
- Earlier credential-free discovery listed 21 cases and exposes the
ordinary saved-skill journey without a Beta prerequisite. These local
checks make no provider or model calls.
- Source inspection of Core `09162fe893b464dd2ccdafd14bd3ef1611202a86`
confirms key-only selection becomes a stored null Latest selection.
Managed execution resolves its current immutable version and records the
admitted map in `contextSnapshot`. The public run API preserves that map
independently of optional result presentation data.
- Seven invalid admitted-version fixtures wrongly qualified without the
new guard. The regressions now reject them before the read or document
can qualify. Beta-off and Beta-on ordinary request controls use the
production sync validator. Wrong assignments, missing null selection,
source drift, creation-version mismatch and another skill's complete
native output cannot pass.
- Existing native commit, null/compact result data, read provenance,
nonce, saved-result, reload and lost-creation-response controls remain
required. Their original red results are retained.
- The earlier experimental version-pin variant remains unqualified. Its
missing-prerequisite regression and the denied target-setting attempt
remain separate failure evidence. This case qualifies ordinary
assignment and the version actually admitted by the server.
- Normal merge of master `5f1b59ba2c9adfb40a681bf419a8b7e7837d1125`
includes the reviewed inbox snapshot fix #18798 and App/Shell
access-fixture fix #18799. The merge has no conflicts. All 17 journey
source, helper, registration and documentation files remain
byte-identical to reviewed head
`eab260955ff43eeacfdafc9b75dfc9b116f0eb79`.
- Previous CI `41672686614` failed two UI test shards with obsolete
user-ID fixtures; its aggregate verify job also failed. The merged
access-fixture repair addresses those failures. This is a new source CI
run; the failed run is retained.
- Dependency output reuse follows exact path and byte checks for 10,977
tracked build inputs and 3,411 generated outputs. The first copy guard
detected 12 bundles regenerated after its initial manifest and stopped
before any destination mutation. The stable replacement manifest passed
all checks before copying. Final focused tests, E2E typecheck and
discovery passed after the verified copy. No editable source or module
link was shared.
- An earlier integration found stale generated Cursor Cloud
declarations. Canonical dependency builds refreshed them, then typecheck
passed. The original typecheck failure remains recorded.
- The initial report support failure came from a blocked `tsx` IPC pipe
before report execution. The unchanged test passed with normal local IPC
permission. The original failure and diagnostic remain recorded.
- `git diff --check` passes. Full workspace recursive typecheck, unit
run and build remain assigned to hosted CI and coordinator verification.
Full CI `41679720041` passed on the prior
`f3f288a54177f2b9a7b225b1d1ab35440dfc597c` source. A clean 5/5 review
passed on `0f445cf1c622afb25a1af92398784209df542696`. Fresh CI and
review are required after the shared clock fixture repair.
- The first real QA staging attempt on
`f3f288a54177f2b9a7b225b1d1ab35440dfc597c` failed
`saved-skill-native-read-attributed`. Assignment, immutable admitted
version and cleanup passed. The test stopped before document, reload and
source-after observations. The original failed attempt and its complete
evidence remain sealed.
- The original successful builtin `read` event contained the full saved
procedure. OpenCode returned its file wrapper with an unknown normalized
operation, null target and a hashed runtime skill name. The old parser
rejected that shape. A synthetic regression failed before this
correction. The corrected parser recognizes that exact retained read in
memory, with no change to the original grade. Wrong paths, copied shell
text, missing starts, mismatched calls, failed/truncated reads and wrong
version/name bindings still fail. A later staging attempt is described
below. Neither failed original was regraded.
- Original CI `41703535552` failed one shared server test and its
dependent verification job. The lease-resume test expected an RPC
timeout of 31,234 ms, but the runtime correctly supplied 31,233 ms after
one elapsed millisecond. The test and runtime service were
byte-identical to the previous passing source. The failed run remains
recorded.
- A scoped advancing clock reproduced that exact 31,233-versus-31,234
failure locally. A stable clock around only the acquisition call now
preserves the exact original assertion. The repaired test and three
related sandbox lifecycle controls pass on disposable embedded Postgres.
No production timeout, lease preservation, cleanup or replacement
behavior changed.
- The first local reproduction stopped before test execution because six
same-package native Postgres library aliases were missing. Their exact
existing versioned libraries matched the canonical dependency. Restoring
only those internal relative aliases allowed the separate deterministic
red and green measurements. This prerequisite failure remains recorded.
- The second real staging attempt on
`02582aadd23fc128ae29daa69482113cf0f95ad1` also failed
`saved-skill-native-read-attributed`. Authentication, setup, assignment,
source-only input, collection and cleanup passed. It completed one
native run. Later document, reload and source-after checks were not
executed. The complete original remains sealed as failed.
- That attempt used the ordinary first-party
`paperclip_read_assigned_skill` tool. Its complete native receipt
contained the exact canonical skill key, created version, `SKILL.md`
bytes, SHA-256, byte count and null next-page offset. Core verifies the
pinned manifest and file checksum before returning that receipt. The old
grader recognized only filesystem and native skill-load output. An
actual-shape regression failed before this correction. The corrected
parser recognizes the retained read in memory. Wrong keys, versions,
source bytes, hashes, partial pages, semantic failures, copied shell
output and foreign or unpaired native calls still fail. No model prompt,
product source or original grade changed. A live pass on this corrected
source remains required.
- CI `41708636059` on the previous `02582` source passed at attempt 3.
The first two interrupted attempts remain recorded. AWS evidence
identified Spot capacity termination for the second Rust runner. The
targeted retry executed Rust and aggregate verification; 46 successful
jobs retained their exact prior execution timestamps and one job
remained skipped. New metadata IDs do not establish new executions.
Fresh full CI and review remain required for this source.
- Earlier CI `41730743279` passed on
`1ee86838ea528edd8c60fb9ddd858faf3c83e783`. A later review finding
correctly showed that the semantic read still depended on an
OpenCode-only directory-name helper. That finding remains recorded.
Three local Codex, ACPX Cursor and Cursor Cloud flow controls reproduced
the rejection before this correction.
- The semantic path now binds the canonical key and immutable version
through the common native runtime skill selection contract. Its
checksum, complete source, page and native call attribution checks stay
unchanged. Missing, duplicate, wrong-key and foreign-version API/native
assignment controls still fail for all three provider configurations.
The OpenCode filesystem wrapper still requires its exact provider and
materialized directory name. These local controls test the existing
canonical receipt shape; they do not claim live provider qualification
or add a guessed provider encoding. Fresh CI and review are required
after this correction.
- This public PR does not enroll the case in the daily plan or change
protected QA configuration.

## Risks

The target must supply a disposable native agent with normal company
skill-write access and cleanup. It must preserve the authoritative
admitted version map and provide a complete source read or named native
skill-load receipt. Missing evidence fails the case even when the
receipt document has the right value. The OpenCode wrapper path requires
matching assignment and native input records for the same immutable
version; it does not accept a generic targetless read. The builtin
first-party path requires the same version binding and exact full-source
receipt. Codex must provide its authoritative controller pair and a
digest that commits to that exact complete result. Partial pages,
unknown operations and semantic failures do not qualify.

Local flow controls simulate the target API and browser boundaries. They
do not establish live Cloud qualification. Experimental assignment pins
need separate qualification. The journey changes only its owned skill,
assignment and task. It changes no shared agent, setting, model,
connection or budget.

## Model Used

OpenAI GPT-6 through Codex with reasoning and code execution. The
runtime does not expose a more specific model identifier or
context-window size. Local checks made no product-model calls.

## 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 21:13:40 -06:00
27a825da2f feat(telemetry): track the server OS with the registered contract (#18897)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Its first-party telemetry uses a generated event contract and typed
helpers.
> - The existing events do not identify the server's OS family.
> - A proposed event is dropped until the generated contract registers
it.
> - This PR syncs the registered contract and emits the OS family when
an enabled server telemetry client starts.
> - The benefit is a way to compare Windows, Linux, and macOS
installations while keeping machine details out of the event.

## Linked Issues or Issue Description

**Problem or motivation**

We cannot compare Paperclip installations by host OS. Counting task or
install events does not provide this dimension.

**Proposed solution**

Send one `server.started` event with Node's raw `process.platform` as
`os_platform` each time the server initializes its enabled telemetry
client. Compare distinct install identifiers per platform, rather than
restart event totals.

**Alternatives considered**

A proposal-only emitter would send no data. A separate later
contract-sync PR would leave the feature incomplete. This PR includes
the canonical generated SDK and promotes the emitter together. CPU
architecture and OS versions are outside this feature's scope.

**Roadmap alignment**

This is a narrow telemetry addition to existing server startup, with no
new user flow. The full contract copy also adopts the three existing
connector proposals described in #13578; their schemas are already
registered. The generated file stays an exact copy, so their helpers and
tests must be promoted in the same change.

## What Changed

- Export `trackServerStarted` and call it once when server telemetry
initializes. Repeated initialization returns the same client and emits
no duplicate event.
- Copy the canonical generated telemetry SDK byte for byte from schema
source commit `5369e6b`. Remove the startup proposal marker. Normal
opt-out and CI suppression apply before initialization.
- Promote the three connector events included in the canonical SDK.
Derive enum types from the generated map and preserve raw values. Update
their real-client transport and disabled-state tests.
- Assign startup to the existing `operational_enum_count` retention
class. Document OS privacy, container/WSL interpretation, and
distinct-install counting.
- Add real-client startup transport tests and server initialization
tests, including file opt-out, environment opt-out, and CI suppression.
- Keep startup running when telemetry state storage fails. Publish the
client and start periodic flushing only after the startup event
succeeds. On failure, clean up the client, disable telemetry for the
process, and log a generic warning without the storage path or
underlying error.

## Verification

- `pnpm --filter @paperclipai/shared typecheck`: passed.
- `pnpm --filter @paperclipai/shared build`: passed.
- `pnpm --filter @paperclipai/shared exec vitest run`: 98 files, 1,055
tests passed.
- `pnpm --filter @paperclipai/shared exec vitest run src/telemetry`: 6
files, 39 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/telemetry-startup.test.ts
src/__tests__/shared-telemetry-events.test.ts`: 2 files, 19 tests
passed.
- Exact generated artifact comparison, `git diff --check`, and local
secret/PII review passed.
- Proposal inventory contains no startup or connector proposals. The
repository extractor fails with TypeScript 7's missing compiler API;
rerunning it with the installed TypeScript 5.9 compiler API passed. No
extractor source change is included.
- Initial local server typecheck attempts found missing workspace
dependencies and a missing plugin SDK link in this fresh checkout.
Dependencies and the local SDK link were repaired; `pnpm --filter
@paperclipai/server typecheck` then passed. Full repository typecheck,
tests, and build are covered by the PR CI rather than claimed as local
passes.

Greptile found that a telemetry state-file error could abort startup.
New regression tests reproduced this on the previous head with both an
injected permission error and the real state store blocked by a file.
Both now pass, including no published client, no periodic flushing, no
send, and no retry after failure. Shared telemetry tests and server
typecheck were rerun after the fix.

Full PR CI passed on `a41540b7899bc8a0aedbd78d72f8b69ad1a4b318`,
including typecheck, build, all selected test shards, and the final
verify gate. [Successful workflow attempt
2](https://github.com/paperclipai/paperclip/actions/runs/41752039595/attempts/2).
Attempt 1 was interrupted when the server (1/12) runner received a
shutdown signal; its incomplete result was retained in the PR
discussion. The same-commit shard retry and dependent gate passed.
Greptile reviewed this exact head at 5/5 with all threads resolved.

## Risks

This change starts sending one OS family value per server startup when
telemetry is enabled. It sends no OS version, architecture, hostname,
path, prompt, or machine identifier beyond the existing envelope.
Containers and WSL report the runtime's OS. Event totals reflect restart
frequency, so use distinct install identifiers for OS adoption analysis.

The complete generated SDK also enables the previously registered
connector proposals. Those events keep their existing approved payloads;
no connection names, credentials, tool arguments, or results are added.
Privacy review is requested for both newly active paths.

Deploy the registered schema before merging this app change. Live
production delivery has not been tested. No schema migration is needed.
Revert this PR to stop the new startup emitter and restore the prior
contract.

## Model Used

- Original implementation: Anthropic Claude Opus 5.5, as recorded in the
original commit.
- Completion: OpenAI `gpt-6.1-sol`, xhigh reasoning, through Codex in T3
Code; shell, Git, and automated tests. Context window size is not
exposed by the runtime.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have described the issue in-PR following the feature issue
template
- [x] The final change and description contain no private issue links or
proposal markers
- [x] The existing PR branch is preserved to retain its review history
- [x] I have run the targeted 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: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-03 21:00:47 -06:00
DottaandPaperclip 057898530c fix(server): handle wrapped recovery action conflicts (#18885)
## Thinking Path

> - Paperclip coordinates durable agent work and recovery.
> - Independent workers can find the same task needs recovery.
> - The database permits one active recovery action for that task.
> - A losing insert must read the action that already committed.
> - Drizzle wraps the PostgreSQL unique violation in its cause.
> - The current classifier reads only the outer error and misses that
conflict.
> - This PR recognizes the same allowed conflict through a bounded cause
chain.
> - Both workers can then return the one durable action through the
existing retry path.

## Linked Issues or Issue Description

Refs: #18783.

**What happened?**

The saved-disposition-debt recovery regression failed in CI
`41733864320`. Two recovery workers collided on
`issue_recovery_actions_active_source_uq`. The fixture captured an
unhandled PostgreSQL `23505` error. A separate canceled shard in that
run lost its AWS Spot instance; that interruption is a different
failure.

**Expected behavior**

Two service instances that race to create the same active action must
return that one action. Unrelated database failures must still
propagate. Existing transaction and ownership rules must stay in force.

**Steps to reproduce**

Hold a PostgreSQL SHARE lock on the recovery-action table. Start the
same source-scoped upsert on two independent service instances. Confirm
both real INSERTs are waiting in `pg_stat_activity`, then release the
lock. The original code persists one action but rejects one caller with
a `DrizzleQueryError`. Its outer code is absent; its `PostgresError`
cause has SQLSTATE `23505` and the expected active-source constraint.

**Paperclip version or commit**

Supporting branch starts at master
`54782706e1d83d92295246e499627930df03662d`. It now contains a normal
merge of master `8771606fd87f69ed4057779e3dce3a9bca8088e6`. The
classifier correction stays unchanged. The PR diff against that master
still contains only the recovery service and its tests.

## What Changed

- Inspect up to eight error causes with cycle detection.
- Require SQLSTATE `23505` and one of the same two recovery-action
constraint names on the same error. Support the PostgreSQL driver's
`constraint_name` field.
- Reuse the existing source-scoped retry path and its unchanged
three-retry limit.
- Add a deterministic real-database collision regression and no-retry
controls for unrelated uniqueness, other SQLSTATEs and cyclic errors.
- Keep the original recovery regression, assertions and timeouts
unchanged. Keep the saved-skill journey separate in #18783.

## Verification

- Review follow-up: the real collision test now checks the intended
retry merge. The surviving action has attempt count 2, active status,
the default unlimited budget, the expected owner, supplied evidence and
supplied attempt timestamp. Its wake and monitor fields remain null.
Existing identity and convergence assertions run first.
- A disposable counterfactual returned the winner unchanged after the
lost insert. It passed the original barrier, one-row, no-rejection and
same-ID assertions, then failed the new attempt-count check with 1
instead of 2. The counterfactual was not committed. The production
service was restored byte-for-byte. The actual service passed all seven
focused race, no-retry and sequential controls. The affected canonical
test-tree-2 typecheck passed against verified unchanged local
declarations.
- Parent `4f8abfe4f2244649219ef6ced3534634193f0b53` full CI
`41746314291` passed all 48 selected jobs with one intended skip. Its
5/5 summary also had two duplicate unresolved reliability comments about
the missing state assertions. This test-only follow-up addresses both.
Fresh full CI and current-head review are required again.
- The deterministic real-database regression failed before the
correction. Both inserts reached the lock and exactly one active action
persisted. The losing error was a `DrizzleQueryError` with a
`PostgresError` cause, SQLSTATE `23505` and constraint
`issue_recovery_actions_active_source_uq`.
- **11 focused checks passed across two files on the integrated head**
(181.72 seconds). This includes the real-database collision, no-retry
controls and all four unchanged recovery producer/dependency
regressions, including the original failed real-clock saved-debt case.
Both callers return the one persisted action.
- Current-head canonical server typecheck passed on Node24.22.2 with all
seven layers: below, cycle, above, tests and test-tree-0 through
test-tree-2. Canonical shared, SDK, DB and Runner TypeScript
prerequisites passed. The first filtered installs lacked CLI and UI
imports required by the new test layers. Those module-resolution
failures remain recorded as setup failures. A full frozen workspace
install with lifecycle scripts disabled supplied the same locked
dependencies. No source or type-configuration workaround was used. No
Rust or broad workspace build ran locally; full repository typecheck,
tests and build remain hosted-CI gates.
- The actual supporting master baseline reproduced the same wrapped23505
failure before the correction. Earlier configuration-resolution, local
IPC and native-library prerequisites remain separate setup failure
evidence. No test failure was reclassified as a pass.
- First supporting CI `41739489620` was canceled before its build/test
jobs completed. Its later automatic full-CI run `41739747947` failed.
The recovery server shard passed. The Runner failure was the Cursor
fixture case repaired upstream in #18782, now included by the normal
master merge. That CI also observed an attachment-browser timeout, a
repository-rehome timeout and a lost-runner connection. Their underlying
causes remain unknown. A later pass will establish recovery, not explain
those earlier failures. These original results remain retained.
- Current `fbd392b9b65dbcf4ac078825096037ed0d490a0b` full CI
[41751706634 attempt
2](https://github.com/paperclipai/paperclip/actions/runs/41751706634/attempts/2)
passed: 48 successful jobs and one intended skip. Attempt 1 remains
retained: ten primary jobs lost their bound Spot capacity and the verify
job failed as a dependent. A single ordinary failed-job recheck produced
eleven fresh successful executions (ten primaries and verify);
timestamps prove 37 successful executions were retained. The skip is not
counted as execution. Current Greptile is 5/5 with both addressed
reliability threads resolved and no unresolved findings. All 57 current
check runs are successful, neutral or skipped, and Snyk is successful.
This is CI recovery; it does not explain the separately retained older
attachment and repository timeouts.
- The original CI failure and both failed saved-skill QA attempts remain
unchanged. This PR does not establish a live QA pass or deploy a
production change.

## Risks

Conflict classification must not turn unrelated errors into retries. The
new controls require those errors to propagate after one insert attempt.
The traversal is bounded and stops at cycles. Transaction, company
scope, ownership, notifications, retry count and database constraints
are unchanged. No schema or public API change is required.

## Model Used

OpenAI GPT-6 through Codex with reasoning and code execution. The
runtime does not expose a more specific model ID or context-window size.
These checks make no model or provider calls.

## 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 19:51:37 -06:00
Michael NguyenandPaperclip 58d1b21098 fix: separate fresh setup from archived personal connections (#18902)
## Thinking Path

> - Paperclip helps people manage AI agents and their service access.
> - Each personal connection belongs to one user.
> - Setup can recover an archived connection by its name and provider.
> - A fresh setup can therefore select another user's old identity and
fail its owner check.
> - This change creates a separate connection for fresh setup after
personal archival.
> - Explicit reconnect still requires the existing owner and preserves
its history.

## Linked Issues or Issue Description

**What happened?**

A fresh GitHub setup can return 403 after a personal connection with the
same name was archived. The request does not include a reconnect target.
Setup still selects the archived identity by name and provider.

**Expected behavior**

Fresh setup creates a separate personal identity. It leaves the archived
connection, credentials, grants, and activity unchanged. Explicit
reconnect rejects a different user.

**Steps to reproduce**

1. Create a personal GitHub connection in a test company.
2. Archive that connection and its application.
3. As another authenticated member, start fresh personal setup with the
same name and provider.
4. Observe the owner-check failure before this change.

**Paperclip version or commit**

Base: db9be4c5a. Tests use isolated database and HTTP fixtures.

**Deployment mode**

Authenticated. The legacy owner can be a local board identity.

## What Changed

- Exclude implicitly recovered archived personal connections from fresh
setup.
- Use the existing company-scoped name allocator to create a separate
application and connection.
- Test three owners: a legacy board identity, another user, and the
current user.
- Check credential isolation, unchanged archived records and activity,
missing-key rejection, and explicit reconnect denial.
- Keep managed revival and generic MCP rollback/reconnect fixtures on
explicit reconnect targets.
- Verify generic personal URL setup creates an independent identity and
preserves the archived records.
- Document fresh setup and reconnect behavior.

## Verification

- Final head: `96184e3179aab2eeb88df87d442691741a4875a2`. Full CI
recovered and completed successfully: [run 41752164958, attempt
2](https://github.com/paperclipai/paperclip/actions/runs/41752164958/attempts/2).
PR rollup: 53 successful checks, 3 skipped, none failing or pending.
Typecheck, build, all server shards, Runner lanes, and E2E gates pass.
- Exact-head Greptile: 5/5, zero inline comments or review threads.
Independent engineering/security reviews approved the production
implementation; engineering approved the test-only follow-up. Browser
acceptance review approved with the timing qualification below.
- Final-head local generic MCP suite: 84/84 passed (23.26 s); server
typecheck passed. Reviewer independently reran the three changed
scenarios: 3/3 passed (15.74 s).
- Production head `587631230`: local tool-access suite 504/504,
recursive typecheck, and full build passed. Broad local checks were not
repeated for the test-only delta. Full local `pnpm test:run` remains
incomplete after interruption during the first chat shard; no
full-local-suite pass is claimed.
- Initial full CI at `587631230` failed three generic MCP fixtures that
still relied on implicit archived personal recovery. All three
reproduced locally (15.01 s). The test-only follow-up gives owner
reconnect/rollback scenarios explicit targets and verifies independent
fresh URL setup without relaxing their assertions. Corrected server
shard 12/12 passes on final head. The unfinished earlier run was
cancelled after the follow-up push.
- Initial current-head E2E shard 3/8 failed a 5000 ms task-heading wait
at `tests/e2e/cloud-auth.spec.ts:46` (40 passed, one failed). Its
screenshot/accessibility snapshot contains the expected heading. The
single authorized no-code rerun passed 41/41 with zero test retries,
including all URL, handoff, and login-form checks. The first-heading
assertion took 3774 ms on rerun. Both attempts tested the same merge
commit for this PR head. These observations support a timing concern;
the original cause remains unknown. The unchanged Cloud-auth path does
not invoke archived-connection setup. Recovery is established; an
infrastructure cause is not. If it recurs, capture an instrumented
first-navigation trace and compare an unchanged baseline under
comparable conditions.
- PR remains draft. Merge and deployment are outside this request.
Synthetic provider/browser fixtures do not prove live GitHub
authorization or a deployed fix.

## Risks

- Fresh setup after personal archival now creates a new ID and a
suffixed name, including for the same owner. Clients must supply an
explicit target to reconnect the old identity.
- The guard applies to all personal gallery connections, not only
GitHub.
- No schema migration is required. Explicit owner checks remain in
place.
- Security review approved the unchanged production implementation.
Merge and deployment are outside this change request.

## Model Used

OpenAI GPT-6 through Codex. The runtime does not expose a more specific
model snapshot or context window. Used reasoning, code editing, and
shell tools.

## 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 17:51:29 -08:00
DottaandPaperclip e2dc2cc0be fix(native): retire idle shared-sandbox ingress owners before dispatch (#18893)
## Thinking Path

> - Paperclip manages agents and their tasks.
> - Native tasks can share one reusable sandbox workspace.
> - A completed child can keep its provider process warm.
> - A parent has its own logical session, but the sandbox has one
ingress listener.
> - Retirement by task identity leaves the child's listener in place.
> - A new parent Runner then fails to bind that listener.
> - This pull request retires a proven idle physical owner before the
parent's distinct session starts.

## Linked Issues or Issue Description

Refs #15353 and #17097 for listener configuration and warm transport
rotation. Refs #18682 for owned process retirement. #18860 repairs a
different resume-source failure. Searches found no existing repair for
retained warm sessions on different tasks that share the same physical
ingress allocation.

**What happened?**

A completed child and a later parent can use the same reusable sandbox.
Warm-session retirement compares logical task ownership. It does not
find the child's distinct task scope. The child keeps the fixed ingress
listener, and the parent's new process fails with
`runner_ingress_bind_conflict` / address already in use.

**Expected behavior**

A parent must start in its own logical session after the proven idle
prior listener closes. Active, foreign or unverified owners must remain
protected.

**Steps to reproduce**

Run the `retires the completed child listener` cases in
`native-session-executor.test.ts`. The actual executor registers a warm
child. PostgreSQL holds its committed, accounted result and clean
expired lease. A distinct parent receives the same allocation and
current workspace writer. The provider boundary uses real TCP listeners.
The original executor reaches the second bind with the child still
listening and fails with EADDRINUSE. The changed executor waits for the
owned close, then starts the distinct parent session.

**Paperclip version or commit**

Reproduced on ab2d2098a2. The branch
includes current master 44a4ca1c61 before this PR was opened.

**Deployment mode**

Native Runner with provider ingress on a reusable sandbox workspace.
Local regression uses real PostgreSQL and local TCP with a controlled
provider-process boundary. No hosted provider call was made.

## What Changed

- Keep physical ingress identity separate from logical task/session
ownership. Never attach the parent to the child's session.
- Reserve a physical allocation during local admission. Retire a
distinct warm owner only after canonical company, agent, workspace,
provider, allocation, lease, task and coordinator checks pass.
- Require the current remote workspace writer and location to match the
admitted run. Require the prior run to be committed and accounted, with
its original controller generation and a clean released or expired
lease.
- Use the existing close, checkpoint and stopped-file collection path.
Wait for close before parent dispatch, then recheck canonical authority
before staging files or calling the parent backend. Retain the entry
when close is uncertain.
- Add actual executor/PostgreSQL/TCP safety cases and document the
contract. Keep maps unchanged for this bug fix.

## Verification

- Untouched production at ab2d2098: actual executor + PostgreSQL + TCP
fails EADDRINUSE. Same positive case passes after the change.
- Final focused suite: 20 passed. It covers distinct parent session
identity; foreign company, allocation and workspace; active or
unaccounted owner; changed controller; reassignment; competing
lease/writer; stale allocation metadata; Stop before and after claim;
workspace-lock race; failed close that leaves the listener and accepted
result intact; and controller transfer, Stop or writer change during
awaited close. These last cases reject the stale parent before backend
invocation after the old listener closes.
- Untouched head3425acbe: transferring the incoming coordinator during
close still reaches the parent backend. The same actual
executor/PostgreSQL/TCP case passes after the post-close authority
check.
- Broader `native warm session supervision` suite: 160 passed. This run
preceded the final two-line logical-session collision guard; the final
20-case suite includes that guard.
- `pnpm --filter @paperclipai/server typecheck`: all source and test
layers passed, including `src/__tests__`.
- `pnpm --filter @paperclipai/server build`: production server and
Runner build passed at current head572acfef79. The exact-head build
stamp is recorded.
- The provider/close boundary is controlled. The fixture seeds canonical
committed/accounted finalization data in PostgreSQL. These tests do not
claim a live model result, operator ACK, hosted PID-to-listener
identity, or recovery of a historical task.
- Full repository pnpm checks were not run locally for this server-only
slice. Required current-head CI and reviews are pending.

## Risks

The new retirement path can close a process from another task. It
requires the same canonical company, agent, allocation and workspace, a
current exclusive writer, and a completed idle owner. Missing or changed
evidence leaves admission blocked. Active owners, changed generations,
reassignment and cancellation remain fenced. A failed close retains the
owner and saved result.

This covers current-controller warm entries with recorded physical
identity. Historical processes outside the registry keep the existing
exact-owner recovery requirements. No port shuffle, port kill, task
capability expansion, new polling loop or invented receipt is added.

## Model Used

OpenAI GPT-6.1 Sol (`gpt-6.1-sol`) in Codex, with source editing,
terminal execution and tool use. The host did not report 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
- [ ] 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-11-03 19:44:12 -06:00