Commit Graph
6622 Commits
Author SHA1 Message Date
Devin FoleyandClaude Opus 5.5 65c5d8d600 fix(claude-local): start approve-all sandbox ACP sessions in bypassPermissions (#19430)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The `claude_local` adapter runs Claude through the ACP engine
(claude-agent-acp) inside remote sandboxes
> - Paperclip seeds the sandbox Claude settings with
`permissions.defaultMode: "default"`, so Claude asks acpx for permission
before every MCP call and every Bash call that is not read-only
> - In the default `approve-all` mode, acpx approves every request. The
permission round trip gives no access control, but it is the step where
tool calls stall
> - Intermittently, the Claude CLI never sends that permission request.
The call stays `pending` until the 30-minute stalled-tool watchdog ends
the turn (`acpx_tool_stalled`)
> - This pull request starts approve-all sandbox sessions in
`bypassPermissions`, so Claude does not use the permission round trip at
all
> - The benefit is that sandbox runs no longer lose about 35 minutes per
stall, and `approve-reads` and `deny-all` agents keep their current
permission behavior

## Linked Issues or Issue Description

Refs #16688
Refs #16769

**What happened?**
Sandbox `claude_local` ACP runs ended with `acpx_tool_stalled`: "A
pending tool call (kind other) received no update for 30 minutes, so the
run ended the turn instead of waiting for the execution timeout." The
diagnostics from #16688 and #16769 show the shape. The stalled call got
its `pending` update and its input updates. Then its permission request
never reached acpx. Finished calls of the same kind in the same run did
send a request. The stall occurred on MCP calls and on Bash calls, in
the main agent and in background subagents. Calls that need no
permission (ToolSearch, read-only Bash) never stalled.

**Expected behavior**
In `approve-all` mode, a tool call must not wait on a permission round
trip that acpx always approves.

**Steps to reproduce**
1. Run a `claude_local` agent with the ACP engine on a sandbox target,
with the default `approve-all` permission mode.
2. Make many MCP or network Bash calls during the turn.
3. Sometimes a call stays `pending` with no permission request. The
watchdog then ends the turn after 30 minutes.

**Agent adapter(s) involved**
Claude Code (`claude_local`, ACP engine, sandbox target).

## What Changed

- `claude-config.ts`: `prepareClaudeConfigSeed` accepts
`permissionDefaultMode` (`"default"` or `"bypassPermissions"`). The
sanitized seed writes that value into `permissions.defaultMode`. In
bypass mode the seed writes `settings.json` also when the host has no
`settings.json`. Hooks, MCP servers and host permission rules are still
removed.
- `claude-config.ts`: `overrideRemoteClaudeLocalPermissionMode` sets
`bypassPermissions` in the **sandbox copy** of the workspace
`.claude/settings.local.json`. The engine writes that file with
`defaultMode: "default"`, and the local tier overrides the user seed.
Before workspace copy-back, the override is reset in the sandbox, so
copy-back never treats it as an agent edit. After copy-back, if the host
file still has `bypassPermissions`, only `defaultMode` is put back. Host
and agent edits to other settings are kept.
- `acp.ts`: the sandbox managed-home seam reads the run's effective ACPX
permission mode. If the mode is `approve-all`, the seam seeds
`bypassPermissions`, applies the workspace override above, and sets
`IS_SANDBOX=1` through the existing `claudeSandboxPermissionEnv` helper.
This lets the bypass apply when the sandbox user is root, the same rule
as the CLI lane. The mode resolution is the same as the engine's: an
absent value or `"default"` becomes `approve-all`. An unrecognized value
keeps `"default"`.
- These keep `defaultMode: "default"` and do not change:
`approve-reads`, `deny-all`, the CLI lane, the Test probes, managed AI
connections, and an operator `CLAUDE_CONFIG_DIR`.
- Tests: two seed tests in `claude-config.test.ts`. In `acp.test.ts`: an
end-to-end executor test for five permission modes (absent,
`approve-all`, `default`, `approve-reads`, `deny-all`) that checks the
mode Claude actually gets during the turn and that the host settings
keep `default`, and a test that host and agent edits to the workspace
settings survive copy-back without `bypassPermissions`.

## Verification

- `pnpm exec vitest run packages/adapters/claude-local`: 393 passed, 1
skipped (before the copy-back commits); full CI is green on the current
head.
- Regression proof: with the new tests and the old `acp.ts` and
`claude-config.ts`, the seed tests and the absent, `approve-all` and
`default` cases fail (`expected 'default' to be 'bypassPermissions'`).
The `approve-reads` and `deny-all` cases pass on both versions, because
their behavior does not change.
- `pnpm exec tsc --noEmit` in `packages/adapters/claude-local`: clean.

## Risks

- Behavior change for sandbox `claude_local` ACP runs in `approve-all`
mode (the default): Claude does not ask permission for tools. Paperclip
already approves every request in this mode, so the access outcome does
not change. Claude's bypass-immune safety checks still go through
`canUseTool`.
- The seed directory is content-addressed, so the bypass seed and the
default seed are different snapshots and do not overwrite each other.
- Agents that set `approve-reads` or `deny-all` keep the permission
round trip. The stall can still occur for them.

## Model Used

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

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 06:44:43 -08:00
DottaandClaude Opus 5.5 d87f6fa4c2 test(runner-e2e): add staging-soak reliability cases to everyday workflows (#19455)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The paid runner E2E suite (`tests/runner-e2e`) drives the real
browser, server, native runner, provider and Daytona
> - A manual staging soak found about nine reliability bugs (S47–S55,
H2, U9). Each fix got unit or integration tests
> - The end-to-end journeys that exposed the bugs existed only as ad-hoc
browser scripts, so a regression could return without a signal
> - These journeys are expensive, so they must not run per PR or in
`--all`. They must run on demand before a release
> - This pull request adds the journeys to the existing
`everyday-workflows` suite as an explicit `reliability` group
> - The benefit is one command that runs the whole reliability set
before a release or after a risky runner change

## Linked Issues or Issue Description

**What is the problem?**
The staging-soak scenarios that found S47–S55, H2 and U9 are not
repeatable. They were ad-hoc browser scripts on staging.

**Why does it matter?**
Without a repeatable end-to-end case, a reliability regression (stalled
follow-ups, duplicate execution, dead Retry, hangs after silent
commands) can return without a signal.

**What is the proposed solution?**
Add the scenarios to the paid `everyday-workflows` suite as
explicit-only cases with independent graders and a selectable
`reliability` group.

Refs #19370, #19355, #19354, #19407, #19419, #19394, #19421, #19431 (the
related fixes).

## What Changed

- `everyday-reliability-cases.ts`: seven new cases
(`interrupt-redirect`, `reassign-midrun`, `three-turn-revise`,
`long-silent-command`, `tree-pause-release`, `queue-steer`,
`queued-across-restart`), their soak signatures, prompts, bounds and the
explicit matrix.
- `everyday-reliability-flow.ts`: browser and public-API drivers for
each journey. A shared watcher records `executionBlocker`,
`interruption` and execution projections while it waits. It uses an
offered Retry at most once (never while an automatic retry is
scheduled).
- `everyday-reliability.ts`: pure graders. They encode the product rule:
interrupted work resumes automatically or offers a Retry that works on
first use. A send-a-message blocker, a dead or repeated Retry, a silent
stall, or duplicate execution fails.
- `types.ts` / `selectors.ts`: new optional `explicitGroup` on a task.
Such a task is selected only by its group, `--case`, or an exact `--id`.
`--all`, the plain suite and generic selectors skip it. `--list` still
shows it.
- `catalog.ts`: the suite gets the reliability tasks, the released
Grok/Kimi/Pi facades (only for `long-silent-command`), a per-turn
Daytona override for `three-turn-revise`, the `reliability` and `slow`
groups, definition version 11, and a computed `expectedMatrixSize` (52 →
62).
- `everyday-flow.ts`: a narrow hook calls the scenario driver after task
creation. Riley is created for the reassign and tree cases. The
reliability checks are added after settlement.
- `everyday-observations.ts`: an allowed interrupted run may end
`operator_interrupted` or `issue_reassigned`. Lifecycle checks accept a
declared reassigned parent owner.
- `live-fixtures.ts`: reliability cells get the existing 1,000-cent
company and lead-agent hard stop.
- Tests: new `everyday-reliability.test.ts` (selection exclusion,
matrix, credentials, and calibrated pass/fail cases for every grader).
Updated counts in `catalog.test.ts`, `everyday.test.ts` and
`installed-cli.test.ts`.
- Docs: new "Staging-soak reliability group" section in
`EVERYDAY-WORKFLOWS.md` (cases, signatures, run commands, cadence,
coverage boundary) and a link from `doc/evals.md`.

`everyday-flow.ts` was already over 1,000 lines. This PR adds only about
20 lines of wiring there. The scenario logic is in the new modules.

Matrix (10 cells, all Daytona, single attempt): one `runner-codex`
(gpt-5.6-sol) cell per case, plus `runner-acpx-grok`, `runner-acpx-kimi`
and `runner-acpx-pi` for `long-silent-command`.

Run command:

```sh
pnpm test:e2e:runner -- --suite everyday-workflows --group reliability --max-parallel 2
```

## Verification

- `pnpm test:e2e:runner:typecheck` passes.
- `node node_modules/vitest/vitest.mjs run --config
tests/runner-e2e/vitest.config.ts everyday catalog installed-cli
selectors support.test`: 15 files, 362 tests pass.
- The full runner-e2e Vitest run has the same 14 failures with and
without this change (`controller-outage`, `operating-receipts`,
`pi-controls-evidence`, `pi-file-evidence`, `workflow-security`). These
fail on the base commit in this local environment too.
- `pnpm test:e2e:runner -- --list --suite everyday-workflows --group
reliability` lists exactly the ten cells.
- `--all`, `--suite everyday-workflows` (still 52 cells),
profile/environment selectors and the daily/weekly scheduled selections
contain no reliability cell. A test asserts this.
- Not run: the paid cells. They need provider and Daytona credentials.
The first paid run of the group is the acceptance step.

## Risks

- The drivers have not run against a live stack. A first paid run can
expose harness timing or selector issues (for example, how fast the
queued Interrupt control appears). The failure then names the stalled
step and keeps `reliability-observations.json`.
- `long-silent-command` does not inject a runner connection loss. The
named-cause contract is checked only if a loss happens naturally.
- The Grok/Kimi/Pi facades run as ordinary released profiles. If one
returns to pending qualification, it drops out of the group and the
matrix size follows.
- No product code changes. Existing everyday cells keep their selection
and checks. The suite definition hash changes because the suite
definition changed.

> 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, via Claude Code with
tool use (shell, file edit, code execution). Standard context window.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 08:37:22 -06:00
DottaandPaperclip b8e69c6dd0 fix(ui): prevent layout shift when usage limits load (#19463)
Reserve the usage-limit property row during the first runtime-state request and replace its skeleton in place.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-11-06 08:34:38 -06:00
DottaandClaude Opus 5.5 e680fa6814 fix(native): retry startup interrupted by a closed runtime API listener (#19462)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Native runs prepare a remote workspace, then start a runner and a
provider session
> - A graceful server shutdown closes the HTTP listener before it drains
runs
> - A run that is still preparing then asks for the local runtime API
origin, and the lookup fails with
`local_runtime_api_listener_unavailable`
> - Recovery did not know this code. It treated the failure as an
ambiguous provider state and blocked the task behind a board-owned
action, although no runner or provider process existed
> - This pull request classifies the closed listener as a definite
pre-session failure
> - The benefit is that a deploy no longer strands a preparing task. The
same run gets a bounded bootstrap retry that the next server process
claims

## Linked Issues or Issue Description

**What happened**
A native Codex task on a remote sandbox spent about ten minutes in
workspace preparation. A deploy started a graceful shutdown during that
time. When preparation finished, the executor called
`resolveLocalRuntimeApiUrl()` to build the tool authority. The listener
was already closed, so it threw
`local_runtime_api_listener_unavailable`. No runner backend or provider
session had started. The failure classifier returned the generic
`native_session_interrupted` code. `nativeProviderRecoveryEvidence` then
returned `ambiguous_state`. The run became a terminal failure, the task
became blocked, and the recovery action told the board to resolve an
ambiguous session and to not open a replacement session. The same
signature occurred on three other runs during an earlier deploy.

**Expected behavior**
A failure that occurs before any runner or provider process exists is
safe to retry on the same run. The task must stay in progress with a
scheduled, bounded bootstrap retry. If a provider event or checkpoint
exists, recovery must stay operator-owned.

**Steps to reproduce**
Start a native run. Close the server's HTTP listener before the executor
creates the runner backend. Observe a `terminal_failure` with
`recoveryMode: "ambiguous_state"` and a blocked task. The new
integration test reproduces this with the real
`resolveLocalRuntimeApiUrl()` and a real Postgres database.

**Environment**
Server native runtime, remote sandbox target, `codex_app_server` driver.
Related recovery work: #19249, #19304, #19305.

## What Changed

- `nativeSessionFailureSourceCode` returns a new
`local_runtime_api_listener_unavailable` source code for this error.
- `nativeProviderRecoveryEvidence` treats that code as definitely
pre-session. The existing guard stays: the run must have no checkpoint
and no durable provider event.
- The chat failed-run retry accepts the code in its list of ordinary
pre-session failures, so an exhausted startup retry can still be retried
from chat.
- New integration tests in `native-startup-retry.integration.test.ts`
cover the retry path and the operator-owned path when a provider event
exists.

## Verification

- Red before the fix: the new test failed with `phase:
"terminal_failure"`, `recoveryMode: "ambiguous_state"`, `recoverable:
false` and a board-owned recovery. This matches the failed run on the
deployed instance.
- Green after the fix: `cd server && npx vitest run
src/services/native-runtime/native-startup-retry.integration.test.ts`
passes 26 of 26 tests. The retry test asserts `retryable_failure`,
`bootstrap_retry`, backoff timing, an agent-owned `resume_native_run`
wake, and a claim through `claimNativeSessionResumptions`.
- Safety case: when a durable `turn.started` event exists, the same
error stays `terminal_failure` with `ambiguous_state` and a blocked
task.
- `npx vitest run
src/services/native-runtime/native-session-executor.test.ts -t
"listener|classif|pre-session"` passes.
- Not run: a live deploy that interrupts a preparing sandbox run. The
test uses the real listener lookup and the real recovery tables, but not
a real sandbox.

## Risks

- Low risk. The change only affects runs that fail with this exact error
and have no checkpoint and no provider event.
- The retry uses the existing bootstrap budget of three attempts with
backoff. Repeated failures still stop and hand the task to the board.
- The GitHub callback bridge that started in the sandbox before the
failure is a per-attempt helper. The existing cleanup path releases it.

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

## Model Used

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

## Checklist

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

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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 08:27:59 -06:00
DottaandPaperclip 6cafcaf89b fix(workspaces): recover unpublished archived Git uploads (#19459)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Remote workspace imports preserve existing files before the server
changes workspace authority.
> - Archived Git imports reconstruct saved files and Git state in a
private repository.
> - A failed upload can leave a pending snapshot journal with no object
in storage.
> - A later reconstruction can have different Git administrative bytes
for the same saved logical source.
> - This pull request lets that import record a new unpublished capture
after it proves that prior objects are absent.
> - The benefit is safe retry through the original import journal while
source checks and atomic publication remain in place.

## Linked Issues or Issue Description

Refs #19396. Refs #19409.

A snapshot upload can fail before object persistence. The caller then
removes its private archive. A retry reconstructs the same saved Git
workspace. If its administrative bytes differ, the snapshot owner
rejects the retry with
`remote_workspace_snapshot_operation_content_changed`. The pending
journal remains blocked even though no snapshot object or remote
authority was published.

## What Changed

- Add an optional unpublished recapture capability to snapshot storage.
Bind it to the original source operation. Existing callers keep the
exact manifest check.
- Enable this capability only for archived Git reconstruction. The
migration owner keeps the source recipe, task binding, ownership, and
publication checks.
- Verify every prior object before new capture admission. Unknown or
corrupt objects stop the retry. Reuse an available prior object,
including an object that appears while the replacement upload runs.
- Compare the observed upload journal under the snapshot row lock before
appending an attempt. Keep network I/O outside that lock. Preserve all
prior attempt metadata.
- Keep the first atomic publication as the winner. A losing uploader
verifies and returns that saved reference.
- Add real PostgreSQL, filesystem, and Git regression tests. Document
the retry behavior.

## Verification

- Baseline `70d23612dd9176cae8ee6b2b6dcf2f6eade9a42a`: the new real Git
fixture fails with
`remote_workspace_snapshot_operation_content_changed`. It fails the
first upload before persistence, removes the old source cache, and
advances unrelated remote refs. The saved task commit and restore point
stay fixed.
- Final frozen source: 20 tests pass in a fresh process. This includes
the real Git regression and all snapshot storage cases. Tests cover
prior object recovery, corrupt and unknown objects, normal caller
mismatch rejection, operation binding, stale journal admission, and late
upload publication in both orders.
- Reproduce the final focused check from `server/`:

  ```sh
node ../node_modules/vitest/vitest.mjs run --config vitest.config.ts
src/services/remote-workspace-snapshot-storage.test.ts
src/services/remote-workspace-migration.test.ts -t
'failed-upload-retry|snapshot storage' --reporter=dot
  ```

- An earlier six-suite run had 201 passing cases and one failing
late-object case. That run started before the final prior-object recheck
was added. The failing case passes in the fresh final run above. This
earlier run is not a clean full-family result for the final source.
- That run skipped 16 lifecycle cases because
`PAPERCLIP_REMOTE_WORKSPACE_LIFECYCLE_TEST_DATABASE_URL` was unset.
Migration, storage, reservation, and concurrency tests used real
disposable embedded PostgreSQL.
- `git diff --check` passes. Full repository typecheck passed before
rebasing. Full test, build, and current-head CI results are pending.

## Risks

- The optional capability permits different physical captures. The
migration owner must prove that the logical source and current ownership
are unchanged. Ordinary mutable workspace uploads keep the previous
mismatch guard.
- An old upload can complete after the last object check. The first
authenticated atomic publication wins. A late upload cannot replace its
object or snapshot reference. Every candidate remains in the retention
journal.
- Unknown storage results and journal changes fail closed. They can
require another canonical retry. This change adds no retry loop and
changes no database schema.

## Model Used

OpenAI Codex, GPT-6 family. The worker used reasoning, tool use, code
editing, and local test execution. The worker runtime did not expose an
exact model ID or context window.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [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>
canary/v2026.1106.0-canary.14
2026-11-06 08:04:35 -06:00
DottaandPaperclip 2a6aae3963 perf(workspaces): overlap bounded snapshot file hashing (#19458)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The server saves portable workspace archives before it moves file
authority to object storage.
> - Each capture checks and hashes every eligible file before and after
archive creation.
> - Those stable reads run in series and leave file I/O idle on
workspaces with many files.
> - This pull request overlaps at most four stable reads across the
whole directory walk.
> - It preserves entry order, source checks, Git checks, and archive
bytes.
> - The benefit is shorter archive capture with bounded extra I/O
concurrency.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Portable remote workspace archive capture in the server. Related: #19396
preserves saved-upload recovery. #17132 applies a similar technique to
the separate Runner file-diff snapshot path; this PR changes the server
archive owner only.

**Current behavior**

The archive walker waits for each file's stable read and hash before
visiting the next file. A local packed-Git fixture with 6,000 files and
52.4 MB of contents took a median 4.640 seconds in the final two serial
samples.

**Proposed behavior**

Keep up to four stable reads active across all nested directories.
Reserve each entry's position before reading it. Await all reads before
returning success, failure, or cancellation. The final two treatment
samples took a median 3.439 seconds, a 25.9% reduction in capture time.

**Reason and benefit**

Captures run twice during a fresh workspace migration. Bounded I/O
overlap reduces time spent in this owner without changing the archive
contract. This measurement does not establish an end-to-end migration
speedup.

**Breaking changes**

None intended. The file format, manifest order, source validation, Git
validation, API, and schema stay the same.

## What Changed

- Share one four-read limit across the recursive walk. Preserve
deterministic entry positions when reads finish out of order.
- Drain active reads on every exit before callers can remove scratch
files or return control to a source writer.
- Check cancellation before creating a file read stream.
- Exercise real file handles across nested directories, out-of-order
completion, source replacement, cancellation, and serialized archive
capture.

## Verification

- Focused archive, archive-environment, and snapshot-storage suites: 113
tests passed. The first sandbox run could not bind local test sockets;
the same suites passed with local listener access.
- Full workspace typecheck passed before rebasing. The rebase changed
neither touched file.
- Full local `pnpm test:run` stopped at an unchanged chat integration
test: `resolves ambiguous provider effects explicitly and never lets
terminal failures starve reconciliation`, with `socket hang up`. The
same case passed once in isolation on the rebased head. Its original
cause remains unknown; this is not a full-suite pass.
- Full `pnpm build` passed on the rebased head. Current-head CI is
pending.
- Benchmark: actual owning sources, Node 26.4 on macOS arm64, one
importer, the same packed-Git fixture. All 12 trial archives had
identical manifest digest, SHA-256, and 72,908,826 bytes. Final paired
medians were 4.640 seconds serial and 3.439 seconds with four reads.
Total process plus Git CPU increased about 12%.
- An initial pair was slower with concurrency (23.352 seconds versus
11.228 seconds); the cause remains unknown. Later paired reductions
ranged from 24% to 37%. The benchmark host was shared. Linux Node 24 and
eight-importer throughput are not yet qualified.
- No user commands, configuration, or document contracts changed. Code
comments explain ordering and cleanup.

## Risks

- Four simultaneous reads can increase I/O and CPU contention when
several captures run together. The bound is per capture. Hosted
throughput needs measurement after the ordinary release.
- A shared-host local benchmark has limited predictive value. This PR
claims a measured capture improvement on that fixture only.
- Rollback is a code revert. No migration or stored-data rewrite is
required.

## Model Used

OpenAI Codex, GPT-6 family, with tool use, code execution, and delegated
review. The runtime did not expose an exact deployment variant or
context-window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [ ] 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-06 08:01:09 -06:00
DottaandPaperclip 4696ff4990 fix: reserve agent In Progress display for active runs (#19453)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task status must tell operators whether an agent is working or
waiting.
> - A task can retain its in-progress lifecycle status after its run
stops.
> - The shared read projection only handled scheduled monitoring waits.
> - The UI also ignored other projected statuses for in-progress tasks.
> - This pull request requires an active task run for the In Progress
display.
> - Operators can see waits without changing execution or recovery
authority.

## Linked Issues or Issue Description

**What happened?**

An agent task still showed In Progress after its run ended with an
access failure and left a pending connection request. Stale execution
locks and saved lifecycle state could show the same false activity.

**Expected behavior**

An agent task shows In Progress only with a task-linked queued or
running run. Otherwise, it shows Blocked for recovery, In Review for a
pending response or review, Monitoring for a scheduled check, or Todo
for remaining work. Human ownership keeps its selected status.

**Steps to reproduce**

1. Create an agent-owned task with an in-progress lifecycle status.
2. End its run and retain a pending human request.
3. Read the task, its summary, and search results. Before this change,
they still show In Progress.

## What Changed

- Derive visible activity from company-scoped, task-linked runs in the
existing shared SQL projection.
- Project inactive tasks to existing waiting or todo states. Preserve
lifecycle status, locks, admission, and recovery.
- Make the shared UI resolver use the projection while preserving
explicit and optimistic status changes.
- Cover stopped runs, stale locks, human waits, company isolation,
monitoring, filters, counts, search, and UI labels.
- Document the presentation rule in the product and execution contracts.

## Verification

- Red before the server fix: 7 new regression cases failed because they
returned in_progress. The 10 existing cases passed.
- Green after the server fix: 20 cases passed in
issue-display-status.test.ts, using embedded Postgres and real task
APIs/search. Seven nearby active-question cases also passed.
- Review regression: dismissed requests, historical questions, and
returned review work each failed before the follow-up fix. All three now
pass using the existing active-interaction rule and pending review
state.
- Red before the UI fix: 4 label cases failed. Five additional safety
cases caught stale projections hiding explicit transitions.
- Green after the UI fix: 43 tests passed across
task-status-presentation, issue-filters, and issue-detail-subissues.
- pnpm -r typecheck passed. All UI token gates and synchronization
checks passed.
- pnpm build passed. Server typecheck and build passed again after the
review fixes.
- Full CI passed on 45c4ababc5, including
typecheck, build, general/serialized tests, Runner tests, and browser
checks. The final rollup has 55 successful checks, one neutral check,
and three configured skips.
- The local pnpm test:run command passed all 1,931 chat integration
tests. Its remaining duplicate full-suite run was stopped once full CI
passed on the same commit. This is not a claim that the complete local
command finished.
- Greptile reviewed the final commit at 5/5. Both prior findings have
regression coverage and all review threads are resolved.
- Live acceptance at commit 45c4ababc5: started a fresh isolated local
instance, created an inactive agent task, and saved a pending human
request. API reads changed from Todo to In Review. An initial readiness
probe ran before startup finished; the doctor then verified the exact
serving revision before acceptance. Chrome showed In Review in the task
header, breadcrumb, and sidebar. Stored lifecycle status remained
in_progress. The instance was stopped after verification.
- The reported stopped state was inspected in the live UI. The fix is
not deployed there. Local API tests reproduce the failure shape without
replaying the real deployment task.

## Risks

- This changes visible status and display-status filter membership for
inactive agent tasks. Stored lifecycle filters keep their existing
meaning.
- The projection uses existing indexes on task runs, recovery actions,
and interactions. It adds no worker, timer, schema change, or execution
action.
- A queued run counts as active admission. Human-owned tasks retain
explicit ownership status.

## Model Used

- OpenAI GPT-6 through Codex, with reasoning, repository tools, and code
execution. A more specific model ID and context-window size are not
exposed in this session.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-06 07:59:38 -06:00
DottaandPaperclip 2f35eb1080 feat(dev): add incremental local verification and pass reuse (#19345)
## Thinking Path

> - Paperclip coordinates agents that make and verify repository
changes.
> - Local verification currently defaults to the complete Vitest suite.
> - Contributor instructions also require broad local checks at each PR
handoff.
> - CI already owns conservative affected-test selection and broad
final-revision checks.
> - This pull request adds local selection and pass reuse from those
existing contracts.
> - Agents can get focused feedback and use CI to close the remaining
coverage gaps.

## Linked Issues or Issue Description

**Subsystem affected**

Developer verification scripts and contributor guidance. Related
selection work: #18398 and #18464.

**Problem or motivation**

An agent that repeats an unchanged local verification step starts the
complete suite again. The handoff guidance also duplicates broad CI
coverage locally.

**Proposed solution**

Add `pnpm verify` for the actual working tree. Select affected Vitest
specifications with the CI task policy and dependency graph. Reuse
successful checks whose inputs are modeled. Run opaque checks fresh.
Report exit 2 when broader verification is required. Update the
contributor contract to accept applicable final-revision CI coverage.

**Alternatives considered**

Plain `vitest related` omits filesystem and opaque dependencies.
Commit-only cache keys miss dirty files. Running full local suites at
every handoff repeats CI work.

**Roadmap alignment**

This is repository development tooling. It does not change the product
roadmap.

## What Changed

- Add `pnpm verify`, `--explain`, `--base`, `--full`, and `--no-cache`.
- Reuse CI selection and exact runner ownership for dirty, staged,
committed, deleted, renamed, and untracked inputs.
- Keep native server suites and serialized suites in their local owners.
Keep six sequential chat shards.
- Cache selected passes within one worktree. Hash source, fixtures,
installed package contents, dependency bindings and installation
metadata, dotenv files, child environment, toolchains, platform, and
selection.
- Run opaque consumers fresh. Preserve failed, interrupted,
mutated-input, and partial-coverage outcomes.
- Update AGENTS.md, CONTRIBUTING.md, and DEVELOPING.md guidance. Record
the shared `verify` command as the sole exception to the package-script
rule. Register the new tests in the existing CI contract-test command.
- Add optional root/environment inputs to shared graph helpers. Existing
CI defaults stay the same.

## Verification

- Fourteen new integration tests pass locally across focused runs with
real Vitest selection and execution; CI colors are covered.
- 51 existing targeted selector/input, project-isolation, and
stable-runner checks passed. Four relevant runner contracts also pass
after the workspace change.
- Repeated unchanged execution skips reusable tests. Source,
environment, installed code, dependency bindings, and new untracked
tests invalidate results.
- Installed-code mutation with a restored modification time still
invalidates a cached pass. Internal skill-directory links preserve
reuse.
- Workspace B/C tests prove discovery, reusable/fresh partition
execution, pass reuse, and external-input failure. Chat selection
produces exactly six single-worker partitions.
- A deliberate unrelated failing test is omitted by narrow selection and
caught by `--full`.
- Red/green regressions prove fingerprints preserve file boundaries and
reject edits made between diff discovery and snapshotting.
- Opaque external-file changes still fail on a subsequent run. Source
mutation and process interruption cannot publish reusable passes.
- `pnpm verify --explain` works in the checkout and requests broad CI
coverage for this tooling change. No duplicate full local suite was
started.
- [Final-revision
CI](https://github.com/paperclipai/paperclip/actions/runs/42117291789)
passed on `ed85d96ddff82c05c325948680b593b0f48c473d` (attempt 2). The
scoped Canary retry preserved all passing test/build results.
- [Greptile
review](https://github.com/paperclipai/paperclip/pull/19345#issuecomment-6670050175)
is 5/5; all four review threads are resolved.
- The additional Paperclip review concluded neutral because its
execution stopped; it produced no assessment.

## Risks

- Pass reuse depends on conservative dependency classification. Opaque
consumers, external database/live settings, and non-isolated projects
retain fresh or broad coverage.
- Local passes cover selected Vitest checks. Required CI,
build/typecheck, release, browser, and platform acceptance remain
separate obligations.
- Cross-worktree caching and cross-agent scheduling are outside this
change.
- The initial installation scan reads dependency contents;
stat-validated, worktree-local digest memoization avoids rereading
unchanged contents. Subsequent scans still inspect file identity and
nanosecond change/modification times.
- No dependency lockfile or workflow changes.

## Model Used

OpenAI Codex, `gpt-6.1-sol`, max reasoning, with local shell and file
tools. Context window size is not exposed by this host.

## 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-06 07:29:10 -06:00
DottaandPaperclip 70d23612dd fix: offer Interrupt when queued-message steering is unavailable (#19450)
## Thinking Path

> - Paperclip lets people manage work performed by AI agents.
> - Task chat saves follow-up messages while an agent runs.
> - A native runner can accept Steer only after an active provider turn
exists.
> - During preparation, or with an unsupported provider, the queue
showed a disabled Steer button and no delivery action.
> - The server also rejected Interrupt for ordinary native queues.
> - This change offers an explicit Interrupt fallback and uses the
existing cancellation and admission path.
> - People can send their saved messages while Stop confirmation,
cleanup, and execution gates remain enforced.

## Linked Issues or Issue Description

Related: #15865 covers mixed legacy queues. #15030 covers steering
acknowledgements. This change covers native queues without available
steering.

**What happened?**

A queued message had a disabled Steer button while the native runner was
still preparing. Clicking it did nothing. The server returned HTTP 409
for an explicit Interrupt request against the same native queue.

**Expected behavior**

Offer Steer when the provider can accept same-turn input. Otherwise
offer Interrupt, labeled with its actual effect. Preserve saved messages
until the previous execution stops and normal admission accepts the
successor.

**Steps to reproduce**

1. Start a task with the Paperclip Runner adapter.
2. Submit a follow-up while the run is preparing, before its provider
turn opens.
3. Observe the disabled Steer button above the composer.
4. With this change, click Interrupt. The existing Stop path cancels the
previous execution and submits the saved queue through normal admission.

**Paperclip version or commit**

Reproduced locally on master at 523818ea9b. The branch is rebased on
2f9d3d43c3.

## What Changed

- Offer Interrupt for unavailable or unsupported native steering when
the parent supplies an interrupt callback.
- Keep Steer for an available provider turn.
- Accept explicit Interrupt for a deferred native queue and record its
run target.
- Bind native continuation to the exact Interrupt queue, run, actor, and
cancellation receipt. This lets one click send the messages after Stop
completes.
- Add component, task-thread, real PostgreSQL route, and continuation
authorization regressions.
- Close the route fixture database pool before disposing its PostgreSQL
instance.
- Document the fallback and its Stop and admission requirements.

## Verification

- Red: two component cases failed because Interrupt was absent. Two
route cases returned HTTP 409, `This queue does not support
interruption`, before the repair.
- Red: stronger delivery assertions also caught `send_predates_stop`
after the first repair. The admission fix covers both the freshness
exception and later eligibility check.
- Green: both route regressions now wait for preparation settlement,
then deliver both saved messages exactly once without a second click.
The complete route suite passes 119 tests. The complete continuation
suite passes 376 tests, including wrong actor, queue, target, and
missing-receipt cases.
- UI: task-thread and queued-message suites pass 394 tests, with one
existing skip. The issue-page suite also passes (the earlier three-suite
run passed 644 tests before the final three UI cases).
- Token gates and `git diff --check` pass.
- Full workspace build and low-memory typecheck pass. The initial
typecheck caught a missing `membershipRole` in an unrelated fixture; the
rebase picked up the correction already on master, and the complete
typecheck then passed.
- The full local `pnpm test:run` passed all six chat shards (1,931
tests). It was stopped during general-server validation after the final
commit passed all PR CI gates. The complete local suite is not claimed
as a pass.
- Earlier route-suite teardown timed out with an open database pool.
Closing that pool fixed teardown; the complete route suite then passed.
- All final-head CI gates pass: 52 successful checks and two intentional
skips. Greptile is 5/5 on the same head with no unresolved review
threads.

## Risks

Interrupt stops the previous execution and starts a successor; it is a
different action from same-turn steering. The UI labels that action
explicitly. Cleanup or execution gates can keep the messages queued.
Existing company access, board permissions, queue revision, run target,
cancellation proof, and admission checks remain in place. No schema
migration is needed.

## Model Used

OpenAI Codex (GPT-6), with reasoning, tool use, and code execution. The
exact model ID and context window are not exposed by this client.

## 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-06 07:11:38 -06:00
DottaandPaperclip 275b334fc5 fix(workspaces): skip redundant repository admission snapshots (#19444)
## Thinking Path

> - Paperclip manages AI agents and their execution state.
> - Remote workspace admission must preserve files before execution
starts.
> - Admission saved the entire workspace once per registered repository
on every run.
> - Existing snapshot receipts can prove that validated repositories are
already saved.
> - This change uses those receipts and saves new repositories once per
admission batch.
> - It also preserves fresh configuration metadata and reports actual
workspace reuse.

## Linked Issues or Issue Description

**What happened**
Remote follow-up runs uploaded an unchanged workspace for each
registered repository before starting the provider. Configuration
persistence also overwrote new evaluation metadata, and freshness logs
could claim replacement when the same workspace was reused.

**Expected behavior**
Validate existing repositories without uploads when a committed snapshot
covers their descriptors. Save acquired repositories before publishing
ready receipts. Retain current evaluation metadata and report the actual
workspace outcome.

**Steps to reproduce**
Use a remote workspace with two registered repositories. Prepare it,
then admit a follow-up run. Observe repository snapshot operations
despite unchanged repositories. The regression tests exercise this with
real Postgres, Git, and separate sandbox directories.

**Environment**
Server remote workspace admission at commit
c0214826b3. Related work: #19374 handles
interrupted clone staging; this change handles snapshot admission cost.

## What Changed

- Skip repository saves when the committed snapshot covers the validated
descriptor.
- Save all repositories in one admission snapshot before publishing
ready receipts.
- Retain descriptor coverage only when the new archive contains the
checkout. Drop deleted checkout coverage so admission can acquire it
again.
- Restore `needs_repair` status for failed batch saves and retry failed
uploads.
- Let fresh preparation metadata override stale reserved metadata while
preserving storage authority.
- Report actual reuse when freshness policy requested replacement.
- Add regression coverage and fix an existing routine user fixture
missing its membership role.

## Verification

- Red: original code fails unnecessary-snapshot, evaluation-metadata,
and reuse-message regressions.
- Green: 517 affected tests across six suites before the final coverage
correction. After that correction, all 102 remote workspace/concurrency
tests and 96 archive tests pass. Imported-repository, deleted-checkout,
and upload-retry cases pass.
- Additional red/green: deleted checkout coverage fails before the fix
and passes after it. All 96 archive tests pass, including manifest
digest and size bounds.
- `pnpm -r typecheck`, `pnpm build`, and `pnpm check:token-gates` pass
locally.
- All final-head CI verification jobs pass, including the aggregate `ci
/ verify` check. Greptile gives the final head 5/5.
- The monolithic local `pnpm test:run` passed all six chat shards and
continued through general server tests. It was stopped after final-head
CI passed; it did not complete locally. The targeted local regressions,
affected suites, typecheck, and build completed successfully.

## Risks

Snapshot receipts must refer to committed bytes. Legacy receipts require
one conservative save. Failed uploads must not publish repository
readiness. Replacement-class drift still retains the original
configuration for an explicitly reused workspace.

Existing directories with damaged or missing Git metadata still require
repair. Master and this PR both validate those directories without
cloning over them, preserving any surviving user files. Clearing a
snapshot descriptor would not change that path. Complete directory
deletion is different: this PR clears coverage so normal acquisition can
clone it again.

## Model Used

OpenAI gpt-6.1-sol through Codex. Used reasoning, repository inspection,
code editing, and local execution. The host does not expose the context
window size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-06 07:08:59 -06:00
Dotta 1cd1e8c598 fix(ui): place task message copy control left of the timestamp (#19451)
## Thinking Path

> - Paperclip is the open source app people use to manage agents at
work.
> - Task threads show a time below each message.
> - Own messages align with the right edge.
> - The copy control added in #19442 sits to the right of the time.
> - This shifts the time inward even when the control is hidden.
> - This pull request moves the control to the left of the time.
> - The time stays at the outside edge.

## Linked Issues or Issue Description

Refs #19442.

**What happened?**
The copy control occupies space to the right of the time on own
messages.

**Expected behavior**
The copy control sits to the left of the time. The time stays at the
right edge.

**Steps to reproduce**
Open a task with your own sent message. Move the pointer over its
footer.

## What Changed

- Move TaskChatBubbleActions before the time in the own-message footer.
- Keep the layout change inside TaskChatBubble. No shared contract
change is needed.

## Verification

- All 36 TaskChatBubble tests pass.
- Design-token gates pass.
- UI typecheck and production build pass.
- Browser layout checks pass on desktop and touch: copy is left of the
time, the time ends at the bubble edge, and the time does not move on
hover.
- Clipboard contents and copied feedback pass.
- Screenshots cover idle, hover, copied feedback, and touch using the
production component in the existing Storybook fixture.
- Current-head CI passed: build, typecheck, selected general tests, all
eight e2e shards, policy, security, and aggregate verification.
- Greptile and Paperclip reviews score the current commit 5/5. No open
findings or review threads remain.
- The first Greptile review requested an order assertion. The existing
copy test now checks that the control precedes the time and the time is
the last footer element. All 36 component tests pass. The review thread
is resolved.
- Initial test and Storybook commands used the wrong working directory
or argument form. Corrected commands run successfully.
- Local typecheck and Storybook initially could not resolve
mdast-util-from-markdown. A frozen dependency install restored the
missing package. Typecheck, build, and browser checks then passed. An
early browser retry reached the server before it was ready; the
ready-server run passed.

## Risks

- Low risk. This change only moves the existing control before the time.

## Model Used

- OpenAI GPT-6 Codex. Exact deployed model ID and context window are not
exposed in this runtime. The agent used reasoning, tools, and code
execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with Fixes / Closes /
Refs OR (b) described the issue in-PR following the relevant issue
template
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change and contains no internal
ticket id or instance details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable (existing copy
tests cover unchanged behavior; browser checks cover position)
- [x] I have updated relevant documentation to reflect my changes (the
behavior is described here)
- [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
2026-11-06 07:07:04 -06:00
DottaandPaperclip 2f9d3d43c3 feat(ui): copy own task messages as Markdown (#19442)
## Thinking Path

> - Paperclip is the open source app people use to manage agents at
work.
> - The task thread shows human messages and agent replies.
> - Users can copy agent replies with an existing control.
> - Users need the same control for their own messages.
> - This pull request adds that control beside the message time.
> - Users can copy the original Markdown and attachment links.

## Linked Issues or Issue Description

**What existing behavior does this improve?**
Copy a message from the task thread.

**Subsystem affected**
ui/ — React board UI.

**Current behavior**
Your own messages have no copy control beside the time.

**Proposed behavior**
Move the pointer over the time to show a copy icon. Select it to copy
the original Markdown. Keyboard focus also shows the icon. Touch screens
show it at all times.

**Reason and benefit**
Users can reuse their message text without losing Markdown or attachment
links.

**Breaking changes**
None.

## What Changed

- Reuse TaskChatBubbleActions in the footer of your sent messages.
- Keep the timestamp and iMessage source label.
- Show the control on hover or keyboard focus. Keep it visible on touch
screens and small screens.
- Respect the reduced motion setting.
- Test original Markdown, attachment links, copy feedback, other
authors, and pending messages.
- Add the required membership role to the existing routine-user test
fixture so UI typecheck can pass.
- Keep this behavior in TaskChatBubble. No shared contract change is
needed.

## Verification

- 45 focused component, artifact, and clipboard tests pass after rebase
onto master.
- The routine-user fixture tests pass: 9 tests.
- UI typecheck, UI production build, design-token gates, and browser
checks pass.
- Five screenshots cover idle, hover, copied feedback, keyboard focus,
and touch states. The existing Storybook own-message story uses the
production component with sample content.
- Browser checks verify clipboard contents and reduced motion.
- GitHub PR CI passed for d46d2890c4:
build, typecheck, selected general tests, all eight e2e shards, policy,
security, and aggregate verification. Storybook visual and out-of-scope
suites were skipped by CI.
- Greptile and Paperclip reviews both scored the current commit 5/5 with
no actionable findings. No open review threads.
- Full repository typecheck and build stop because Cargo is missing in
the local environment. Earlier full tests reached resource limits. No
full-suite pass is claimed.
- The first typecheck after rebase found stale local dependencies and a
missing membershipRole in a routine-user test fixture. A frozen
dependency install resolved the package errors. This PR adds the
required fixture field and the later UI typecheck passes.
- Browser states to verify: idle, hover, keyboard focus, copied
feedback, touch, and reduced motion.

## Risks

- Low risk. The change adds a footer control for sent messages owned by
the viewer.
- The control uses the existing clipboard fallback and error feedback.

## Model Used

- OpenAI GPT-6 Codex. Exact deployed model ID and context window are not
exposed in this runtime. The agent used reasoning, tools, and code
execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
canary/v2026.1106.0-canary.12
2026-11-06 06:35:16 -06:00
github-actions[bot]andlockfile-bot 523818ea9b chore(lockfile): refresh pnpm-lock.yaml (#19443)
## Thinking Path

> - Paperclip uses a dependency lock for reproducible builds.
> - Dependency changes on master need matching lock entries.
> - Paid eval reports also pin a reviewed offline correction.
> - Reports must install before paid model work starts.
> - This automated PR keeps the lock and report proof together.

## Linked Issues or Issue Description

**What happened?**

Dependency manifests on master changed after the last committed lock
refresh.

**Expected behavior**

Frozen installs must match those manifests. Eval reports must retain
matching offline lock proof.

**Steps to reproduce**

Run a frozen install before and after this lock refresh.

## What Changed

- Refresh the dependency lock from the manifests on master.
- Include the matching offline report correction and hashes.

## Verification

- Pinned pnpm resolution completed with lifecycle scripts disabled.
- The report proof refresh accepted the reviewed committed baseline.
- The workflow rejected changes outside the lock and report proof files.

## Risks

The lockfile bot merges this PR without CI or review when every added
package version is at least 3 days old on npm. Otherwise the PR waits
for a maintainer, and the daily refresh tries again. The bot merges only
the exact commit this workflow generated from manifests already reviewed
on master. Unknown report lock baselines remain blocked. This update
changes no permissions or paid enable gates.

## Model Used

None — generated by pnpm and the deterministic report-lock updater.

## Checklist

- [x] Thinking path and problem description included
- [x] Generated lock and matching report proof included
- [x] Release-age gate decides between bot merge and maintainer review

Co-authored-by: lockfile-bot <lockfile-bot@users.noreply.github.com>
2026-11-06 12:19:36 +00:00
DottaandClaude Opus 5.5 e9999071f3 fix(ui): store intraword underscores as typed, without the editor's escapes (#19350)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - People write task requests in the Markdown editor, and the stored
text becomes the agent's prompt
> - MDXEditor exports every literal underscore as `\_`, even inside
words such as `E2E_ASK_12` or `snake_case`
> - Agents then see backslashes the person never typed, and sometimes
repeat them in their answers
> - This pull request removes only the intraword escape when the editor
hands text back, as is already done for `\>` and `\<`
> - The benefit is that identifiers, filenames and exact strings reach
agents exactly as typed

## Linked Issues or Issue Description

**What happened?**
In QA staging smoke (launch 3108, task QA-671), a person created a task
through the UI that asked for the exact reply
`E2E_ASK_12_journey_1793913565974_507b01b8`. The stored description, and
the agent's prompt, contained `E2E\_ASK\_12\_journey\_…`. The agent's
`paperclip_finish` summary was unescaped, but its final answer copied
the escaped form. The posted reply therefore did not equal the requested
text, though the rendered Markdown looks the same. Earlier attempts
passed only when the model happened to unescape.

**Expected behavior**
Text the person typed is stored as typed. An underscore inside a word
never needs an escape, because CommonMark never treats it as emphasis.

**Steps to reproduce**
1. Type `snake_case` in any Markdown editor field, such as a new task
description or the chat composer.
2. Read the stored value through the API.
3. Before this change it is `snake\_case`.

**Paperclip version or commit**
master at da3084a05d.

## What Changed

- New `ui/src/lib/underscore-markdown.ts`:
`unescapeIntrawordUnderscores` parses the text with
`mdast-util-from-markdown`, collects the source ranges of `inlineCode`
and `code` nodes, and turns `\_` back into `_` only outside those
ranges. It only changes a run of escaped underscores between two letters
or digits (`snake\_case`, `foo\_\_bar`). Text it can't parse is left
unchanged.
- These stay exactly as the editor wrote them: code spans, and fenced or
indented code blocks at any blockquote or list depth; escapes at a word
boundary (`\_\_init\_\_`, which would otherwise render as strong
emphasis); escaped backslashes (`\\_`).
- `MarkdownEditor.tsx`: `toStoredMarkdown` applies the helper after the
existing `\>` and `\<` handling, which is unchanged.
- `ui/package.json`: `mdast-util-from-markdown` becomes a direct
dependency. It was already installed transitively at 2.0.3. Per the
lockfile policy, `pnpm-lock.yaml` is not committed.
- Tests:
- Unit cases cover lists, blockquotes, list-indented fences (including
Greptile's case), inline code, escaped and unmatched backticks,
`foo\_\_bar`, `__init__` and escaped backslashes.
- Two editor tests: the original intraword test, and a round trip of
prose plus code.
- Negative control: when the code ranges are removed, the code
byte-identity tests fail.

## Verification

- `npx vitest run src/components/task-chat src/components/MarkdownEditor
src/lib` in `ui/`: 277 files, 3314 tests passed.
- `pnpm check:token-gates`: all gates clean. `vite build` passed. No
type errors in the changed files.
- An earlier CI Build failure on this PR was `npm error notarget No
matching version found for @microsoft/teams.api@2.2.0`, in the
bundled-server prefetch. That version is published now, and the failure
is unrelated to this change.
- Not verified live on QA yet. QA only uses this after its stacks are
rolled to a build that contains it.

## Risks

- Low risk. Only escapes that cannot change rendering are removed, and
only outside parsed code.
- Stored values written before this change keep their escapes until the
text is edited again.
- Parsing adds a small cost per editor change, and only when the text
contains `\_`.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, browser, code editing). Extended reasoning was used.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 06:18:47 -06:00
Nicky LeachandClaude Opus 5.5 832ede93ed fix(apps): stop the Connectors page from pegging the CPU on managed instances (#19380)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Connectors page (`/apps`) merges native connectors with about
1,600 apps from the Composio and Arcade catalogs
> - #19328 made some native connectors yield to aggregator sign-in on
managed instances. To decide, it checks whether any aggregator serves
each native row
> - That check scans the whole catalog. It runs once per native row for
every catalog app, so one pass makes about 1,600 × 110 × 1,600 name
comparisons
> - The pass also re-runs on every render, because a memo dependency is
a `Map` rebuilt in the component body. Aggregator polling keeps renders
coming
> - On a managed instance, the page pegs a CPU core and does not become
usable. Self-hosted instances skip the branch, and the tests used one or
two catalog entries, so nothing caught it
> - This pull request makes the check a set lookup, keeps the memo
inputs stable, and adds a test at shipped catalog scale plus a general
review rule for rendering large collections
> - The benefit is that `/apps` loads normally on managed instances
again, and similar regressions fail a test or get flagged in review

## Linked Issues or Issue Description

Refs #19328
Refs #19247

**What happened?**
On a managed instance, opening `/apps` pegs the CPU at 100%, and the
page is very slow or unresponsive.

**Steps to reproduce**
1. Open a managed instance (`useCloudInstance()?.managed === true`).
2. Go to `/apps`.
3. The tab holds a CPU core at 100%.

**Expected behavior**
The Connectors page renders in well under a second, and it does not
re-run catalog work on unrelated renders.

**Root cause**
In `ui/src/pages/apps/Browse.tsx`, `yieldsToAggregators()` calls
`hasAggregatorRoute()`. That function scans every aggregator catalog
entry with regex normalization. The `managedCatalog` loop calls
`yieldsToAggregators()` for every native row, for each of about 1,600
catalog apps. A Node benchmark with the shipped catalog data measured
about 214 s per pass on a managed instance and 40 ms self-hosted. The
`rows` memo depended on `upstreamAppsBySlug` and `gallery`, which were
rebuilt on every render, so the memo never reused its result.

## What Changed

- Index every aggregator identity once (`managedCatalogIdentities`), so
the aggregator route check is one `Set` lookup.
- Cache each slug's yield decision for the pass. Rows hold their final
connections and chat endpoints by then, so the answer cannot change.
- Normalize native row identities once before matching them against
catalog apps.
- Memoize `gallery`, the aggregator snapshots, `upstreamAppsBySlug` and
`managedCatalog`. A small `useShallowStableItems` helper keeps the
per-gateway `useQueries` data stable, so the `rows` memo runs only when
connector data changes.
- Match catalog apps to native rows through an identity-to-row index
instead of scanning the native rows for each catalog app. The earliest
row among an app's alias lookups is the row the old ordered scan found.
- Add a test that loads the shipped aggregator catalog and every native
connector in managed-instance mode, with a saved Composio gateway and
account. It counts `aggregatorAppIdentity` calls. The full load must
stay under 20× the catalog name count (today about 5×). An unchanged
refetch and a search must not rebuild the rows, and a changed account
must still appear. Above the limit the counter throws, so a regression
fails in milliseconds instead of blocking the test worker.
- Add `rules/ui-render-cost.mdc`, a general rule for `ui/src/**` code
that renders large collections: no nested scans, stable memo
dependencies, and a realistic-size test with a deterministic work
counter when a change touches such a path. Register it in
`.greptile/files.json`.

## Verification

- `cd ui && npx vitest run src/pages/apps/Browse.test.tsx`: 100 of 100
pass.
- With the previous `Browse.tsx` and the new test, the new test fails in
70 ms with "Connector catalog work exceeded its budget".
- Measured on the new code: a full managed-instance load makes 25,810
identity calls for 5,095 catalog names. A search makes 0.
- `node scripts/run-tsc.mjs -b` in `ui/`: no new errors. The only errors
are in `PluginOrganizationSwitcher`, because `@paperclipai/plugin-sdk`
is not built in a fresh worktree. They are unrelated to this change.
- `pnpm check:token-gates`: all gates clean.
- I did not check this in a browser on a managed instance. A local
self-hosted instance does not take that branch.

## Risks

- Low. The row-building logic gives the same results. The existing
managed-instance Slack, Asana and Google tests still pass.
- `useShallowStableItems` writes a ref during render. In the worst case,
a concurrent render does one extra memo pass. It cannot return stale
data, because it compares every item by identity.
- The new rule applies to all `ui/src/**` reviews. Its body limits it to
large or unbounded collections, to keep review noise low.

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

## Model Used

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

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 06:00:31 -06:00
DottaandPaperclip c0214826b3 fix(workspaces): preserve pending repository requests during import (#19439)
## Thinking Path

> - Paperclip manages agent work and its saved files.
> - An ephemeral web server needs remote workspace authority before the
old volume can detach.
> - A task can have a repository request that has never prepared a
checkout.
> - The offline importer currently treats that pending request as
unfinished file publication.
> - This blocks migration even when the target does not exist and there
is no saved commit, branch or failure.
> - This pull request preserves that request in the database and imports
the existing files separately.
> - The next authorized sandbox preparation can acquire the repository
through its existing owner.

## Linked Issues or Issue Description

**What happened?**

On Core `253bf071f0a496554b61fff897ad3f11afaf8766`, offline migration of
a volume-backed workspace fails with `repository_preparation_unresolved`
for an untouched pending repository request. The requested directory has
never existed. The request has no pinned commit, branch or failure.

**Expected behavior**

Preserve the pending request and all existing files. Do not label the
absent checkout as part of a saved archive. Keep partial publication and
active preparation blocked.

**Steps to reproduce**

Create a local task workspace. Add an unmaterialized pending repository
request. Run the canonical offline importer after stopping writers. The
unchanged importer rejects the request before preserving the workspace.

Related migration work: #19429. A GitHub search found no duplicate
pending-repository import fix or matching public issue.

## What Changed

- Admit an untouched pending request only at the existing offline writer
fence and after proving its target is absent.
- Keep the database request unchanged and exclude it from materialized
snapshot descriptors.
- Recheck file absence and source identity before publication. Reject a
request that becomes ready during capture.
- Cover cold filesystem loss, real sandbox-side Git acquisition, and
eleven blocked states. Document the preservation contract.

## Verification

- Red: the new real PostgreSQL/filesystem regression fails on the
unchanged importer with `repository_preparation_unresolved`.
- Green: both affected migration/lifecycle suites pass, **236 tests**.
The cold test deletes the predecessor directory, restores saved bytes,
and acquires the pending repository through the full admission owner,
checking ready state and the pinned commit. Real Git runs against a
local HTTP origin through an exact-URL redirect at the sandbox transport
boundary. It does not claim live Daytona or GitHub qualification.
- Full workspace typecheck and build pass at the final head. Both
affected suites pass **236 tests**, including the full admission owner
and eleven blocked states. All final-head CI checks pass: **54 success,
2 skipped, 1 neutral**. Greptile is **5/5**, with no unresolved review
threads.
- The full local test command failed in `recovers lost inline and check
responses without duplicate GitHub publications` in the chat integration
suite. That exact case passes in a fresh isolated invocation. Its cause
remains unclassified; this is not a full-local-suite pass claim. The
original failure is retained alongside the isolated result.
- No candidate code has been applied to the serving instance.

## Risks

- The exception applies only to offline migration and a pending request
with no target, commit, branch or failure. Unreadable paths, symlinks,
partial directories and active/repair states retain their blockers.
- Requests retain normal repository authorization and acquisition
behavior. This change neither fetches repositories during migration nor
marks requests ready.
- A changed materialized source aborts publication and leaves original
files intact.

## Model Used

OpenAI GPT-6 through Codex, with reasoning and tool use. The exact
deployment 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
- [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-06 05:49:16 -06:00
DottaandPaperclip 21d2a577bb feat: show and change routine responsible users (#19128)
## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - Routines create tasks for repeated work.
> - Each routine has a responsible person.
> - The Overview did not show this person or permit a change.
> - People need to see who will be responsible for automatic tasks.
> - This pull request adds a small name picker beside Default agent.
> - Authorized people can adopt a routine or transfer it to an active
company member.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The routine Overview and the routine update API.

**Subsystem affected**

UI, server, and shared contracts.

**Current behavior**

The routine stores a responsible user. The Overview does not show that
person. The update API cannot change that person.

**Proposed behavior**

Show Responsible user beside Default agent. Click the name to search
active company members. Save only the new person and the current
revision ID.

**Reason and benefit**

People can see who owns future automatic tasks. An authorized person can
adopt a routine without making a new routine.

**Breaking changes**

The detail response adds an optional actor-specific field. The update
API adds an optional responsible user ID. Existing requests keep their
current behavior.

Related: #17368 covers local Board ownership migration. This change adds
the routine control and explicit transfer.

## What Changed

- Show a quiet, searchable person picker beside Default agent.
- Explain that automatic tasks use this person. Manual runs use the
person who starts them.
- Permit human operators with `tasks:assign`, instance administrators,
and local operators to transfer responsibility.
- Check active company membership inside the update transaction.
- Save a revision and record the old and new person in the activity log.
- Keep existing task responsibility and ownership during revision
restore.
- Show errors for failed saves and stale revisions.
- Add tests for permissions, company boundaries, transfers, inheritance,
and picker states.
- Keep the picker disabled until saved data is refreshed. Refresh the
activity list after a transfer.
- Capture the actual previous person under the update row lock for the
activity record.
- Use shared query error handling and retain cached people during a
connection failure.

## Verification

- Focused checks cover 145 tests in 5 files. The first post-fix run
passed 144 tests. One existing Fireflies route test timed out during the
first import while the UI typecheck also ran. The same test passed in
the pre-fix run. The isolated route suite then passed all 21 tests with
no source change.
- The new tests reproduce slow detail refreshes, activity refresh, and
two concurrent transfers without a base revision. The concurrency test
uses PostgreSQL row locks.
- UI and server TypeScript checks passed at commit `1df9587eb`.
- The UI production build passed for the updated head.
- The branch includes master through `26391707f`.
- Design token gates, query-error policy, and Git whitespace checks
passed. The query-error policy reports zero findings.
- Local browser checks passed on desktop and mobile for the initial
feature commit. Search, selection, reload persistence, and revision
creation passed. No page errors or horizontal overflow were found. The
follow-up does not change the normal layout.
- Earlier full local checks could not finish: Cargo was absent, and
concurrent full test/build checks exceeded the 8 GiB memory limit. These
attempts are not passes.
- The first CI run failed on two error-rendering policy findings. That
failure is fixed locally. The three findings from the first Greptile
review are fixed and their threads are resolved.
- Greptile reviewed `1df9587eb2d3e034c2769b1f6955ac52323c95bb` and
scored it 5/5. It confirms all three prior findings are addressed. There
are no open review threads.
- [CI attempt
1](https://github.com/paperclipai/paperclip/actions/runs/42046369415/attempts/1)
passed 53 checks. The server 8/12 shard failed only because the existing
`history-http` runtime fixture did not become ready within 10 seconds
(99 refused connections). The fixture and runtime source are unchanged
from the merged master. That case passed locally on the same PR head
with no source change. Its full 75-test suite also passed locally. The
cause of the CI failure remains unknown.
- [CI attempt
2](https://github.com/paperclipai/paperclip/actions/runs/42046369415/attempts/2)
passed on `1df9587eb`: 54 successful checks, 3 skipped, and 1 neutral.
No failed or pending checks remained. The original fixture-readiness
failure remains recorded above.
- A later review found that viewers with an explicit `tasks:assign`
grant could see an editable picker even though writes were denied.
Commit `8b95ee707` now mirrors active-membership and viewer restrictions
in the GET capability flag. Instance-admin and local-operator behavior
is preserved.
- Three membership regressions failed on the incorrect editable flag
before the fix. After the fix, all 43 routine-route and company-access
cases passed (`--testTimeout 60000` accommodated this sandbox's cold
route imports; no repository timeout was changed). The first pre-fix run
timed out during the cold import in one case; the second pre-fix run
reproduced all three assertion failures.
- Layered server typechecking and whitespace checks passed after the
permission fix.
- [Fresh
CI](https://github.com/paperclipai/paperclip/actions/runs/42057752313)
passed on `8b95ee7075417527687b4a86cbaf63ba87408364` without a retry.
Greptile reviewed this exact head, scored it 5/5, and added no new
findings. All four review threads are resolved. The security status is
also successful.
- The separate Paperclip Review check is still queued because its
assigned review worker requires runtime recovery. This is the remaining
check before completion; the CI workflow itself passed.

## Risks

- A transfer changes the person used for future automatic tasks.
Existing tasks keep their current person.
- Only people can transfer responsibility. The server checks the
permission again on every write.
- A stale revision rejects the save. The user must reload before
retrying.
- No database migration is required.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, tool use, and code
execution. The exact runtime model ID and context window are not exposed
to this session.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task workspaces must survive replacement of the web server.
> - Workspace snapshots therefore preserve both files and portable Git
state.
> - A resolved merge can still await its merge commit.
> - The archive rejected this state, so an existing workspace could not
migrate.
> - This pull request saves and checks the complete supported merge
state.
> - The benefit is that migration preserves the user's choice to
continue or abort the merge.

## Linked Issues or Issue Description

**What happened?**

A legacy workspace contained a resolved merge with `MERGE_HEAD`,
`MERGE_MODE`, `MERGE_MSG`, `ORIG_HEAD`, and `AUTO_MERGE`. Its index had
no unresolved entries. The snapshot owner refused the operation. This
kept the original safe but blocked migration.

**Expected behavior**

Save this supported state without changing the source. Restore it
without the original repository, borrowed object store, or linked
worktree. Retain the merge parents, selected index, later edits, and
Git's normal safety checks.

**Steps to reproduce**

1. Merge two branches that conflict.
2. Resolve and stage the conflict. Leave the merge uncommitted.
3. Capture the workspace. The base implementation rejects the merge
marker.

Related prior work: #19310 preserves an inactive rebase bookmark. No
duplicate open merge-preservation PR was found. This is a workspace
preservation fix, not a new roadmap feature.

## What Changed

- Admit only resolved pending merges with valid, bounded metadata and
available objects.
- Retain and verify the merge mode, message, parents, original head,
automatic merge tree, and index.
- Verify copied metadata and object closure before publication. Keep the
before/after source checks.
- Keep unresolved conflicts, autostash, rerere, mixed operations,
malformed state, and unsafe links blocked.
- Test real ordinary, linked, and borrowed repositories through local
and serialized capture. Delete all original sources before continuing or
aborting the restored merge.
- Document the supported state and the remaining refusal cases.

## Verification

- Six new continuation cases fail against the unchanged base at the
pending-operation guard.
- The first candidate restored and continued the merges, but six abort
assertions failed. The fixture rewrote the worktree without updating
Git's index cache. It now first verifies that abort protects later
edits, uses normal `git restore` to discard those fixture edits, then
aborts. No archive assertion was removed.
- The final archive and environment suites pass all 98 tests at
`3c87d13d1b`, including linked-metadata protections and pending merges
in ordinary, linked, borrowed, shallow, partial, and SHA-256
repositories. All 12 local/serialized continuation cases pass after
source removal.
- Full repository build and typecheck pass at `1544cc4a90`. The build
first found a nullability error; an explicit rejection return fixed it.
- The first full local test attempt stopped before tests ran because
omitted package postinstall steps left PostgreSQL library links absent.
Direct `initdb --version` reproduced the missing-library failure.
Restoring the package links made that probe pass; the full test rerun is
active.
- The initial typecheck also found missing excluded-plugin SDK links
after the same install. Running the repository postinstall link helper
corrected this, and the full typecheck now passes.
- All current-head CI gates pass at `3c87d13d1b`: 54 success, 2 skipped,
1 neutral. Fresh Greptile is 5/5 and no review threads remain open.
- Review requested shallow and SHA-256 coverage. The current commit adds
both plus partial-clone coverage; all 98 archive tests and the full
server typecheck pass.
- All six local chat shards pass. The monolithic local general-suite run
reports one viewer-permission test failure and remains active. That
exact test passes alone, and its full 505-test file also passes alone.
The original failure cause is unresolved; this is not a full local-suite
PASS. The complete current-head CI suite is green.

## Risks

- This widens capture eligibility only for the checked resolved-merge
state. No migration commits or aborts the source operation.
- A missing object or changed metadata still blocks publication and
retains the source.
- Unresolved merges and other operation formats remain unsupported.
Their preservation guard is unchanged.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools, and code
execution. The runtime does not expose a more specific 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 the available 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 relevant issue
template
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal task
id
- [x] I have run the affected tests locally and they pass; full-suite
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 the 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 merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
canary/v2026.1106.0-canary.11
2026-11-06 04:20:32 -06:00
DottaandClaude Opus 5.5 8c578668b8 fix(heartbeat): start new queued runs promptly after restart and name the wait (#19431)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The heartbeat queue starts queued agent turns. Restarts and deploys
happen often.
> - After a restart, new queued turns waited minutes before they
started. The task showed only "Queued", with no reason.
> - The cause is startup order. The queued-run dispatch worker registers
only after many unrelated first passes finish one after another: tool
reviews, delivery workers, the terminal workspace sweep, native restart
recovery, and residual and issue recovery.
> - The server binds its listener before this recovery, and
`/api/health` answers during it. A deployment can route traffic to the
new server for minutes before queued work starts.
> - This pull request starts queued turns that never executed as soon as
plugin workers and adapters are loaded. It also names the wait while the
server starts, recovers, or shuts down.
> - The benefit is that tasks resume quickly after a restart and show a
visible reason while they wait.

## Linked Issues or Issue Description

**What happened?**
On a hosted instance, the server restarted twice for deploys. Both
times, queued runs on idle agents started about 3.5 to 4 minutes after
the restart. Before the restart, the runs also waited about 5 minutes
while the deploy drained the old server. During the wait after the
restart, the execution presentation showed `phase: queued`, `label:
"Queued"`, `cause: null`.

**Expected behavior**
Queued work resumes automatically and quickly after a restart. While it
waits, the task shows a reason.

**Steps to reproduce**
1. Start a task drain (`POST /api/instance/task-drain`). Assign issues
to idle agents. The runs stay queued.
2. Stop the server (SIGTERM) and start it again.
3. Make any startup first pass slow (a large instance does this
naturally). Queued runs start only after every earlier startup pass
finishes.

**Paperclip version or commit**
master at 7e3caa7bac.

**Deployment mode**
Hosted Railway deployment with fleet deploy drain. Reproduced locally
with embedded PostgreSQL and an injected 20-second startup step.

## What Changed

- `heartbeat/queue.ts`: add `resumeFreshQueuedRunsAtStartup`. It starts
queued runs on agents with no run left `running` and no queued retry or
continuation (`started_at`, `retry_of_run_id`,
`process_loss_retry_count`, `continuation_attempt`). Every run still
passes the ordinary per-agent claim and dispatch gates, and scheduling
suppression (including the task drain) still applies.
- `heartbeat.ts`: expose it. Count it as a pending wake, like
`resumeQueuedRunsAfterDrain`, so drain status and upgrade readiness see
it.
- `index.ts`: run that pass at startup after plugin workers and external
adapters load. It does not wait for the unrelated startup first passes.
It skips while a drain, warm standby, or shutdown is active. Log when it
completes and when the full queued-run pass completes, with process
uptime, so a hosted restart shows both timings.
- `execution-projection.ts`: while the server is `starting`,
`recovering`, or `draining` (shutdown), a queued run shows **Waiting for
server restart** (cause `server_restarting`) with a next action. A task
drain keeps its **Waiting for maintenance** reason.
- `doc/execution-semantics.md`: describe the startup fast path and the
new presentation.
- The full queued-run pass, its position after native restart recovery,
and the drain behavior are unchanged.

## Verification

- Red, then green, local restart with a 20-second injected startup step
(embedded PostgreSQL, three process agents, runs queued under a task
drain, SIGTERM, restart):
- Before: the listener bound at 09:41:50.6. Runs started at 09:42:10.8,
20.2 s later, after the slow step ended.
- After: runs started 0.2 to 0.3 s after the listener bound, while the
slow step still ran. Each original run executed once.
- During the before run, `GET /api/issues/:id/execution` showed `Waiting
for server restart` / `server_restarting` until dispatch.
- Red, then green: `execution-projection.test.ts` restart-phase cases
failed on the old code (`label: "Queued"`, `cause: null`) and pass now.
- New `heartbeat-startup-fresh-dispatch.test.ts` (real PostgreSQL): a
fresh run starts; an agent with a `running` row from the previous
process and an agent with a queued retry stay queued; nothing starts
while a task drain is active.
- `npx vitest run` for the new test, `heartbeat-task-drain*.test.ts`,
`execution-projection*.test.ts`, `startup-recovery-timing.test.ts`,
`heartbeat/queue.test.ts`, `liveness.test.ts`: 132 passed.
- `npx tsc --noEmit -p server/tsconfig.json` passed.

## Risks

- New queued runs can start before native restart recovery finishes. The
fast path excludes any agent with a `running` row or a queued retry or
continuation, and the claim checks still enforce issue ownership,
predecessor cleanup, budgets, pauses, and concurrency. A new wakeup
during startup already starts runs at this point, so this adds no new
class of early execution.
- The drain is unchanged. It still holds new runs while the deploy waits
for in-flight runs (the Cloud wait budget is at most 5 minutes). This PR
does not change Cloud.
- The new label depends on the process-local startup phase. It shows
only on the process that is restarting.

> 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, via Claude Code with
tool use (shell, file edits, local server runs). Extended reasoning
enabled.

## Checklist

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

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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
canary/v2026.1106.0-canary.10
2026-11-06 04:00:42 -06:00
DottaandClaude Opus 5.5 140fcf98ed fix(retry): name a declined task Retry and let a new click replace it (#19426)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A task interrupted by a restart or deploy must resume automatically,
or offer a Retry that works on first use
> - A board Retry saves an intent first. Dispatch then rechecks the
requester's authority and saved source before it starts a run
> - When that recheck refused the intent, dispatch returned a bare
"deferred". The intent stayed saved with no reason, and the task still
showed Retry
> - Each later click with a new key joined that same intent, so Retry
returned 202 and never started a run
> - This pull request names the refusal on the intent and lets a new
click replace an intent that can never start
> - The benefit is that a Retry button that is shown starts one run, or
explains why it cannot

## Linked Issues or Issue Description

**What happened?**
A native task in a managed sandbox lost its runner twice. Saved-work
recovery recovered the files. The task showed "Saved work recovered.
Retry or send a message to continue." and `canRetry: true`. A Retry
click returned 202 and logged `issue.retry_requested`, but no run
started. A second click with a new idempotency key returned the same
request ID and `state: failed`. No run started.

**Expected behavior**
If Retry is offered, a click starts exactly one run. If the Retry cannot
start, the task names the reason, and a later click is a fresh attempt.
A Retry accepted during the pre-deploy drain starts after the restart.

**Steps to reproduce**
1. Recover the files of a failed native run so the hold offers Retry.
2. Save a Retry intent whose dispatch-time authority recheck fails (for
example, the requester lost access).
3. Dispatch it: no run starts, and the intent has no wait reason.
4. Click Retry again with a new key: the response returns the first
request ID, and no run starts.

**Paperclip version or commit**
Reproduced on `b400914345` (staging served `4d49a31286`; the affected
code is the same).

Related: #19399 (automatic Retry after a lost runner), #19422.

## What Changed

- `heartbeat/queue.ts`: when dispatch declines a task Retry because the
requester can no longer change the task, or its saved board message
changed, record a named wait (`retry_authority_denied`,
`retry_source_changed`) and log it. Before, it returned "deferred" with
no record.
- `task-retry.ts`: a new click with a new key replaces an unadmitted
intent that dispatch declined for its requester's access
(`retry_authority_denied`). The old intent becomes `skipped` /
`task_retry_replaced`, and activity records `replacedRequestId`. Waits
on shared state (cleanup, recovery, an active run, preservation) still
coalesce. The restart hook never replaces a user's click. Chat-owned
intents keep their own owner.
- `task-interruption-presentation.ts`: fixed public messages for the two
new wait reasons. `retry_authority_denied` keeps Retry, because a new
click is a fresh attempt. `retry_source_changed` is terminal: an edited
saved message cannot be replayed, so Retry is hidden and the user sends
a new message.
- `doc/execution-semantics.md`: document the rule.

## Verification

- New regressions in `server/src/services/workspace-recovery.test.ts`
use the real recovery, Retry and dispatch services on embedded Postgres.
The fixture runs the automatic Retry from a lost runner, then that
Retry's own run loses its runner, and recovery saves its files:
- `starts exactly one run when Retry is pressed after the automatic
Retry's own run lost its runner` (also checks that a replayed dispatch
and a second click start no second run).
- `does not let a Retry that could not start swallow a later Retry`: red
before the fix in two steps. With no fix, the declined intent has no
wait reason. With only the wait fix, the second click returned the first
request ID. Green after the fix: one run, bound to the second click.
- `dispatches a Retry accepted during the pre-deploy drain once the
restarted server resumes`: green before and after. The drain does not
lose a saved Retry.
- `npx vitest run src/services/workspace-recovery.test.ts
src/services/task-retry.test.ts
src/services/task-interruption-presentation.test.ts
src/services/quarantined-task-retry.test.ts
src/services/explicit-native-continuation.test.ts`: 892 passed.
- Not verified on staging. After deploy, an intent that dispatch refuses
shows a named `retryWait` reason.

## Risks

- Low risk. Only intents that dispatch declined for their requester's
access are replaced. All other coalescing stays.
- A replaced intent's late dispatch finds it `skipped` and starts
nothing, so a double click still starts at most one run.
- The exact gate that held the staging intent is not confirmed from
records. If it was a shared-state wait, the new code names it only if it
is one of the two reasons above.

## Model Used

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

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 03:52:21 -06:00
DottaandPaperclip 8836a71113 test(testops): pin calibrated secret acknowledgment source (#19428)
## Thinking Path

> - Paperclip lets people manage agents and inspect saved work.
> - Protected chat evaluations use a fixed source and reviewed case
declarations.
> - The last four-case run produced one verified PASS and three original
FAILURES.
> - Review found narrow acknowledgment and partial-installation
assertion gaps.
> - Their local repairs preserve the original user goals and safety
evidence.
> - This PR pins the reviewed source for one bounded secret-pair
measurement.
> - A final uniform-ten measurement on that same source is still
required.

## Linked Issues or Issue Description

Refs: #19423, #19325, #19416, #19093, #19084, #19112.
The core and full-catalog PRs remain open and unmerged.

**What happened?**

Launch 3310 retains one original PASS and three original FAILURES. Case
53 used the
same undismissed form correctly. Company rejected a valid ordered
save/ACK card
before human entry. Personal rejected a canonical wake-on-accept/After
Saving
confirmation before private entry. Team rejected a truthful qualified
required-skill
partial-installation report. No original result is regraded.

**What did you expect to happen?**

Admit supported saved cards and truthful partial reports while
preserving actual
current-human save, immutable policy, acceptance, audit, continuation,
private
ownership, delivery and saved-resource evidence. Declined or canceled
actions and
false recovery claims must still fail.

**Steps to reproduce**

Inspect the original selected journeys from source
`5f74dc6545ea66ac71255ac8e6010f5215a1f257` against their actual cards,
responses and
saved state. The local repairs calibrate the assertions; they do not
establish that
the next native secret journeys will finish within their original
clocks.

## What Changed

- Pin immutable source `d68c00a3d78e5cd56b77aedb31f1e8a0f6d6f84e` and
its exact
242-row descriptor snapshot. Update only Company v17, Personal v16 and
Team v10
oracle-version leaves in the 244-row manifest and retained repair
metadata.
- Append the measured limits after the exact historical condition
prefix. Keep
  every runtime condition and dependency list unchanged.
- Keep all 244 YAML identities, queries, contexts, outcomes,
permissions,
human/native bounds, clocks and model unchanged. Cap 2 and zero retries
stay fixed.
- Plan only cases 578 and 580 next, with original 480,000 ms and 660,000
ms cells.
Team calibration is not a new native PASS. Final uniform ten remains
required.

## Verification

- All 40 local pin controls passed:
`node --test
tests/runner-e2e/chat-full-catalog-testops-manifest.test.mjs
tests/runner-e2e/chat-full-catalog-testops-adapter.test.mjs
tests/runner-e2e/chat-full-catalog-testops-reconcile.test.mjs
tests/runner-e2e/chat-completion-testops-cli.test.mjs`.
- The exact source passed 352 fresh controls across ten files. These
cover current
confirmation, independent human accounting/history, contract, Team and
all
242 declarations with zero fixture callbacks. Full runner E2E types
passed.
- SDK506 synchronization passed unchanged with registry digest
  `efa70621e9fb42aa185f7ee616a1a726aa3eccb367e67e28817cedf26ef2ade8`.
  No install, native build, SDK build or local model call was needed.
- Fresh master `7e3caa7bac3f2c1a560158b28ec42381a153dfff` matches the
accepted ten
protected helpers, five source-parity files, 67 dependency-graph inputs
and
report-lock policy. No private helper imports ran during this
preparation.
- Local qualification used Node `v26.4.0`. An initial pin test import
failed before
the qualified dependency link existed; that setup failure remains
retained.
- Remote ref `codex/chat-secret-ack-v17-qualified-d68c00a3-20261106` was
read back at
  the exact source above. Descriptor SHA256 is
  `3b62889da60db1c8daafcc23f0fefa7cfcda165224ba90214b2ac391215d3be0`.
- New-head CI and reviews remain pending at PR creation. No paid
evaluation ran here.

## Risks

This source is unmeasured. Company used 344,242 ms for its initial
native turn and had
only 86,070 ms left at the false gate. Measured API handlers do not
explain that time;
no avoidable latency defect or full-flow fit has been proved. The gate
repair does
not create a native completion or private-save claim. Personal must
preserve the
actual canonical card policy through real save, acceptance and
independent evidence.
Team's local assertion calibration does not regrade result 5242 or
qualify runtime
migration. Every saved-state, actor, privacy and delivery check remains
mandatory.

Case 53's verified 241,214 ms / 300,000 ms journey is carried on
unchanged code, not claimed
as a new measurement or reliability result. All older failures remain
retained.
Secret journeys require independent semantic safety review before pass
credit.
Core and full-catalog PRs stay unmerged; the 196/244 gate remains. Only
the secret
pair is planned next, followed by the required uniform ten on the same
source.
No merge override or retry is requested by this PR.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with reasoning, tool use and code
execution.
The context-window limit is not exposed in this session. The native eval
model
remains `openrouter/deepseek/deepseek-v4.1-flash-1017` and was not run
locally.

## 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-06 03:47:43 -06:00
DottaandClaude Opus 5.5 7e3caa7bac fix(tree-control): resume work a pause gate refused after release (#19421)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board can pause an issue tree. A pause stops work. Resume or
release must continue that work once.
> - PR #19394 made release re-dispatch a run that a pause Stop
cancelled.
> - That rule covers only a run that an operator Stop ended and that the
hold snapshot named.
> - A scheduled retry that comes due during a pause is not stopped. The
dispatch gate cancels it with `issue_paused`. The release did not see it
as owed work, so the task stalled with no run.
> - This pull request makes every gate that refuses work for a pause
record the covering hold. Release then re-dispatches that work exactly
once.
> - The benefit is that a paused task with a parked retry or a queued
run continues after release, with no manual wake.

## Linked Issues or Issue Description

Refs #19394

**What happened?**
A child task had a `workspace_busy` scheduled retry. The board paused
the parent tree. The retry came due during the pause. The
scheduled-retry gate cancelled it with `issue_paused` ("Scheduled retry
suppressed because the issue is held by an active subtree pause hold").
The board released the hold. Four minutes later the child was still
`in_progress` with no run, no execution blocker, and no new activity.

**Expected behavior**
Release (or `mode: "resume"`) re-dispatches the refused work exactly
once. No reconciliation and no user message are necessary.
Stranded-issue recovery does not add a second run.

**Steps to reproduce**
1. Give an assigned child task a parked `workspace_busy` scheduled
retry.
2. Pause the parent tree.
3. Let the retry come due while the pause is active. The gate cancels it
with `issue_paused`.
4. Release the pause. No run starts for the child.

**Paperclip version or commit**
master at 4d49a31286 (includes #19394).

**Agent adapter(s) involved**
Not adapter-specific (core bug).

## What Changed

- `issue-tree-control.ts`: add `ISSUE_PAUSED_ERROR_CODE` and
`pauseSuppressionResult()`. A gate records `treeHoldSuppression: {
holdId, rootIssueId }` on a run that it refused for a pause.
- `findOwedPauseResume` now treats the latest run as owed when it is an
`issue_paused` cancellation under a pause hold. It uses the recorded
hold. For an unmarked run (a scheduled retry cancelled before this
change), it uses a snapshot pause that was active at cancellation. The
operator Stop rule does not change.
- Scheduled-retry gate (`run-dispatch/adapters/postgres.ts`,
`cancelSuppressedRetryInTx`): locks the pause row `FOR SHARE` and
records the covering hold. A concurrent release either sees the refusal,
or the retry sees no pause and runs normally.
- Queued-run claim gate (`heartbeat/queue.ts`): cancels with
`issue_paused` and the hold marker, after the agent start lock is
released. Before, the cancel re-entered that lock from inside it, and
the agent's queue stalled until the 30-second stale-lock timeout. After
the refusal is saved, the gate re-checks the hold and resumes the task
itself if release already finished (`resumeIfPauseAlreadyReleased`).
- Release (`issue-tree-resume.ts`, route): revisits the snapshot members
and every task with a run refused for that hold, also when `wakeAgents`
is set. This covers tasks created under the tree after the snapshot and
tasks moved out before release.
- `packages/db`: partial index
`heartbeat_runs_tree_hold_suppression_idx` (migration 0436, `CREATE
INDEX CONCURRENTLY`, registered as a top-level migration) for that
lookup.
- Docs: `doc/execution-semantics.md` (gate-refused work, race handling,
non-assignee and pre-fix scope) and `doc/DATABASE.md` (index).

## Verification

- Red before the fix, green after: `pnpm --filter @paperclipai/server
exec vitest run src/__tests__/issue-tree-pause-resume-run.test.ts` (21
tests).
- "resumes a parked retry the pause suppressed exactly once after
release / resume / legacy": before, `expected [] to have a length of 1`
(no resumed run). After, one `issue_tree_resumed` run with
`interruptedRunId` = the suppressed retry, no execution blocker, and the
task finished. `reconcileStrandedAssignedIssues` added no run during the
hold or after release.
- "resumes a queued run the claim gate refused during the hold exactly
once": before, the test timed out at 15 s (start-lock re-entry). After,
the run is cancelled with `issue_paused` and the hold marker, and
release re-dispatches it once.
- "resumes a refused retry on a task moved out of the paused tree before
release" and "... on a task outside the snapshot when release also wakes
members": review findings, one resume each.
- "leaves a due retry parked when the pause is released while the gate
refuses it": the test waits until the gate blocks on an uncommitted
release. The retry is not cancelled and runs once. Without the `FOR
SHARE` lock the gate does not wait and the test fails.
- "resumes work a gate refused after the release that should have
resumed it had finished": the late-refusal re-check resumes once, and a
repeated check does not dispatch again.
- "does not resume a paused-code cancellation that no pause hold
covered": negative case.
- Related suites pass locally: `src/__tests__/issue-tree*`,
`src/modules/run-dispatch`, `src/services/heartbeat/queue.test.ts`,
`heartbeat-dependency-scheduling`, `heartbeat-workspace-busy`, and
`packages/db` `client.test.ts`.
- I did not run a live staging check here. Staging APIs are not
reachable from this environment.

## Risks

- Migration 0436 adds a partial index on `heartbeat_runs`. It is built
concurrently and covers only cancelled runs with the marker, so it is
small.
- The scheduled-retry gate now takes a `FOR SHARE` row lock on the pause
hold before it cancels. Release updates that row, so the two serialize
briefly. Release does not lock issues or runs, so the lock order cannot
deadlock with the gate's issue-then-run locks.
- The queued-run claim gate now records `issue_paused` in place of the
generic `cancelled`. Stranded recovery already classifies `issue_paused`
as non-retryable. The pause resume owns that work, so recovery does not
add a second run.
- Scope limits (documented): a non-assignee run refused by a pause is
not replayed, the same as non-assignee wakes that admission drops during
a hold. Among pre-fix rows, only scheduled retries carry `issue_paused`,
so only those fall back to the snapshot. A pre-fix row whose hold is
already released does not self-recover; the staging task that showed
this bug needs one manual wake, or a new pause and release.

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

## Model Used

- Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, used through
Claude Code with tool use (shell, file edits, test execution).

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
canary/v2026.1106.0-canary.9
2026-11-06 03:03:15 -06:00
Devin FoleyandClaude Opus 5.5 2c7ee84221 fix(server): stop reporting work cancelled by a disconnected client (#19037)
A route that aborts its storage read when the response closes rejects with
AbortError after the client has gone. The error handler reported that
cancellation as a server crash. Skip the error sinks for an AbortError when
the response was closed before it finished; report it otherwise.

Fixes PAPERCLIP-BACKEND-5A

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 00:39:27 -08:00
DottaandPaperclip b400914345 test(testops): pin calibrated four-journey fixture source (#19423)
## Thinking Path

> - Paperclip lets people manage AI agents and inspect saved work.
> - The protected chat evaluation uses a fixed source and a reviewed
case inventory.
> - The last four selected journeys all reported FAILED. Their originals
remain unchanged.
> - Review found one fixture attention-state mutation and three narrow
observation or assertion errors.
> - The source repairs passed model-free controls, but their next native
measurement has not run.
> - This PR pins that reviewed source without merging its core or
full-catalog overlays.
> - The benefit is one bounded measurement with honest conditions and
unchanged safety evidence.

## Linked Issues or Issue Description

Refs: #19418, #19325, #19416. The last two core PRs remain open and
unmerged.
The older open source pin #18740 targets a different historical
candidate.

**What happened?**

The multipart fixture dismissed a saved form before asking for its
walkthrough.
The Company guard rejected a bounded secure-page save instruction. The
Personal
route prefilter rejected a valid task citation beside the setup hint.
The Team
assertion rejected a truthful current unattached-skill report. Launch
3277 retains
zero reported passes from four cases; none is regraded here.

**What did you expect to happen?**

Measure the same user goals with the original form still undismissed,
supported
secure-entry directions, independently validated task links, and
truthful partial
installation reports. Require all real saved-state, actor and privacy
evidence.

**Steps to reproduce**

Run the protected selected journeys from source
`58449c4ecd7fd374f21f175411ef3249f55c18a3`. Inspect each original
failure against
its user-visible result and saved state. The new source is a local
calibration,
not a claim that these native journeys now pass.

## What Changed

- Pin immutable source `5f74dc6545ea66ac71255ac8e6010f5215a1f257`.
- Copy its exact 242-row descriptor snapshot. Update eight
oracle-version leaves
in the 244-row manifest and retained repair metadata. Dependency lists
stay fixed.
- Update the expected target assertion and append the new limits after
the exact
  historical condition prefix. Retain all existing runtime conditions.
- Keep all 244 YAML identities, queries, outcomes, human/native bounds,
clocks,
  model, cap 2, zero retries and trusted private interfaces unchanged.

## Verification

- All 40 local manifest, adapter, reconciliation and CLI admission
controls passed:
`node --test
tests/runner-e2e/chat-full-catalog-testops-manifest.test.mjs
tests/runner-e2e/chat-full-catalog-testops-adapter.test.mjs
tests/runner-e2e/chat-full-catalog-testops-reconcile.test.mjs
tests/runner-e2e/chat-completion-testops-cli.test.mjs`.
- The exact source passed 375 affected controls across 20 files,
including explicit
installed-browser canaries and the 242-declaration gate with zero
fixture callbacks.
Full runner E2E typecheck passed. These component results are not native
passes.
- The 506-input SDK guard passed without refresh. The launcher, ten
protected
helpers, five source/controller parity files and 67 dependency-graph
inputs match
  the accepted baseline. No install or build was needed.
- Local qualification used Node `v26.4.0`. Earlier alias and
declaration-output setup
failures remain retained. The normal remote CI configuration is
unchanged.
- Verify remote ref
`codex/chat-four-fixture-v16-qualified-5f74dc65-20261106` resolves
  to the source above. The snapshot SHA256 is
  `913090f18cc4e0f7d997b99586618080f1b5c847b2b5d74984d3c480baf5318d`.
- New-head CI and reviews must finish before merge. No paid evaluation
ran for this PR.

## Risks

The source remains unmeasured. Removing the fixture dismissal does not
guarantee
that the agent will reuse the form: a later human comment still makes it
historical,
but it remains answerable and the latest human direction governs. The
same-form
fidelity check stays strict. The Company journey did not reach human
secure entry;
its later save, approval, binding and consumer delivery remain unproved.
Personal
setup hints are not trusted URLs; actual navigation and private
ownership checks
remain required. Team calibration gives no runtime-migration credit.

All original failures remain retained. Secret journeys still require
independent
semantic safety review before pass credit. No native reliability or
latency claim
is made. The core and full-catalog PRs remain unmerged; the 196/244 gate
stays fixed.
A later full-ten run is a separate measurement. Required CI and genuine
current-head
review are pending at creation; no merge override is requested.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with reasoning, tool use and code
execution.
The context-window limit is not exposed in this session. Native eval
configuration
remains `openrouter/deepseek/deepseek-v4.1-flash-1017` and was not run
locally.

## 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-06 02:33:18 -06:00
Devin FoleyandPaperclip bf53060904 fix(recovery): continue saved input after verified workspace copy-back (#19422)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native execution preserves accepted results and task files after a
failure.
> - A later user message can request a new turn after the old execution
stops.
> - File recovery can finish by copying files back or by retaining a
verified archive.
> - The retained-result gate accepted the archive proof but missed a
verified copy-back receipt.
> - This change accepts that existing receipt for terminal failures and
preserves execution authority and prior results.

## Linked Issues or Issue Description

**What happened?**
A task keeps a user message queued with `finalizing_saved_result` after
its old native coordinator reached `terminal_failure`. The accepted
result and a successful, exact `workspace_recovery` copy-back receipt
are saved. The gate only checks the alternate
`workspace_recovery_snapshot` receipt. An older failed result can also
keep a newer failed turn's saved input blocked.

**Expected behavior**
A new user message or deliberate task Retry can continue from verified
recovered files after the old execution stops. Preserve the old result
and unknown action outcomes. Keep an active finalizer or missing stop
evidence blocked.

**Steps to reproduce**
1. Retain an accepted result when a native run reaches terminal failure.
2. Complete host copy-back with the exact run, allocation, descriptor
and final file digest.
3. Save a new user message or request task Retry.
4. Observe that admission still reports `finalizing_saved_result`.

**Paperclip version or commit**
Reproduced against `4d49a3128613b282fb344272ed7ca15f98dea2ac`.

**Deployment mode**
Authenticated native execution with retained workspace recovery. Local
regressions use disposable PostgreSQL and the real heartbeat queue. They
start no model provider or sandbox.

Related work: #19355 covers saved input after Stop. #18267 concerns
manual reconciliation of duplicate legacy holds; it excludes workspace
and system recovery and does not fix this receipt gate. I searched open
PRs and issues before starting this change.

## What Changed

- Reuse the existing host-verified repair receipt when an accepted
result belongs to a coordinator in `terminal_failure`.
- Retain the separate checks for active controllers, environment
termination, live executions, questions, approvals and current user
authority.
- Add message, saved-message and task Retry regressions, plus concurrent
queue delivery and invalid-receipt cases.
- Document this continuation path. Add no schema, API or background
worker.

## Verification

- All 483 tests pass across explicit continuation, task Retry,
quarantined Retry and finalized recovery migration suites.
- The final focused fixture has 17 cases. All pass with the change. With
the original production gate restored, all four positive cases fail and
all 13 safety cases pass.
- Negative cases cover missing, unfinished and mismatched receipts,
another task or source, unrecovered files, a live finalizer, an active
controller, missing remote stop and active execution.
- The real heartbeat path delivers one saved message under concurrent
recovery calls. Later recovery calls create no second turn. The old
accepted result and recovery budget stay unchanged; unknown outcomes
stay unknown.
- The first delivery fixture omitted the newer failed source needed by
the recovery scanner. I corrected the fixture to preserve that history
and repeated the unchanged-code comparison. Earlier failure logs remain
in the verification record.
- `pnpm -r typecheck`: passed.
- `node scripts/check-module-boundaries.mjs` and `git diff --check`:
passed.
- `pnpm build`: passed.
- Full local `pnpm test:run`: running with a 20-minute bound; no
full-suite pass is claimed. All hosted CI checks passed. Greptile scored
the exact head `d04fb98892fd77853ad03e6b44cd612e805abc13` 5/5 with no
findings or review threads.
- Hosted delivery of the already saved input still requires a qualified
staging build containing this change.

## Risks

The new path requires the existing exact repair receipt and a terminal
failure. A coordinator still accepting or committing a result keeps the
previous hold. Stop, owner and live-run checks still run before
admission. The old result is neither applied nor replayed. Receipt
verification adds bounded indexed reads only on this retained-result
failure path. No migration or provider configuration changes are
required.

## Model Used

OpenAI GPT-6.1 Sol (`gpt-6.1-sol`) through Codex in T3 Code, with xhigh
reasoning. The context-window size is not exposed. Used code editing,
tool calls and local test execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used, including the available runtime
details
- [x] I have checked ROADMAP.md and confirmed this is a scoped bug fix
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have described the issue in this PR using the bug-report fields
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal task
id
- [x] I have run the affected tests locally and they pass
- [x] I have added regression tests and updated relevant documentation
- [x] I have considered and documented the risks
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open findings
- [x] I will address all review findings before requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
canary/v2026.1106.0-canary.8
2026-11-06 00:15:30 -08:00
Devin FoleyandPaperclip 11bc490cda fix(recovery): keep a stopped copy-back's files instead of holding the workspace (#19385)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A native run can have its result accepted before the copy-back of
its sandbox files finishes. If the copy-back fails, it retries.
> - A board Stop during those retries ends the run. It sets the run's
finalization to `terminal_failure` / `native_retry_cancelled`, and the
result stays accepted.
> - After that, nothing exports the files. The finalization worker skips
terminal rows, and cancelled-workspace preservation skipped any run with
an accepted result.
> - The task workspace stays dirty and bound to the stopped run. Every
later run on the task waits for the workspace, then fails setup with the
unfinished-writer error.
> - This pull request lets cancelled-workspace preservation also admit
an accepted result whose copy-back a Stop ended.
> - The benefit is that stopping a stuck copy-back saves its files and
frees the task, like stopping any other run.

## Linked Issues or Issue Description

Refs #16700, #17677, #18820, #19304 and #19305 (cancelled-workspace
preservation). This pull request adds the accepted-result case to that
path and keeps its other checks unchanged.

**What happened?**

On a hosted instance, a native run's result was accepted, but its
copy-back kept failing and retrying. A board operator pressed Stop to
unblock the task. The run became `cancelled`, its sandbox was stopped
and kept, and its finalization moved to `terminal_failure` /
`native_retry_cancelled` with the result still accepted.

The next run on the task was cancelled with "Shared execution workspace
is busy: run … ended, but its workspace save has not settled". About 24
automatic retries followed over an hour. The last one failed with "The
workspace still has an unfinished writer or save operation." After that
nothing ran: the task stayed `in_progress` with no run and no recovery
action. A Retry or a new comment would hit the same block.

The workspace stayed `dirty: true` with the stopped run as its writer,
and no `workspaceRestoreRecovery` was ever recorded. The reason:
`preserveCancelledNativeWorkspaces` requires `resultId IS NULL`, and
`registerCancelledWorkspacePreservationWork` and
`preserveNativeFailedWorkspace` both return early when `resultId` is
set.

**Expected behavior**

Stopping a run during its copy-back retries keeps its files, as stopping
any other run does. Paperclip's workspace recovery saves them and
releases the workspace. Nothing commits or replays the stopped result.

**Steps to reproduce**

1. Accept a native result for a run whose copy-back is in
`retryable_failure`.
2. Stop the run with the board Stop. The finalization becomes
`terminal_failure` / `native_retry_cancelled`.
3. Start another run on the task. It waits for the workspace and then
fails setup. No save of the stopped run's files is ever recorded.

The new database tests run steps 1 and 2 against PostgreSQL and check
that the save is recorded.

**Paperclip version or commit**

`master` at `9ceaec6f97e`.

**Deployment mode**

Any deployment that uses remote (sandbox) task workspaces. Seen on a
cloud-managed instance.

## What Changed

-
`server/src/services/native-runtime/native-failed-workspace-recovery.ts`:
cancelled-workspace preservation now also admits an accepted result when
the finalization is `terminal_failure` with `native_retry_cancelled`.
That is the state a Stop leaves after ending copy-back retries. All
three entry points use the same rule:
  - the startup scan `preserveCancelledNativeWorkspaces`;
  - the worker `registerCancelledWorkspacePreservationWork`;
- `preserveNativeFailedWorkspace`, only when it is called with
`requireCancelledStop`.

Preservation still requires everything it required before: a cancelled
run, an acknowledged Stop, no live finalization owner or controller
lease, a prepared workspace reference, and the exact retained source.
- An accepted result with any other failure code is still skipped; those
runs keep their own export repair path. Failed (not cancelled) runs are
unchanged.
- The accepted result stays recorded and uncommitted. Preservation does
not change the task's status or queue a run.
- `doc/execution-semantics.md`: describe the stopped copy-back case in
the paragraph on writers stopped mid-run.
- `native-cancelled-workspace-preservation.test.ts`: move the
accepted-result fixture into an `acceptResult` helper, and add a test
with three cases.

## Verification

- Failing first. The new test "preserves files of an accepted result
whose copy-back was stopped" has three cases: worker, startup scan, and
a sandbox that the Stop already stopped and kept, through the real
`prepareSandboxStopAndRetain` and `settleStopOnlyCleanup`. All three
fail on `master`, with no `workspaceRestoreFailure` recorded, and pass
with this change.
- Each case checks that:
- the save obligation and recovery action are recorded for the exact
lease;
  - `inspectWorkspaceRecoverySource` finds one source;
- the finalization keeps its `resultId`, `terminal_failure` and
`native_retry_cancelled`;
- the workspace stays held by the stopped writer until the save
publishes;
  - no run or wake is created, and the task status is unchanged.
- The existing "does not admit preservation for accepted-result" case
still passes. It uses an accepted result with a different failure code.
- `cd server && npx vitest run
src/services/native-runtime/native-cancelled-workspace-preservation.test.ts`:
44 passed.
- Related suites, 10 files and 854 passed:
- `native-failed-workspace-recovery`, `workspace-recovery`,
`heartbeat/run-control`
  - `native-cancellation-request` (unit and integration)
- `heartbeat-remote-workspace-busy`, `native-workspace-export-retry`,
`native-workspace-reconstruction`
  - `native-stop-accounting`, `task-workspace-binding`
- `cd server && npx tsc --noEmit -p tsconfig.json`: pass.
- Not run end to end on a live instance. Diagnosed from that instance's
run, finalization, lease, workspace and recovery-action rows.

## Risks

- On deploy, the startup scan and the worker will pick up existing
cancelled runs in this state and record their saves. That is the
intended repair, and it uses the same retention checks as other stopped
writers.
- A copy-back interrupted by a server restart can leave a
`remote_workspace_snapshot` operation marked `running` after its process
is gone. This change does not settle such rows. If one blocks the later
save, the save follows its existing retry and board hand-off.
- 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 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-06 00:03:41 -08:00
DottaandClaude Opus 5.5 39096a55a4 fix(runner): answer lease renewal outside the PRP frame chain and name lease loss (#19419)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native runs in a managed sandbox talk to the server over one
authenticated PRP connection. The runner (`paperclip-runnerd`) renews
its connection lease on that connection.
> - The server serializes every inbound PRP frame through one promise
chain per connection. Before this change, `lease_renew` waited in that
chain behind earlier events. An event whose business commit stalls (for
example the commit of a tool event) therefore also stalls the lease
reply. The runner then stops polling its provider (ACK backpressure) and
exits with "active connection lease expired" when the lease runs out.
> - When that happens, the server sees only a socket close. The run log
and the terminal error say "transport loss", not "lease expired".
> - A follow-up from #19407 asked for renewal to be handled outside the
chain and for lease loss to be named.
> - This pull request answers `lease_renew` (and `pong`) as soon as the
frame is decrypted. It keeps in-order decryption and the order of
state-changing frames. It also records why a lease was lost and reports
it as `runner_connection_lease_expired`.
> - The benefit is that a slow event commit can no longer starve lease
renewal, and a lease loss is visible in the run log and error code.

## Linked Issues or Issue Description

Refs #19407

**What happened?**
On staging, native runs with ACPX providers (Grok, Kimi, and Pi with
OpenRouter/DeepSeek) ran a long foreground shell command that printed
nothing (a 15–20 minute `sleep 60` loop). Each run went silent soon
after `tool.execution.started`. The command finished in the sandbox, but
the run never got the completion and stayed `running` until someone
cancelled it. It happened in 3 of 3 attempts. The same task on Codex
kept reporting activity and succeeded.

**What did you expect to happen?**
A quiet tool call keeps its runner connection and its lease. If the
connection or lease is lost, the run log and the error code say why.

**Steps to reproduce**
1. Assign a native ACPX agent a task that runs a foreground shell loop
with no output for longer than 2 minutes.
2. Observe that the run goes silent and does not finish after the
command ends.

**Root cause analysis**
- The ACPX path emits no PRP frames during a silent tool call. The
runner has no timer, and the agent sends no `tool_call_update`. Codex
keeps the connection busy, because its model polls the running command.
Silence is the only adapter-specific factor that I found. I found no
ACPX code path in runnerd that blocks the main loop: polls use a 1 ms
timeout, and synchronous sidecar requests run only for server commands.
- The staging build (`de1143448b`) did not include #19407. That build
had no server pings. With a 60-minute connection lease (the default for
`runner-prp-coordinator` and `DurablePrpCodexTransport`), the runner
renews only every 30 minutes. A fully silent connection that goes
through the sandbox preview proxy therefore carried no traffic at all. A
route that the proxy drops without a FIN then becomes half-open forever.
#19407 added 20-second pings and a 90-second termination, which closes
that window.
- The lease hypothesis from #19407 is real but secondary. The 60-second
lease applies only when the TTL is not set. Renewal waited behind any
slow event commit, and the server could not report lease loss. This pull
request fixes both.

## What Changed

- `DurablePrpControlPlane.attachWireConnection` decrypts each secure
frame when it arrives, in counter order. Frames that arrive before the
channel is secure, or behind such a frame, still decrypt in the chain.
- `lease_renew` and `pong` are handled at once, outside the
per-connection chain. Admission against persisted authority is
unchanged. Events, command results, and handoff frames keep their
serialized order.
- `#renewLease` persists the renewed expiry with `save()`, so an event
write that is already staged is absorbed into the same write. Before,
the renewal used `commit()`, which would invalidate that staged event
and close a healthy connection when renewal runs concurrently.
- New `connectionLeaseFailure` getter (exported type
`DurablePrpConnectionLeaseFailure`). It records
`connection_lease_expired` when an authenticated connection is refused
or closes with an expired lease. A new authenticated connection clears
it. A credential revoked by a warm handoff is not reported, because a
replacement runner owns the run.
- `connectRunnerPrpIngress` reads that reason after a close. It logs
`state=reconnecting failure=runner_connection_lease_expired` in the run
log. If reconnect grace runs out, it fails with that code instead of
`runner_ingress_unavailable` or `runner_ingress_auth_failed`, and keeps
the reconnect error as the cause.
- `packages/paperclip-runner/docs/durable-recovery.md` documents the new
behavior.

## Verification

- `pnpm --filter @paperclipai/paperclip-runner exec vitest run
src/control-plane` (220 passed).
- New: "answers lease renewal while an earlier event commit is still in
flight". It was red before the fix (no `lease_renewed` while
`onCommittedEvent` waits), and it is green now. It also checks that the
event later commits and ACKs, and that the renewed expiry survives.
- New: "keeps a staged tool-progress write when a lease renewal lands
mid-write". It holds a file-backed `tool.execution.progressed` write at
`fsync`. It is red with the old `commit()` renewal (the event is dropped
and the connection closes), and green with `save()`.
- New: "reports an expired connection lease instead of closing
silently". It was red before the fix and is green now.
- `pnpm --filter @paperclipai/server exec vitest run
src/realtime/runner-prp-outbound.test.ts
src/realtime/runner-prp-ws.test.ts
src/services/native-runtime/runner-prp-coordinator.test.ts` (all
passed).
- New: "names an expired runner connection lease in the run log and
terminal error when reconnect is refused/unauthenticated". It was red
before the fix and is green now.
- `src/live/runnerd-codex-transport.test.ts` has the same result with
and without this change with a locally built `paperclip-runnerd`: the
same 48 tests fail locally on both, for environment reasons, and the
other 207 pass.

## Risks

- Lease renewal now runs concurrently with an in-flight event handler.
The handler builds its state candidate at commit time, and `save()`
absorbs a staged event write. A new regression test covers the
file-backed race. External checkpoint persistence commits events
synchronously, and the renewal reply still waits for the checkpoint
flush.
- Renewal still stays silent while a warm handoff or a terminal
lifecycle command is pending. The runner treats any other control frame
inside those fences as fatal, so this pull request does not change that
behavior.
- No protocol, schema, or contract-digest change. The runner is
unchanged.

> 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 edits, test runs) and a read-only
exploration subagent on the same model.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 02:01:01 -06:00
DottaandPaperclip 4d49a31286 test(testops): pin reviewed four-case recovery source (#19418)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - The protected TestOps workflow evaluates a fixed source commit.
> - The last five-case attempt retained two passes and three failures.
> - Reviewed fixture changes address observed admission and reporting
gaps.
> - Product denial guidance and closed diagnostics remain candidate
overlays.
> - This PR updates only the immutable source pin and its public
metadata snapshot.
> - A bounded four-case measurement can test this candidate without
merging catalog or core code.

## Linked Issues or Issue Description

Refs #19325, #19416, #19395.

**What happened?**
The current pin targets the prior five-case source. The next reviewed
source changes question fidelity context, Personal menu observation,
Company secure-save acknowledgment admission, and Team partial-install
reporting. The last attempt's original failures remain unchanged.

**Expected behavior**
The workflow must admit the exact reviewed source and matching
descriptors. It must preserve the selected model, native and human
bounds, clocks, credentials, and zero automatic retries.

**Steps to reproduce**
Compare the current manifest and descriptor snapshot with source
`58449c4ecd7fd374f21f175411ef3249f55c18a3`. Run the local manifest,
adapter, reconciliation, and CLI admission controls listed below.

**Paperclip version or commit**
Controller base `90b8190c33e8f8eae44cae1f57721251fe1e1343`; candidate
source `58449c4ecd7fd374f21f175411ef3249f55c18a3`.

**Deployment mode**
Protected AWS Product E2E through the existing TestOps workflow.

## What Changed

- Pin the reviewed immutable source and exact descriptor snapshot.
- Update four existing oracle-version leaves and Company/Personal
source-file provenance.
- Append current conditions while preserving the complete historical
conditions prefix.
- Retain all 244 case identities, YAML requests and outcomes, counts,
model, clocks, runtime conditions and policy gates.
- Change one expected target literal in the manifest test. No workflow,
lock, runtime, SDK or credential changes.

## Verification

- 40 local controls pass:
`node --test
tests/runner-e2e/chat-full-catalog-testops-manifest.test.mjs
tests/runner-e2e/chat-full-catalog-testops-adapter.test.mjs
tests/runner-e2e/chat-full-catalog-testops-reconcile.test.mjs
tests/runner-e2e/chat-completion-testops-cli.test.mjs`
- Parsed all 244 rows. Only the four oracle versions and two source-file
arrays differ. Runtime conditions are identical. The candidate snapshot
matches committed source bytes.
- The source qualification includes focused positive/negative controls,
full runner E2E types, and unchanged SDK506 registry
`efa70621e9fb42aa185f7ee616a1a726aa3eccb367e67e28817cedf26ef2ade8`.
Counts from overlapping component sets are not summed.
- All 67 controller dependency graph inputs match the accepted
frozen-install proof. No install, SDK build or runtime build was
repeated.
- The existing selector admits exactly cases 53, 578, 580 and 610 in the
native lane with cap 2. No workflow or settings change is needed. This
local admission control did not submit a run.
- Full repository build/typecheck/test commands were not repeated for
this metadata-only change. Normal PR CI and fresh review remain
required.

## Risks

- The candidate has no new native PASS. Launch 3244 retains two original
PASSES and three original FAILURES. Older failed and missing-original
results remain unchanged.
- Case 53 changes factual setup context to identify the existing visible
form and adds stricter request fidelity. Its YAML query and original
clock are fixed. This is not a latency fix.
- Personal v14 treats the inline code path as an imprecise hint. Actual
current-company UI links must establish navigation before private entry.
Save, owner isolation, consumer delivery and final confirmation remain
separate required proofs.
- Team v8 fixes truthful partial-report admission. It does not qualify
end-to-end runtime migration.
- Company v15 accepts completed-entry acknowledgment questions. Actual
current-human save, payload, source, continuation and governed-approval
proofs remain mandatory. The corrected 403 handoff guidance is a
recovery hypothesis. Closed diagnostics do not prove a native repair.
- Pinned OpenCode model-free tests preserve the tested finish types in
both directions. They do not prove real provider behavior or engine wait
authority.
- The next measurement selects only 53/578/580/610 once, with native cap
2, zero automatic retries and original bounds. A later uniform ten
remains separately unrun. The unchanged 585 pass is carried-forward
evidence.
- Core #19325 and #19416 must remain open and unmerged. This PR does not
merge the held full catalog or change its 196/244 gate. A maintainer
will perform any normal merge and actual measurement after required
checks.

## Model Used

OpenAI GPT-6.1 Sol (`gpt-6.1-sol`), with reasoning, code execution and
repository tools. The host did not report a context-window size. No
model inference was used for local fixture 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>
nightly/v2026.1106.0-nightly.0 canary/v2026.1106.0-canary.7
2026-11-06 01:16:57 -06:00
DottaandPaperclip c1d567ff6a fix(workspaces): isolate archived nested Git reconstruction (#19409)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Durable workspace import must preserve archived task files and Git
history.
> - A saved nested checkout can be a linked worktree whose branch
belongs to its old absolute path.
> - Archived import reconstructs that checkout in a new scratch
directory.
> - Ordinary linked recovery rejects the old branch at that new path. It
can also write into a cache used by other import workers.
> - This pull request reconstructs archived nested checkouts as private
standalone repositories.
> - Each import preserves saved files and extra history without changing
shared worktree registrations or branches.

## Linked Issues or Issue Description

**What happened?**
Archived workspace import fails when a saved nested linked checkout has
a branch owned by the old path. The new scratch path fails the
linked-worktree ownership check. The existing cache lock is
process-local, so separate import processes also share mutable Git state
without a common lock.

**Expected behavior**
Archived import must create an independent portable copy of the saved
checkout. It must retain the saved commit, branch name, staged edits,
working files, ignored recovery files, settings, and extra saved
commits. It must keep the source cache unchanged and enforce source
authorization.

**Steps to reproduce**
1. Create a managed nested linked checkout with an unpushed commit and
staged and unstaged edits.
2. Save an encrypted restore point, including ignored files and an extra
history tip.
3. Restore its saved contents under a different absolute workspace path.
4. Before this repair, the new path cannot adopt the branch owned by the
old path.

**Paperclip version or commit**
The real Git regression fails before the repair at `fc1f5f3052`. The
final branch includes the merged interrupted-upload and nested-source
authorization fix from #19396.

**Deployment mode**
Archived remote workspace migration with real Git repositories,
disposable PostgreSQL, and encrypted object storage. Local integration
tests use the filesystem storage provider.

Builds on merged #19402 and #19396. Related #19374 concerns interrupted
clone staging; it does not implement private archived reconstruction. No
duplicate open repair was found.

## What Changed

- Enable private repository reconstruction only for archived migration.
Ordinary linked recovery keeps its path ownership guard.
- Add an object-only source to the managed checkout owner. A verified
cache supplies only the saved commit and authenticated bundle
prerequisites through scoped fetches. The private copy includes no
unrelated task refs or objects, hard links, object alternates, or
current cache files.
- Fetch missing authenticated bundle prerequisites before importing a
saved bundle in both private reconstruction and ordinary linked
recovery. A pruned cache can use the approved remote; unavailable
history still blocks restore.
- Keep every authenticated restore point's extra commit under a private
ref in root and nested private copies so portable archive capture and
garbage collection retain it. Ordinary recovery keeps its existing
history and expiration rules.
- Share the verified, bounded ignore-file copy between root and nested
reconstruction. Sources remain unchanged.
- Retain source authorization before and after reconstruction and the
final source-row locks from #19396.
- Test concurrent private copies, source rejection, foreign task refs
and objects, ordinary linked prerequisite recovery, a real migration,
and cold restore after removing all original repositories. Test hard
links, oversized excludes, unsafe info paths, and replacement during a
descriptor read.

## Verification

- Final affected run: 292 tests pass across ten suites. These cover real
Git repositories, encrypted restore points, disposable PostgreSQL
migration, portable archives, the restore-point ledger, source
rejection, and concurrent copies.
- Full repository typecheck and build pass after the scoped-object
repair. Final server typecheck and full build also pass after
restricting preserved refs to archive mode.
- RED then GREEN: a linked checkout at a different path; a pruned bundle
prerequisite still available from the approved remote; foreign task refs
and objects entering the archive; root extra history disappearing after
cold restore; and archive-only refs blocking ordinary standalone
expiration.
- Two concurrent private copies retain the saved head, branch, staged
and unstaged edits, ignored recovery bytes, settings, and modes. Source
refs, index, worktree registrations, and working files remain unchanged.
- Delete the original checkout, cache, remote, and private copies after
portable capture. Cold restore retains the saved files and extra history
after garbage collection. No alternates or unrelated task objects
remain.
- Ordinary linked recovery retains its path ownership check and recovers
pruned prerequisites. New and existing ordinary standalone destinations
have no archive-only refs and remain eligible for expiration after
normal object cleanup.
- Unsafe exclude tests reject hard links, oversized files, nonregular
paths, info-directory substitution, and replacement during the
descriptor read. They preserve the target and replacement source bytes.
- Preserve the failed attempts: an initial migration fixture changed its
home after constructing the source path. Later expiration fixture
attempts used a filtered current-head tip, omitted the local-source
callback, or used cleanup that generated Git server-info files or left
packed unreachable objects. Correct those setup assumptions before the
actual regressions fail. Final affected suites pass.
- Two earlier local full-suite attempts were interrupted before editing
valid review findings. Neither is reported as a full-suite PASS.
Final-head full CI passes; review is 5/5 with no open findings.

## Risks

Archived imports use additional private disk space for independent Git
objects. Missing, changed, or unsafe sources still block import. The
repair does not weaken the default linked-worktree ownership check,
change stored formats, or update live provider state. The shared
checkout addition is optional and has no effect for existing callers
that do not supply an object source.

## Model Used

OpenAI GPT-6 in Codex, with code editing, tool use, and test execution.
The exact runtime model identifier and context-window size are not
exposed in this session.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
canary/v2026.1106.0-canary.6
2026-11-06 00:56:06 -06:00
DottaandClaude Opus 5.5 90b8190c33 fix(native-runtime): detect half-open runner ingress and project Stop for live runs (#19407)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native runs in a managed sandbox talk to the server through a
provider-ingress WebSocket. The server opens this socket to the runner
(`paperclip-runnerd`) through the sandbox preview proxy.
> - A staging run ran a 20-minute foreground shell command that printed
nothing. After the first second, no run events arrived. The run stayed
"Working" for over 35 minutes. No watchdog acted, and the run projection
said `canStop: false`.
> - The server only detected a lost runner connection from a `close` or
`error` event. It never sent pings. A half-open route, for example a
proxy that drops the connection without a FIN, never emits `close`. The
run then waits forever and the 7-minute reconnect grace never starts.
> - A long silent tool call is legitimate. The fix must tell "quiet but
alive" apart from "nothing alive". A protocol ping does that: runnerd
answers every ping with a pong while its provider waits on the tool.
> - This pull request pings authenticated runner connections and
terminates the socket after 90 seconds of total silence. The existing
reconnect grace and transport-failure path then recover the run. It also
makes the execution projection report `canStop: true` for every queued
or running run.
> - The benefit is that a run with a dead runner connection now ends in
a bounded time instead of hanging. API readers also see the Stop that
the board already offers.

## Linked Issues or Issue Description

**What happened?**
A native-runner run (managed sandbox, OpenCode with an OpenRouter model)
ran a foreground shell command for about 20 minutes with no output. The
last run event came at about T+1s. The command finished on disk, but the
run never got another event and stayed `running` / "Working" for over 35
minutes. Its projection had `canStop: false` and `permittedActions:
["inspect_run"]`. A board cancel through the API worked.

**Expected behavior**
The server detects a dead runner connection and recovers or fails the
run in a bounded time. A runner that is alive but quiet stays attached.
The projection reports that a live run can be stopped.

**Steps to reproduce**
1. Start a native run in a managed sandbox.
2. Make the agent run a long foreground command that prints nothing.
3. Let the preview route drop the WebSocket without a close frame.
4. Before this fix, the run stays "Working" forever.

## What Changed

- `server/src/realtime/runner-prp-outbound.ts`: after PRP
authentication, `WsJsonWireConnection.monitorLiveness` sends a WebSocket
ping every 20 s. Any inbound frame or pong counts as liveness. After 90
s with nothing inbound, it terminates the socket. That emits an ordinary
`close`, so the PRP authority and the reconnect loop use the existing
7-minute grace and then fail the transport. If the server's own event
loop stalls, the window restarts instead of blaming the runner. The 90 s
bound is longer than runnerd's own 60 s connection lease, which already
requires runnerd to read the socket.
- `server/src/services/execution-projection.ts`: `canStop` is now `true`
for every `queued` or `running` run. The old failed-native-retry rule is
unchanged.
- `server/src/services/execution-projection.ts`: a queued run now shows
"Waiting for previous run to stop" (cause `previous_run_settling`) when
a predecessor that admission checks (the task execution owner, the
retried run, or the agent's latest settled run on the task) still has
pending cleanup by `hasPendingRunCleanup`. Before, a turn queued after a
Stop showed only "Queued" with no reason while the stopped run's
controller still waited on the dead runner. That in-process wait
(`claimQueuedRun` settling-owner gate) also made Retry a no-op. The
liveness bound above makes that controller settle. Its existing cleanup
then calls `startNextQueuedRunForAgent`, which starts the queued turn.
- `server/src/services/run-cancellation.ts` and
`server/src/modules/run-dispatch/adapters/postgres.ts`: a user-stopped
native run whose runner was unreachable can later settle as `failed`
(`native_provider_terminal_failed`) when its transport fails closed. The
dispatch gate's message deferral required a `cancelled` source and
compared the message with `finishedAt`. The user's follow-up message was
therefore marked `skipped`, and the task asked for a second message. The
new `recordedUserStop` helper honors the recorded user Stop on such a
failed source, so the gate saves the message for the deferred-receipt
worker.
- `packages/shared/src/types/execution-projection.ts`: documents the
`canStop` meaning.
- `doc/execution-semantics.md`: documents the ingress liveness bound and
the new `canStop` meaning.

## Verification

- Red then green: `runner-prp-outbound.test.ts` "detects a half-open
authenticated ingress and fails the run within a bounded window". On the
old code it failed because no ping was sent and the socket was never
terminated. It now passes and the active-run `failure` rejects with
`runner_ingress_unavailable`.
- Safety: "keeps a quiet but live runner attached through a long silent
tool call" checks that a runner answering pings stays attached for 20
simulated minutes with no termination and no failure.
- Red then green: `execution-projection.test.ts` "offers Stop for every
live run, including a long-silent native turn". On the old code it
failed with `canStop: false`.
- Red then green: `execution-projection-recovery.test.ts` "names the
wait when a stopped predecessor still holds the task's runner" (real
Postgres). On the old code the successor projected a plain "Queued". It
also checks that the wait clears on lease release and ignores another
agent's run.
- Red then green: `workspace-recovery.test.ts` "continues a message sent
after Stop when the stopped run later failed closed". On the old code it
failed 3 of 3 runs with the receipt `skipped`. It now passes, and the
full file passes twice (392 tests). The test now pins the message after
the recorded Stop. Before, the fixture could write both in the same
millisecond, which made the sibling variants flaky.
- Passed: run-cancellation, explicit-native-continuation, run-dispatch
and run-control suites (622 tests).
- Passed: `npx vitest run src/realtime/
src/services/execution-projection*.test.ts
src/__tests__/heartbeat-run-privacy-routes.test.ts`, the
native-runner-execution-driver tests, and the plugin-runner-task tests.
- Passed: `pnpm --filter @paperclipai/server typecheck`.
- I read runnerd (`runner-core/src/durable/transport.rs`) and confirmed
it answers `Ping` with `Pong` in its non-blocking read loop.
- Not run: a live managed-sandbox reproduction. Staging and Daytona were
not reachable from this environment. `plugin-runner-probe.test.ts` needs
a local cargo build of runnerd, and it did not start in this worktree.

## Risks

- A false termination is possible if a runner fails to answer pings for
90 s while it is still alive. runnerd already loses its own connection
lease after 60 s without reading, so this bound does not add a stricter
requirement. A terminated live runner reconnects within the existing
grace.
- If the preview proxy drops WebSocket control frames, ping alone does
not prove liveness. Inbound PRP frames (runnerd's lease renewals, about
every 30 s) still count, so a live runner stays attached.
- `canStop` now means "the board can Stop now" for live runs. The UI
already shows Stop for running and queued runs without reading this
hint, so the UI does not change.
- Out of scope: the inbound runner WebSocket server
(`runner-prp-ws.ts`), and a lost OpenCode SSE event while the runner is
alive. A stopped run whose controller hangs for a reason other than a
dead transport still holds the queued successor. The projection now
names that wait, but this PR does not force-release it, because the
controller has no termination evidence. A queued reason for the agent
concurrency cap is also not covered. The wait label calls the admission
predicate (`hasPendingRunCleanup`) for up to three predecessor runs per
queued run. This adds a few reads to projections of queued runs only.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use (shell,
file edits) 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-06 00:52:01 -06:00
Dotta 92452524f4 fix(server): recover authenticated empty retention checkpoints (#19417)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Ephemeral server enrollment must preserve existing task files.
> - Archived task directories can depend on encrypted retention
checkpoints.
> - A missing object blocks migration even when its authenticated
contents were exactly empty.
> - Metadata counters alone cannot prove that no saved files existed.
> - This change verifies the exact candidate against the original
encrypted key envelope before normal migration publication.
> - The benefit is lossless migration of this bounded case without
resetting task history or weakening ordinary restore.

## Linked Issues or Issue Description

**What happened?**
Migration stops at an archived empty directory when its retained
encrypted object is missing. The original encrypted key envelope still
authenticates the payload hash, byte length, company, workspace,
checkpoint ID, and generation.

**Expected behavior**
Administrative migration can reconstruct those exact original bytes when
storage confirms absence and every authenticated check passes. It must
keep missing nonempty, expired, corrupt, or ambiguous sources blocked.

**Steps to reproduce**
1. Capture and encrypt an empty task directory.
2. Retain its reference and encrypted key envelope.
3. Remove the old directory and storage object.
4. Run the normal remote workspace migration.

**Paperclip version or commit**
The regression fails before this change at `205ffcb606`. Related
preservation work is in #19396, #19402, and #19409. No duplicate open
fix was found.

**Deployment mode**
Administrative migration to external workspace storage. Tests use real
encryption, real filesystem storage, and disposable PostgreSQL.

## What Changed

- Let the encryption owner verify a candidate against the existing
scoped encrypted key envelope. Normal ciphertext validation keeps its
previous order and checks.
- Share the task-directory encoder with an exact empty payload
constructor.
- Add a default-off loader option for confirmed missing empty
checkpoints. Only the administrative migration caller enables it.
- Keep the original checkpoint reference and object unchanged. Publish
the recovered bytes through the existing snapshot and source-fence
owners.
- Reject forged counters, wrong identities, missing nonempty data,
present corruption, unreadable present objects, unknown absence, and
expiry.

## Verification

- Final affected run: all 238 tests pass across five crypto, snapshot,
retention, and migration suites. Full repository typecheck and build
pass; server typecheck and full build also pass after the cancellation
repair.
- RED then GREEN: exact missing-empty recovery and cancellation of a
stalled HEAD through the real storage-service boundary. Cancellation
retains the caller's original abort reason.
- Disposable PostgreSQL migration publishes a cold-restorable empty
archive. Archived status, task status, task binding, original reference,
and the absent original source stay unchanged. Replay keeps one
canonical snapshot.
- Negative cases reject forged empty counters, an authenticated
directory or file, every foreign identity field, expiry, present
corruption, unreadable present objects, unknown absence, and missing
nonempty data. Ordinary restore keeps its previous failure behavior.
- Initial verification retained one new fixture-path failure after 145
passes, and a typecheck rejected the fixture's widened schema literal.
Correct the fixture to use the returned staging path and its explicit
reference type. The final complete affected run passes.
- The new HEAD request runs only after a failed read with the
administrative option enabled. It uses the existing cancellation
contract and adds no polling or background work.
- Final-head full CI passes; review is 5/5 with no open findings. This
PR does not claim deployed enrollment or a live migration result.

## Risks

Recovery requires the original valid encrypted key envelope and an exact
format match. A format change or unavailable key continues to block
migration. Existing ciphertext hash, size, authentication, and ordinary
restore behavior remain enforced. This change has no schema,
provider-session, API, or UI contract changes.

## Model Used

OpenAI GPT-6 in Codex, with code editing, tool use, and test execution.
The exact runtime model identifier and context-window size are not
exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` 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 references)
- [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
2026-11-06 00:50:28 -06:00
DottaandClaude Opus 5.5 a4bb059f59 fix(heartbeat): retry transient sandbox provider lease refusals and explain the block (#19412)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A task runs in an execution environment. A managed sandbox
environment gets a lease from a sandbox provider (for example Daytona)
before the agent starts.
> - When the provider answered sandbox create with a temporary error
(HTTP 502), the run failed as the generic `setup_failed`.
> - `setup_failed` is the fail-closed code for unknown setup errors. It
gets no automatic retry, so the task went to `blocked` at once. The task
showed only "Couldn't finish this attempt".
> - A user had to find and press Retry. The Retry then worked, which
shows that the failure was temporary.
> - This pull request classifies temporary provider refusals during
lease acquisition from structured fields. It retries them within the
existing bounded bootstrap budget, and it explains the block when the
retries are used up.
> - The benefit is that a short provider outage no longer stops a task.
When the outage continues, the task shows the cause and a Retry that
works.

## Linked Issues or Issue Description

**What happened?**
A task was assigned to an agent that uses a managed Daytona sandbox
environment. Sandbox creation returned `HTTP 502`, and the cleanup of
the provisional sandbox could not be confirmed. The run failed with
`setup_failed` and the error `Failed to acquire lease for environment
"Paperclip Sandbox" (sandbox): Daytona sandbox creation failed (HTTP
502); cleanup could not be confirmed for paperclip-create-…`. Three
seconds later the task was `blocked`. No automatic retry was scheduled.
The task showed no specific reason. A manual Retry started a new run
that completed normally.

**Expected behavior**
A temporary provider error (HTTP 5xx, 429, timeout) before the agent
starts must retry automatically with bounded backoff. The task must show
why it waits. If the retries are used up, the task must show the cause,
a next action, and a Retry that works on the first click.

**Steps to reproduce**
1. Configure an agent with a sandbox environment.
2. Make the provider return HTTP 502 on sandbox create.
3. Assign a task to the agent.
4. See that the run fails with `setup_failed` and the task goes to
`blocked` with no retry.

**Paperclip version or commit**
`master` at `612da1e8e0`.

## What Changed

- Plugin SDK: an `environmentAcquireLease` error now carries a closed
diagnostic envelope (HTTP status or a closed errno code) across the
worker RPC. It takes the status of the create call only, not of the
compensating cleanup lookup. The existing creation-cleanup ownership
envelope is unchanged. New module: `environment-acquire-error.ts`. The
resume diagnostic helpers are shared.
- Server: new `EnvironmentProviderUnavailableError` and code
`environment_provider_unavailable`. The environment run orchestrator
sets it as the cause of `lease_acquire_failed` only when structured
evidence shows a transient status (408, 425, 429, 500, 502, 503, 504, or
a closed connection errno). It never parses message text. Saved-sandbox
resume and stopped-worker refusals keep their own codes.
- Setup failure (`failRunSetup`): a fresh, pre-dispatch run records
`environment_provider_unavailable` with a `providerFailure` (phase
`startup`, retryability `transient`). The run uses the bounded bootstrap
retry (30 s, then 60 s). An accepted interaction continuation keeps its
own continuation retry, as for `plugin_worker_unavailable`.
- Recovery: the new code is registered with the other bootstrap codes.
It is not sent to the generic continuation retry. It does not create an
execution-reconciliation hold, because no provider work started. On
exhaustion the stranded notice has the title "Sandbox provider was
temporarily unavailable" and a specific next action.
- UI: `ProviderFailureNotice` labels the `sandbox` provider as "Sandbox
provider". The task notice shows the provider failure, "Paperclip will
retry automatically" while a retry is scheduled, and Retry after
exhaustion.
- Docs: `doc/execution-semantics.md` describes the policy and the
sandbox ownership rule.

## Verification

Red before the fix, green after the fix:

- `server/src/__tests__/heartbeat-process-recovery.test.ts` › "transient
sandbox provider refusal during lease acquisition":
- "retries automatically and runs exactly one successful successor
without user action": the real heartbeat. The lease runtime returns a
Daytona 502 once, then delegates. Before the fix: one `failed` run and
no successor. After the fix: one `scheduled_retry` successor with no
hold and a `retry_scheduled` presentation. After promotion, exactly one
successor succeeds.
- "explains the block with a working Retry once automatic retries are
used up": three refusals. The task is `blocked` with a "Sandbox provider
was temporarily unavailable" notice. The presentation is `{ state:
"failed", canRetry: true, providerFailure: { httpStatus: 502 } }`, and
no execution-reconciliation hold exists. One `requestTaskRetry` is
accepted, and its run succeeds.
- `server/src/services/heartbeat/run-completion.test.ts` › "routes a …
sandbox provider lease refusal through bounded bootstrap recovery": the
real orchestrator wrapping. Only the fresh, structured 502 is retried. A
message-only 502, a 400, a run after provider dispatch, and a retained
native run stay `setup_failed`.
- `packages/plugins/sdk/tests/environment-acquire-error.test.ts`: the
real worker RPC. The test checks the create status (not the 404 cleanup
lookup), the errno, no provider text, the unchanged cleanup envelope,
and that forged envelopes are rejected.

Commands:

```sh
pnpm --filter @paperclipai/plugin-sdk exec vitest run tests/environment-acquire-error.test.ts tests/environment-creation-cleanup.test.ts tests/environment-resume-error.test.ts
cd server && npx vitest run src/__tests__/heartbeat-process-recovery.test.ts src/services/recovery/ src/services/heartbeat/run-completion.test.ts src/services/heartbeat/retries.test.ts src/services/legacy-execution-recovery.test.ts src/services/task-interruption-presentation.test.ts src/services/task-retry.test.ts src/__tests__/environment-runtime.test.ts src/__tests__/heartbeat-retry-scheduling.test.ts
cd packages/plugins/sandbox-providers/daytona && npx vitest run
pnpm --filter @paperclipai/plugin-sdk typecheck && pnpm --filter @paperclipai/server typecheck
```

I did not run a live Daytona 502 on staging. Re-check on staging after
deploy (see Risks).

## Risks

- Sandbox leaks or double creation: each retry is a new run with a new
durable creation record and a new provider name
(`paperclip-create-<operationId>`). The failed attempt stays owned by
its own record, and the existing creation reconciler settles it. A retry
never reuses or abandons it. While a provisional sandbox waits for
reconciliation, up to two more sandboxes can be created for the same
task. This is the same as a manual Retry today.
- Plugins built with an older SDK do not send the envelope. Their
refusals stay `setup_failed`, which is the current behavior.
- An in-process error with `status`/`statusCode` 500–504 or a connection
errno during lease acquisition is now retried twice. The budget is
bounded and shared with the other bootstrap codes.
- An RPC timeout on the host side is not classified by this change.

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

## Model Used

- Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, used through
Claude Code as an agent with tool use (shell, file edits, test runs).
Extended reasoning was on.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
canary/v2026.1106.0-canary.5
2026-11-06 00:08:44 -06:00
Devin FoleyandClaude Opus 5.5 558a5143ed fix(workspaces): reject damaged Git entries before workspace capture (#19160)
Workspace capture now checks that each .git entry is the root of its own
repository before it reads any Git state. A structurally damaged entry
(an empty .git directory, a gitfile for a removed worktree, an invalid
HEAD, or missing objects/refs) stops capture with the closed reason
remote_workspace_archive_invalid_git_repository instead of a raw
"Command failed: git ..." error, and capture no longer saves an enclosing
repository's state as the workspace's own. Other Git failures (dubious
ownership, a full disk, a timeout, a failing Git program) keep their
existing diagnostics. Metadata reads are bounded and never block on named
pipes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:06:05 -08:00
Nicky Leach 205ffcb606 Verify live Claude and Codex ACPX eligibility before parking (#19295) 2026-11-05 21:37:34 -08:00
DottaandPaperclip b918ee165d test(evals): pin coherent chat feature follow-ups (#19411)
## Thinking Path

> - Paperclip manages agent work and its permission boundaries.
> - The chat catalog runs feature checks from an immutable source.
> - The last full-ten attempt had five reported passes, four failures
and one missing original.
> - Local evidence identified narrow assertion, setup and
evidence-retention repairs.
> - This PR updates the trusted source pin and its exact descriptor
snapshot.
> - The next five-case measurement can test the affected features under
the same model, clocks and native cap.

## Linked Issues or Issue Description

Refs: #19395, #19405, #19093, #19084, #19112.

The previous source remains preserved. This PR selects reviewed source
`f5692c57dafaf9afbef479c25fcd277815932516` on
`codex/chat-full-ten-coherent-qualified-f5692c57-20261106`, tree
`b9e302d2ea7b605a60125dec8c81672eac7a3c57`. The full catalog and all
core PRs remain unmerged. Core #19405 is included only as a source
overlay.

## What Changed

- Update the source target, exact descriptor snapshot and the matching
pin checks. Preserve historical conditions, frozen source anchors and
the prior query clarification.
- Record Company ACK handling and Company/Personal negation corrections,
plus the skill-update preview negation correction. A denied or unapplied
action still fails when the agent claims it completed.
- Disclose the Team setup change: a disposable catalog package selects
the approved OpenCode runtime and same-company account. Other file bytes
and non-runtime sidecar fields stay unchanged. Original and effective
package identities and hashes are recorded. A real PostgreSQL install
without caller overrides proves the saved runtime. This gives no
end-to-end runtime-migration or override-handling credit.
- Include defensive failed-turn evidence retirement and generic,
schema-valid human-input examples. The old case 53 missing-original
ENOENT cause is not diagnosed. The examples are a usability hypothesis,
not a schema repair or native-success claim.

All 244 YAML definition fields (query, assumed context and expected
outcome) remain byte-identical versus source 936c. The Team derived
runtime fixture and task setup context change is disclosed above.
Native/human bounds, clocks, DeepSeek 1017, credentials, SDK506/efa,
launcher, private interfaces, native cap two and zero automatic retries
remain unchanged. Only descriptor `sourceFiles` and oracle versions
change. The 196/244 gate stays unchanged.

## Verification

Pin-local checks:

```sh
node --test tests/runner-e2e/chat-full-catalog-testops-manifest.test.mjs tests/runner-e2e/chat-full-catalog-testops-adapter.test.mjs tests/runner-e2e/chat-completion-testops-contract.test.mjs
```

- All 49 pin/admission controls pass locally. They join all 244
identities, historical query overrides, native bounds and the exact
242-row descriptor snapshot.
- Source qualification: 447 controls across 32 suites, full E2E
TypeScript checks and normal contract generators pass. The separate 21
core contract controls are carried-forward component evidence; they are
not added to 447. Local source checks used Node 26.4.0. AWS runtime
settings are unchanged.
- The selected source descriptor SHA256 is
`24fe88af167724c6fb120d3984296aab47c993a65782158e03d80a936fbd94ed`.
- The actual source launcher is byte-identical to 936c: SHA256
`2b9a6953ee51f0ea0dbbd475b0491d9a6c39a1d6835eeaa912e0dbb7cf987724`, Git
blob `c88df15f509b2ac8638af826647a97d45f16bd1f`.
- All 506 registered SDK inputs and the ten trusted helper bytes are
unchanged. Five required source-helper joins pass. No SDK renewal or
native build is part of this PR.
- The current controller's 67 dependency inputs match the qualified
frozen reporting graph. No reporting-lock, package, permission or
workflow change is included.
- Exact head `b833036fb477b55b05cbdf1f4feb2e2c5387b420` is green: CI run
42166216108 attempt 2 succeeded, Windows succeeded, and both current
reviews are 5/5 with zero unresolved threads. The check rollup has 56
successes, three configured skips and one neutral result. New
representative cases 53, 578, 580, 585 and 610 are unrun. A later
uniform-ten regression is separate.
- CI attempt 1 remains retained: one cloud-auth browser fixture read a
trailing health response after context teardown began, after all
task-recovery assertions had passed. One authorized rerun of only that
failed job and its dependent checks succeeded on the unchanged head.
This does not fix or qualify fixture reliability; no eval or model run
was retried.

The previous full-ten outcome remains five reported passes, four
failures and one missing-original/reporting gap. Case 585 reached only
its first preview before the false rejection; its update phase is
unqualified. Case 610's old noncompletion and runtime-override journey
remain unresolved. All original failures and evidence remain retained.
No old result is regraded or replaced.

## Risks

The local repairs may not produce a native pass. The new Team setup
tests installation with an already selected runtime package; it does not
qualify runtime migration. Failed-turn retirement has local regression
coverage but does not establish the cause of the old case 53 reporting
gap. The core guidance overlay remains unmerged and its native benefit
is unproved. Normal protected merge and a new authenticated measurement
are still required.

## Model Used

OpenAI `gpt-6.1-sol` through Codex. It used code inspection, local test
execution and GitHub tooling. The context window size is not exposed. No
model inference or provider call was made for this PR's local checks.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-05 23:32:34 -06:00
DottaandPaperclip 66d3fa97a9 fix(workspaces): preserve ignored files in archived Git imports (#19402)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Durable workspace migration must preserve archived files and saved
recovery data.
> - An archived Git workspace can contain ignored recovery receipts and
bundles in its encrypted restore point.
> - The importer reconstructs a private repository from a verified
source before it applies that restore point.
> - Reconstruction omitted the source repository's local ignore rules.
The restored files then changed classification, and the exact
restore-point check failed.
> - This pull request copies the verified local ignore rules into the
private repository before restoration.
> - The source stays unchanged, and the original restore-point integrity
checks still apply.

## Linked Issues or Issue Description

**What happened?**
An archived workspace with saved files under an ignored recovery
directory failed migration with `restored_contents_mismatch`. The task
commit and staged index matched. The ignored files were restored, but
the private repository treated them as untracked work because its
`info/exclude` rules were missing.

**Expected behavior**
Reconstruction from an existing verified source must preserve its local
ignore policy. Saved ignored files must survive migration and a cold
object-store restore with their original classification. Import must not
follow a symlink to an unrelated ignore file or edit the source
repository.

**Steps to reproduce**
1. Exclude a recovery directory in the managed source repository's
`info/exclude` file.
2. Save staged edits, unstaged edits, an ignored recovery receipt, and
an ignored binary bundle in an encrypted restore point.
3. Archive and remove the task worktree.
4. Import the archived workspace. Before this change, restore-point
validation fails because the reconstructed repository lacks the local
exclusion rule.

**Paperclip version or commit**
Reproduced on master at `879140cbbd`.

**Deployment mode**
Cloud workspace migration with a retained verified Git source,
PostgreSQL, and encrypted durable object storage. The tests use real
disposable PostgreSQL, Git repositories, and encrypted filesystem-backed
object storage.

Related migration work: #18826, #19138, and #19396. No duplicate open
fix was found.

## What Changed

- Copy the verified source's `info/exclude` file into the private
reconstructed repository before applying its restore point.
- Bound the read to one MiB. Reject symlinked, non-regular, hard-linked,
or changing source files.
- Keep the original source bytes unchanged.
- Add regressions that check staged and unstaged edits, ignored recovery
files, cold restore, and rejection of a symlinked ignore file.
- Document the reconstruction behavior.

## Verification

- Before the repair, the saved-ignored-files regression fails with
`restored_contents_mismatch`.
- After the repair, both focused cases pass.
- At rebased head `81a0b1c041`, all 206 tests across migration, snapshot
storage, archive, and migration concurrency suites pass. The merged
interrupted-upload and nested-source approval regressions remain intact.
- Server package typecheck passes.
- Full repository typecheck and build pass at the rebased head. The full
local test suite is running. The prior local full-test run was
interrupted for rebase, and is not a passing result.
- Initial CI hit a 15-second timeout in unchanged
`activity-routes.test.ts`; the exact case passed in a focused local run.
The browser shard lost its self-hosted runner, as confirmed by its check
annotation; GitHub did not retain its full log. Those failures remain
recorded. An unchanged retry was superseded by the required conflict
rebase. At exact head `81a0b1c041`, all 53 CI checks and both required
aggregate gates pass; Greptile is 5/5. The full local suite remains in
progress, so no local full-suite PASS is claimed.

A second automated review noted the existing nested-checkout
counterpart. This PR fixes root reconstruction. The nested
linked-checkout repair and its saved-ignored-files/cold-restore
regression are implemented in #19409, which is required before the
migration rollout uses nested imports. This PR leaves that sibling
recovery path unchanged. The two identical scope warnings are addressed
through that linked repair.

## Risks

The copy applies only when an archived Git restore-point source is still
present and verified. It changes only the private reconstruction
directory. Missing sources retain the existing restoration path. Unsafe
or unstable local ignore files block import and retain the original
migration obligation. No database migration or saved format changes are
included.

## Model Used

OpenAI GPT-6 in Codex, with code editing, tool use, and test execution.
The exact runtime model identifier and context-window size are not
exposed in this session.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
canary/v2026.1106.0-canary.4
2026-11-05 23:11:28 -06:00
DottaandClaude Opus 5.5 612da1e8e0 test(server): drain throttled follow-up in busy recovered-source execution-control test (#19403)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The server runs durable execution-control workers, which include the
workspace-recovery sweep
> - `execution-control-work.test.ts` has a test for a Retry that commits
during a busy recovered-source pass
> - This test fails intermittently in PR runs that do not touch it, so
it blocks unrelated PRs
> - The test assumed that one `settle()` pass always ends idle, but the
worker can correctly owe a throttled follow-up pass
> - This pull request makes the test drive fake time through that
throttle window while it waits for the sweep to finish
> - The benefit is a deterministic test that still proves every source
is visited and the worker becomes quiet

## Linked Issues or Issue Description

**What happened?**
`execution-control durable work > visits a newly committed earlier Retry
during a busy recovered-source pass, then becomes quiet` failed in 2 of
the last 250 failed `PR` workflow runs (jobs 127272079294 and
127206639630). Both failures were identical: `AssertionError: expected 1
to be +0` at `vi.waitFor` in `settle()`
(`execution-control-work.test.ts:146`). The test took about 6s, which is
the 5s `waitFor` limit plus overhead.

**Expected behavior**
The test passes on every run. Its result does not depend on runner
speed.

**Steps to reproduce**
1. Add a 300ms real-time delay in `registerExecutionControlWork` after
`await duty.run()` and before `await duty.next()`. This simulates slow
database I/O on a loaded runner.
2. Run `pnpm exec vitest run
src/__tests__/execution-control-work.test.ts -t "busy recovered-source
pass"` in `server/`.
3. The test fails 3 of 3 times with the same CI error.

**Root cause**
- `registerExecutionControlWork` lets a duty make one full pass per
`EXECUTION_RECONCILIATION_INTERVAL_MS` (15s), measured from the start of
the previous full pass.
- The released recovery jobs call `worker.wake()` when they finish. If a
wake arrives while the full pass is still reading its next deadline,
`hasPending()` stays true. The coordinator then keeps its idle-tracked
intent until the next permitted pass, up to 15s of fake time later.
- `settle()` advances fake time by 15s and then waits 5s in real time
for `idleWorkSnapshot().active === 0`. The full pass starts inside that
15s advance, so the follow-up is due after the window ends. The wait
then expires.
- On a fast machine the jobs usually finish after the deadline read, so
the test passes. On a slow runner the order changes, so the test fails.
- This is correct product behavior: the worker owes the follow-up pass.
The defect is in the test's timing assumption.

## What Changed

- In the busy recovered-source test, replace the bounded `settle()` loop
with a `vi.waitFor` that advances fake time by
`EXECUTION_RECONCILIATION_INTERVAL_MS` on each attempt. It waits until
every source was visited and no idle-tracked work is active.
- The test still calls `quiet(c)` after this, so it still asserts no
further scheduling and no database calls during the next 90s.
- The shared `settle()` helper is not changed, because `reschedules an
earlier deadline…` asserts an exact `nextWakeAt()` after `settle()`.

## Verification

- Red: with the 300ms delay injected (not committed), the test failed
3/3 times with the CI assertion.
- Green: with the same injection at 300ms (3 runs) and at 1000ms (1
run), all four recovered-source Retry tests pass.
- Without injection, the full `execution-control-work.test.ts` passed
5/5 times with 12 CPU-burner processes running.
- `pnpm exec vitest run src/__tests__/execution-control-work.test.ts` in
`server/`.

## Risks

- Low risk. This is a test-only change. The test still requires every
source to be visited, the worker to become idle, and then 90s of quiet.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits, GitHub CLI). Default context
window. No extended thinking mode.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:58:33 -06:00
DottaandClaude Opus 5.5 223687bbb9 test(server): size heavy chat-channel tests to their workload to stop timeout cascades (#19404)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Chat channels connect GitHub, Slack, and other providers to tasks,
and `chat-channels.integration.test.ts` covers them against a real
embedded Postgres
> - Two parameterized tests do much more database work than the other
tests in the file, but they use the global 15s `testTimeout`
> - On a slow CI runner they time out. Vitest then keeps the timed-out
test body running, and that body breaks later, unrelated tests in the
same file
> - This failure blocks PRs that do not touch chat code
> - This pull request gives the two heavy tests a time budget that fits
their workload
> - The benefit is that a slow runner no longer turns one heavy test
into a cascade of 2–7 failures

## Linked Issues or Issue Description

**What happened?**
In run 42098299218 (job 127495756270, shard `General tests (chat
(3/3))`), 7 chat tests failed in two Vitest processes on a PR that
changed only unrelated test files:
- Process 1: `reactivates denied retry notices when authority is
restored during bounded pass` timed out at 15000ms. Then `… during
requester merge` failed with `A function to advance timers was called
but the timers APIs are not mocked`.
- Process 2: `preserves one current GitHub Retry across backlog_51` and
`… consumed_backlog_52` timed out at 15000ms. About 110 tests later,
three pre-run FIFO tests failed: `advances pre-run FIFO selection past a
suppressed page with microsecond timestamps (manual)` (`expected +0 to
be 1`), `retires an already-visible pre-run FIFO notice once without
publishing scheduler failures` (`expected [] to have a length of 1`),
and `retries a contended pre-run FIFO receipt before I/O and observes
cancellation after the lock releases` (`expected undefined to match
object { state: 'retry' }`).

**Expected behavior**
These tests pass on a slower runner. A timeout in one test does not
change the result of a different test.

**Steps to reproduce**
1. On `master`, in `server/`, run:
`pnpm exec vitest run src/__tests__/chat-channels.integration.test.ts -t
"GitHub Retry across (backlog_51|consumed_backlog_52)|advances pre-run
FIFO selection past a suppressed page|retires an already-visible pre-run
FIFO notice once|retries a contended pre-run FIFO receipt before I/O"
--testTimeout=1200`
2. The lower timeout simulates a slow runner. The two backlog tests time
out, and then the same three FIFO tests fail with the same assertions as
in CI. The `scoped` FIFO case passes, as it did in CI.
3. Run the same command without `--testTimeout`. All 6 tests pass.

**Root cause**
- The workload is large. `backlog_51` and `consumed_backlog_52` deliver
51–52 real inbound GitHub messages through the full delivery path.
`bounded pass` inserts and authorizes 100 rejected retry notices.
- CI timing data supports this. On passing runs these tests take
5.4–7.7s (backlog) and 3.6–5.3s (bounded pass), and lighter tests in the
same file take 0.5–1s. On the failing runner, the lighter tests were
2–3x slower (for example, `idle` took 1856ms instead of 500–680ms). This
put the heavy tests over 15s.
- When Vitest times out a test, it does not stop the test body. The body
keeps running while the next tests start:
- The unfinished backlog tests left 51+ `deferred_issue_execution`
inbound wakeups. The pre-run FIFO tests use unscoped, bounded passes
(`enqueueInboundWakeupPublications`, `processPendingPublications`). The
leftover rows filled those pages, so the new test's row was not reached.
- The unfinished `bounded pass` body later ran its `finally` block,
which calls `vi.useRealTimers()`. This disabled the fake timers that
`requester merge` had just installed.

## What Changed

- `preserves one current GitHub Retry across %s`: set an explicit 60s
timeout.
- `reactivates denied retry notices when authority is restored during
%s`: set an explicit 60s timeout. Increase the `bounded pass` wait for
its held 100-row reconciliation from 10s to 30s, so that the inner wait
does not become the new limit.
- The same file already uses explicit 20s, 25s, 30s, 40s, 60s, and 90s
budgets for other heavy tests. No product code changed.

## Verification

- Red: the reproduction command above fails on `master` with the CI
cascade. A tighter `--testTimeout=1600` on `reactivates denied retry
notices` also shows that later modes fail after `bounded pass` times
out.
- Green: the same reproduction command with this change (still
`--testTimeout=1200` for all other tests) passes 10/10 selected tests,
including the three FIFO tests.
- `pnpm exec vitest run src/__tests__/chat-channels.integration.test.ts
-t "GitHub Retry across|reactivates denied retry notices|pre-run FIFO"`:
46 passed.

## Risks

- Low risk. This is a test-only change. A real hang in these two tests
now fails after 60s instead of 15s. This is well inside the job limit.
- The cascade can still occur if one of these tests needs more than 60s.
That would mean a runner about 10x slower than a normal one.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits, GitHub CLI). Default context
window. No extended thinking mode.

## Checklist

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

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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:40:13 -06:00
DottaandClaude Opus 5.5 ec94e43c4a test(server): budget the 130 MB isolated re-home test for its data volume (#19400)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Isolated repository re-home moves a task workspace onto the shared
project clone, so the private clone can be deleted
> - One re-home test moves two 65 MiB files on purpose, to prove that
large changes still move
> - On CI this test times out at the 15000 ms default in pull requests
that do not touch re-home
> - The test does a fixed, large amount of CPU work, and a loaded runner
cannot finish it in 15 seconds
> - This pull request gives that one test a 60000 ms budget that matches
its data volume
> - The benefit is a stable server shard, with no change to what the
test proves

## Linked Issues or Issue Description

**What happened?**

A survey of failed `PR` workflow runs found intermittent failures of one
test in pull requests that do not touch re-home:

```
FAIL src/__tests__/isolated-repository-rehome.test.ts > isolated repository source re-home > re-homes a workspace whose changes exceed the default restore point limits
Error: Test timed out in 15000ms.
```

Examples: jobs 127465954304, 127499682176, and 127538360101 (shard
`general-server-without-chat` 7/12). The survey counted 4 such failures.
The other 27 tests in the file passed in each of these runs.

**Expected behavior**

The test passes on every run, and it still proves that a 65 MiB
untracked file and a 65 MiB saved ignored file move with an exact
snapshot match.

**Steps to reproduce**

Run the unchanged test on master while the CPU is oversubscribed:

```sh
cd server
node load.mjs 72 pnpm exec vitest run src/__tests__/isolated-repository-rehome.test.ts -t 'exceed the default' --reporter=verbose
```

`load.mjs` starts 72 busy-loop Node processes (4 per core on an 18-core
machine) and runs the command. The test fails with `Test timed out in
15000ms`.

**Root cause**

The file sizes are part of the test contract. #17410 added the test to
prove two fixes that only show above 64 MiB: `applyRestorePoint` streams
`git diff --binary` into `git apply`, and the ignored-file read sizes
its buffer from the tree. So the files cannot get smaller.

Each full pass over the workspace writes both files into a Git object
store, so it hashes and deflates 130 MB. One test run makes about eight
passes: the test's `before` snapshot, the re-home `before` snapshot, the
capture, the import into the shared clone, the apply, the two
verification snapshots, and the test's final snapshot. That is about 1
GB of SHA-1 and zlib work, plus the byte comparison.

I timed the phases with the re-home hooks on an idle machine: snapshot
0.6 s each, capture and import 1.9 s, worktree add and apply 0.9 s,
verification 1.0 s. With 2 busy processes per core the same phases took
1.3 s, 3.9 s, 1.3 s, and 2.1 s. The work is CPU-bound and scales with
load. No phase waits on a timer, a lock, or a background task. The 15000
ms default is the cause.

## What Changed

- Give `re-homes a workspace whose changes exceed the default restore
point limits` an explicit 60000 ms test budget.
- No other test changes. No production changes. No retries, no skips.

## Verification

Red, on master, 72 busy-loop processes:

```
× ... re-homes a workspace whose changes exceed the default restore point limits
  → Test timed out in 15000ms.
```

Green, with this change, same load:

```
✓ ... re-homes a workspace whose changes exceed the default restore point limits 30414ms
```

So the test needs about 30 s at 4 processes per core, which is half of
the new budget. A hung re-home still fails inside 60 s.

Full file, idle: 28/28 tests pass.

```sh
cd server
pnpm exec vitest run src/__tests__/isolated-repository-rehome.test.ts
```

## Risks

- Low risk. One test budget changes.
- A real slowdown in the re-home path of up to 4 times would no longer
fail this test. Other tests in the file keep the 15000 ms default.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits) and extended reasoning.

## Checklist

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

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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:31:00 -06:00
DottaandClaude Opus 5.5 9668e73b4b fix(native): continue a turn automatically when reattachment loses its runner (#19399)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Native runs in managed sandboxes recover through reattachment: after
a server restart, and for a same-run retry when a turn ends without a
semantic result
> - Reattachment fails closed when the original runner or sandbox is
gone (`native_remote_recovery_lease_mismatch`,
`runner_remote_process_identity_unavailable`)
> - Saved-work recovery then saves the files, but the task stays blocked
with "Saved work recovered. Send a message to continue." The recovered
hold offers no Retry
> - A restart, deploy or maintenance window must not need a user message
to continue work
> - This pull request requests the task Retry automatically, once, when
it is safe, and offers Retry on the recovered hold when it is not
> - The benefit is that interrupted work continues after infrastructure
events, and a user who must act gets a Retry that works on first use

## Linked Issues or Issue Description

**What happened?**
A native task in a managed sandbox was interrupted. In one case a server
restart claimed a queued user-comment turn for reattachment. In another
case a turn completed without a semantic result, and the same-run retry
claimed the run for reattachment with no restart. In both cases the
original runner or sandbox lease was gone. The run failed with
`native_remote_recovery_lease_mismatch` or
`runner_remote_process_identity_unavailable`. Recovery saved the work.
The task stayed `blocked` with "Saved work recovered. Send a message to
continue." The execution blocker had `canRetry: false`. A manual `POST
/api/issues/:id/retry` started a run that succeeded.

**Expected behavior**
After an infrastructure interruption, the task continues automatically
when the interrupted turn's outcome is known. Otherwise the task offers
a Retry that works on first use.

**Steps to reproduce**
1. Start a native task in a managed sandbox from a user comment.
2. Lose the sandbox lease or the remote runner process (for example,
restart the server during a maintenance window).
3. Let reattachment fail and saved-work recovery complete.
4. See that the task waits for a new message and offers no Retry on the
execution blocker.

**Root cause**
Reattachment (`reattach_remote_runner`) correctly refuses to replace a
missing runner or allocation. Nothing then continued the user's request.
The automatic continuation rule only allows a new user turn after
reconciliation. `getExecutionBlocker` hid Retry whenever a workspace
restore hold existed, also after the files were verifiably recovered.
The same-run retry path also re-enters the restart-recovery owner, so
its event said "after server restart" with no restart.

## What Changed

- New `server/src/services/heartbeat/restart-interrupted-turn.ts`: after
a reattachment claim executes, if the run failed because its runner or
allocation was lost, request the same task Retry that `POST
/issues/:id/retry` creates. It runs once per run (idempotency key
`restart-interrupted-turn:<runId>`), under the authority of the user who
sent the message, assigned the task, or clicked Retry. It requires that
the run recorded no provider turn or that its last provider turn is
settled. System wakes, in-flight turns, and a Retry that this hook
already requested do not continue automatically.
- `heartbeat/recovery.ts`: call the hook after each reattachment claim.
Label same-run retry reattachment as "Reattaching to the existing remote
runner to resume this run", and add `trigger: "same_run_retry"` to the
event payload.
- `execution-blocker.ts`: a failed run whose saved work is verifiably
recovered now offers Retry (`canRetry: true`) with "Saved work
recovered. Retry or send a message to continue." An intentional Stop
keeps "send a message" only.
- `task-retry.ts`: optional `initiatedBy: "restart_recovery"` records
the activity as actor `system`/`restart-recovery` with
`onBehalfOfUserId`.
- `doc/execution-semantics.md`: document the rule.

## Verification

- New regressions in `server/src/services/workspace-recovery.test.ts`
use the real recovery, Retry and dispatch services on embedded Postgres:
- `continues a user turn once ... after $title and recovery saved the
work` (3 cases: restart lease loss before the turn, same-run retry with
the runner gone after a settled turn, same-run retry with the lease gone
after a settled turn). Each asserts one queued successor bound to the
Retry and the user's authority after recovery, idempotent hook calls,
system activity attribution, and no second automatic attempt for the
automatic Retry's own failure.
- `does not continue automatically when $title, and offers a Retry that
works on first use` (in-flight turn, system wake, unrelated error). Each
asserts no successor, `canRetry: true` on the blocker and the
interruption presentation, and one successor after one Retry click.
- Red before the fix: all 6 tests fail (no successor, blocker `canRetry:
false`). Green after the fix.
- `npx vitest run src/services/workspace-recovery.test.ts
src/services/task-retry.test.ts
src/services/task-interruption-presentation.test.ts
src/services/execution-blocker-read.test.ts
src/services/heartbeat/recovery.test.ts
src/services/explicit-native-continuation.test.ts
src/services/quarantined-task-retry.test.ts
src/__tests__/workspace-restore-repair-route.test.ts
src/services/native-runtime/native-failed-workspace-recovery.test.ts`:
1098 passed.
- Not verified on staging. After the next deploy, check a task
interrupted by a restart: it should continue without a message, or show
Retry.

## Risks

- The automatic Retry uses the original user's authority. Admission
rechecks that user's current permission, stop proof, saved-work
recovery, approvals, pauses, budget and ownership. A refused request
leaves the visible Retry.
- A settled turn that ended without a result is continued with a fresh
Retry turn from saved work. A turn whose outcome is unknown (in flight)
is not.
- The hook runs after the claim's execution returns. If the server stops
between the failure and the request, the task shows Retry instead.
- Not in scope: same-run disposition recovery still cannot start a new
runner in the existing sandbox after the original remote runner exits.
This change continues the task with one fresh Retry turn instead. If
that Retry turn also ends without a result and loses its runner, the
task shows Retry.

## Model Used

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

## Checklist

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

Related: #19355

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:29:46 -06:00
DottaandClaude Opus 5.5 e7fb554647 test(server): load setup-token route modules during suite setup (#19398)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The setup-token login route lets an owner sign in to Claude for a
sandbox environment
> - The route suite `server/src/routes/setup-token-route.test.ts` fails
on CI in pull requests that do not touch it
> - The first test in the suite times out at 15000 ms, and the other 29
tests pass
> - The first test pays the one-time import cost of the agent route
graph inside its own test budget
> - This pull request moves that import to suite setup, which has a
30000 ms hook budget
> - The benefit is a stable server shard with no change to what the
tests prove

## Linked Issues or Issue Description

**What happened?**

A survey of failed `PR` workflow runs found intermittent failures of one
test in pull requests that do not touch this area:

```
FAIL src/routes/setup-token-route.test.ts > company-and-environment setup-token route — full path > drives start, status, prompt, code, and completion with no agent id
Error: Test timed out in 15000ms.
```

Examples: jobs 127423956746 and 127457298585 (shard
`general-server-without-chat` 12/12). The survey counted 5 such
failures.

**Expected behavior**

The full-path test passes on every run. The test budget measures the
route behavior, not the module import.

**Steps to reproduce**

Run the unchanged suite on master while all CPU cores are busy:

```sh
cd server
node load.mjs 24 pnpm exec vitest run src/routes/setup-token-route.test.ts --reporter=verbose
```

`load.mjs` starts 24 busy-loop Node processes and runs the command. The
first test fails at 15005 ms.

**Root cause**

`createApp()` called `vi.importActual("../routes/agents.js")` and four
other modules on each call. The first call loads and transforms the full
agent route graph. I timed it: `createApp()` took 11670 ms of the 11698
ms test on an idle machine. The HTTP steps took less than 30 ms. On a
loaded CI runner the import alone crossed the 15000 ms test budget.
Later tests reused the module cache, so only the first test failed.

## What Changed

- Add `loadRouteModules()`, which imports the route and logger modules.
- Call it once in the suite `beforeAll`, after `vi.resetModules()` and
the mock registration. The hook budget is 30000 ms in
`server/vitest.config.ts`.
- `createApp()` now reuses the loaded modules. Each test still builds a
fresh Express app, a fresh transport, and a fresh log capture.
- This follows the pattern in
`server/src/__tests__/agent-skills-routes.test.ts` (#16396).
- No timeout changes, no retries, no skips.

## Verification

Red, on master, with 24 busy-loop processes on an 18-core machine:

```
× ... drives start, status, prompt, code, and completion with no agent id 15005ms
```

Also red on an idle machine on 1 of 3 runs (15006 ms; the other runs
took 11790 ms and 13748 ms).

Green, with this change:

- Idle, 3 runs: 30/30 tests pass. The first test takes 42 to 46 ms.
- With 24 busy-loop processes: 30/30 tests pass. The first test takes 40
ms. The file takes 19.4 s in total.

```sh
cd server
pnpm exec vitest run src/routes/setup-token-route.test.ts --reporter=verbose
```

`tsc --noEmit -p server` reports no errors in this file.

## Risks

- Low risk. The change touches one test file only.
- The import cost now counts against the 30000 ms hook budget. Locally
it is 12 to 15 s idle. The mocks register before the import, as before,
so each test sees the same mocked services.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits) and extended reasoning.

## Checklist

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

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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:23:35 -06:00
DottaandPaperclip da34bcd48d fix(workspaces): resume archived imports from their saved upload (#19396)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Durable workspaces let a replacement web server restore saved task
files.
> - Legacy workspace imports keep a journal and an encrypted upload
receipt across interruption.
> - An archived Git import can rebuild a missing source from its saved
task commit and edits.
> - A fresh clone can also contain newer remote refs. Those refs change
the archive manifest even when the task files are unchanged.
> - This pull request recovers the exact authenticated upload for an
interrupted archived Git import.
> - The original journal, source binding, task binding, and execution
checks still control publication.

## Linked Issues or Issue Description

**What happened?**
An archived Git workspace import stopped after its upload persisted. A
retry rebuilt the source from the remote repository. New remote refs
changed the archive manifest, so the original upload receipt rejected
the retry with `remote_workspace_snapshot_operation_content_changed`.

**Expected behavior**
The retry must recover its original saved upload and complete the same
import. It must keep the saved task commit, staged edits, unstaged
edits, lifecycle state, and task binding. A changed source or task
binding must still block publication.

**Steps to reproduce**
1. Archive a Git task with staged and unstaged edits, save its
restoration recipe, and remove its local repository.
2. Start the workspace import and lose the upload reply after object
persistence, or interrupt its independent restore after the upload is
acknowledged.
3. Advance the remote default branch and add an unrelated branch.
4. Retry the original import. Before this change, both interruption
cases fail with the same content-change error.

**Paperclip version or commit**
Reproduced on master at `d8b0eae68772a3bd2bb4a8163b5ce95764b97cff`.

**Deployment mode**
Cloud workspace migration with external PostgreSQL and durable encrypted
object storage. The regression tests use real disposable PostgreSQL, an
HTTP Git remote, and encrypted filesystem-backed object storage.

Related merged work: #18826 and #19138. No duplicate open fix was found.

## What Changed

- Add scoped recovery of an acknowledged snapshot or a pending upload
whose object persisted. Authenticate the archive before returning it.
Recovery alone does not publish the snapshot.
- Resume archived Git imports from those exact saved bytes. Keep the
original operation and all publication checks.
- Lock and recheck each saved nested source’s company, project, and
repository identity in the final publication transaction.
- Keep mutable local sources on the existing capture and
content-comparison path.
- Add regressions for both interruption points, unrelated remote
changes, source revocation during recovery, and local edits after
interruption.
- Document the retry behavior.

## Verification

- Before the repair: both new archived retry regressions fail with
`remote_workspace_snapshot_operation_content_changed`; the
mutable-source control passes.
- After the repair: all 204 tests in the migration, snapshot storage,
archive, and archive environment suites pass. The suites use real
PostgreSQL and encrypted storage.
- Server package typecheck and full repository typecheck pass on
`bffebbab60edbdf50b42fc37a9e58beb01ceb94d`.
- An added source-revocation test initially expected a later expiry
error. The existing publication fingerprint rejected the change first.
The test now requires that earlier
`remote_workspace_migration_publication_changed` error and still checks
that authority was not published.
- The review finding was reproduced before the safety repair: changing a
saved nested repository URL after upload still allowed publication. The
new regression requires rejection, and unchanged approved nested sources
still restore.
- Full repository typecheck and build pass on the final head. The full
local test command stopped at one Slack manager authorization fixture
with `socket hang up`; the exact case then passed in isolation on the
same head, and all three CI chat shards passed. The original local
failure is retained.
- The first CI server shard run had 4,724 passes and one runtime-process
readiness failure in an unchanged fixture. The exact fixture passed in
isolation. One unchanged rerun of that shard passed. The first failure
remains recorded in the workflow history.
- The earlier candidate’s full local test run was stopped after the
confirmed review finding; that interrupted run is not reported as a
pass.
- All CI gates, including `ci / verify` and `ci / e2e`, pass on
`bffebbab60edbdf50b42fc37a9e58beb01ceb94d`. Greptile is 5/5 on that
head, with no remaining findings or unresolved review threads.

## Risks

Recovery applies to archived Git sources, whose saved recipe and
lifecycle checks still run before publication. A source change during
recovery must keep the snapshot unpublished. Missing or corrupt saved
objects still fail authentication or retain the existing import
obligation. Mutable local sources still reject changed contents. This
change adds no database migration and changes no saved archive format.

## Model Used

OpenAI GPT-6 in Codex, with code editing, tool use, and test execution.
The exact runtime model identifier and context-window size are not
exposed in this session.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-05 22:19:20 -06:00
DottaandClaude Opus 5.5 5c185db77c fix(tree-control): resume work a subtree pause interrupted (#19394)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Task tree control lets the board pause a task tree and then resume
it.
> - A pause stops the runs that are active in the tree. The release must
then continue the work that was pending.
> - A child task whose run was active at pause time did not start again
after release. The child became blocked and asked for a user message.
> - A pause is an operator hold, not a failure. It must not strand work,
and it must not require reconciliation for a run that never started.
> - This pull request re-dispatches interrupted work one time on
release, and it settles a legacy Stop that never entered the adapter.
> - The benefit is that pause and resume keep a task tree moving without
manual recovery, and real safety gates stay in force.

## Linked Issues or Issue Description

**What happened?**

A parent task delegated two children. One child's run was active (in
host workspace preparation, before the adapter started) when the board
paused the parent's tree. The pause cancelled that run. After the board
released the hold, no new run started for that child. The child became
`blocked` with the execution blocker cause
`legacy_execution_requires_reconciliation`. Later the next action became
"Send a message to continue." A sibling child that had no active run at
pause time resumed normally.

**Expected behavior**

A run that a subtree pause cancels is an operator hold. On release, the
pending work starts again one time, automatically. It does not go
through execution reconciliation when the run never entered its adapter.
It does not wait for a user message.

**Steps to reproduce**

1. Create a parent task and a child task that is assigned to an agent.
2. Wake the child. While its run is in host preparation (or queued),
call `POST /api/issues/:parentId/tree-holds` with `{ "mode": "pause" }`.
3. Call `POST /api/issues/:parentId/tree-holds/:holdId/release` without
`wakeAgents`.
4. Before this change, no new run starts, and `getExecutionBlocker`
reports `legacy_execution_requires_reconciliation` for the child.

**Root cause**

- (a) The pause records each interrupted run in the hold member snapshot
(`activeRunId`). Nothing used that record on release.
- (b) Release woke tasks only when `metadata.wakeAgents === true`.
Without it, release relied on stranded-issue recovery. That recovery
stands down when the latest run was cancelled by an operator
(`isOperatorCancelledRun`). So the interrupted child had no owner.
- (c) The pause Stop of a legacy run in preparation is written directly
as `cancelled`. Legacy terminalization classifies a cancelled run with
no bootstrap evidence as an unknown outcome. It records
`legacy_execution_requires_reconciliation`, although the gated dispatch
never entered the adapter.
- Native runs: the verified native Stop does not create the
reconciliation hold, but gap (b) applies to native runs too. The release
change is runtime-independent.

## What Changed

- `issueTreeControlService.findOwedPauseResume` decides if a task is
owed one resume. The task's latest run must be an operator Stop that a
pause hold member snapshot names, recorded while that hold was active.
The task must still be executable by the same agent, and no pause may
still cover it. The obligation belongs to the run, so overlapping pauses
keep it until the last covering release.
- New `services/issue-tree-resume.ts` re-dispatches owed work with wake
reason `issue_tree_resumed` (`holdId` and `interruptedRunId` in the
context). A compare-and-set claim (`treeHoldResume`) on the interrupted
run makes the resume happen at most once. A failed wake returns the
claim.
- Pause release and `mode: "resume"` call it for every member of the
released hold, also without `wakeAgents`. When `wakeAgents` is true, the
existing loop wakes every executable member.
- A task that still has an execution blocker is not replayed. The
release writes `issue.tree_resume_wake_skipped`, the blocker stays
visible, and the obligation stays unclaimed.
- `releaseHold` updates only an `active` hold. A repeated release gets
`409`.
- `settleCancelledLegacyPreparation`: when the executor that owned a
cancelled legacy run proves that dispatch never entered the adapter, it
records bootstrap evidence and resolves that run's
`legacy-execution:<runId>` hold as `false_positive`. It also corrects an
automatic no-replay disposition and restores a `blocked` status only
when that disposition made the change. Then it re-dispatches owed work,
for a release that happened while the Stop was unwinding. It requires a
requested Stop with no existing recovery receipt, and no process,
session, native finalization, unreleased lease, or execution-start
event.
- `settleUnrecoverableExecutions` waits while a legacy executor still
holds its controller lease, and records a `legacyCancellationBlock`
receipt when it blocks a task for a cancelled legacy run.
- Docs: `doc/execution-semantics.md` and `doc/composer-stop.md` describe
the resume rule.

## Verification

- New regression
`server/src/__tests__/issue-tree-pause-resume-run.test.ts` uses embedded
Postgres, the real heartbeat, and the real tree-control routes:
- A run paused during preparation: nothing runs during the hold, there
is no blocker, and release creates exactly one `issue_tree_resumed` run
that succeeds. A second release returns `409` and creates no run.
- Release before the stopped preparation finishes unwinding (with the
periodic disposition run in between): exactly one resumed run after the
executor settles.
- An automatic no-replay disposition that ran first: the blocker clears,
the status returns to `in_progress`, and release resumes once.
- Overlapping child and parent pauses: no resume while the parent pause
remains, then exactly one resume after it is released.
- A queued run cancelled by the pause: release creates exactly one
resumed run.
  - A task reassigned to a user during the hold: no resume.
- A mid-turn legacy Stop with unknown outcome: no replay, the
reconciliation blocker remains, and `issue.tree_resume_wake_skipped` is
logged.
- Red before each fix: the first 4 cases failed on master; the 3 review
cases failed on the first PR commit. All 7 pass now.
- Also passed locally: `issue-tree-control-routes`,
`issue-tree-control-service`, `heartbeat-workspace-busy`,
`heartbeat-undispatched-execution-wait`, `execution-control-work`,
`legacy-workspace-restore-recovery`, `legacy-continuation-authority`,
`task-retry`, `legacy-execution-recovery`, `agent-conversations`,
`heartbeat-retry-scheduling`, `satisfied-connection-intents`.
- Not verified here: a live staging pause/resume. Re-check on staging
after deploy.

## Risks

- Behavior change: release now starts the work that the pause
interrupted, also without `wakeAgents`. Before, that work stayed idle.
Other idle tasks still wake only with `wakeAgents`.
- The settlement trusts the executor's adapter-entry flag. The flag is
set only inside the atomic dispatch gate, which refuses a cancelled run.
The settlement also requires no process, session, lease, or execution
event.
- A native run whose Stop has not finalized at release time is not a
target. Stranded recovery and explicit wake still apply.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edit, test execution).

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:08:01 -06:00
DottaandClaude Opus 5.5 6ca7b151f7 feat: compact an agent's context from the task Properties panel (#19378)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A task's Properties panel shows the assignee agent's context window
(paperclipai/paperclip#19273)
> - The board can see that the context is nearly full, but it cannot act
on it
> - A full context makes the agent compact at an unplanned time, often
in the middle of work
> - This pull request adds "Compact now" to the Context hover card
> - The benefit is that the board can compact the agent's context
between runs, at a time it chooses

## Linked Issues or Issue Description

Refs paperclipai/paperclip#19273

**Problem**
The Context row shows how full the agent's context window is. The board
cannot ask the agent to compact it.

**Proposed solution**
A "Compact now" button in the Context hover card. It runs the harness's
own compaction: Codex `thread/compact/start`, and Claude's `/compact`
command through ACP.

**Alternatives considered**
A separate PRP `session.compact` command. Both harnesses run compaction
as a turn, so a compaction turn keeps the existing durable turn binding,
interruption, and recovery. A separate command needs a second turn
lifecycle.

## What Changed

- UI: the Context hover card shows Compact now with three states:
available, Compacting…, and unavailable with a reason (agent mid-run,
harness unsupported, task closed). The Storybook story shows each state.
- Server: `POST /api/issues/:id/context/compact` (board only,
`decideIssueAccess` `issue:mutate`, activity log
`issue.context_compaction_requested`). It queues a short run marked by
`contextSnapshot.contextCompactionRequestId`. `GET
/issues/:id/context-usage` returns the `compaction` state.
- Server: a compaction run does no task work. The native status arbiter
preserves the task status. The lifecycle skips handoff, liveness
continuation, review-path disposition and the issue comment, as it does
for goal-control runs.
- Runner TS: `startTurn({ compact: true })`. The Codex harness session
sends `thread/compact/start`. The runnerd transport maps it to PRP
`turn.start` with `compact: true` (Codex) or `text: "/compact"`
(Claude/ACPX). The native runtime option `sessionCompaction` requires a
resumed session and settles with a synthesized yielded result.
- Runner Rust: `turn.start` accepts `compact: true` for Codex.
`CodexProvider::start_compaction_turn` sends `thread/compact/start` and
binds the turn from the `turn/started` that follows the empty response.
- Fix (from #19273): Codex emitted `context.compacted` on both the start
and the end of one compaction item. It now emits it once, and the count
is distinct by `compactionId`.
- Fix (from #19273): the conversation event view dropped
`context.compacted`, so a finished run never showed the divider. The
view now keeps compactions, plus context readings for runs that
compacted. A finished run's compactions show as thread dividers.
- Fix: the Codex harness stamps before/after sizes on the compaction
event. Codex reports the compacted size before the compaction item ends,
so the old neighbour-based guess showed the wrong size.

## Verification

- `pnpm -r typecheck`, `pnpm build`, `pnpm check:token-gates`, `node
scripts/storybook-catalog.mjs`: pass.
- New tests: `server/src/__tests__/context-compaction.test.ts`,
`status-arbiter.test.ts`, `issue-context-usage.test.ts` (duplicate
compaction counts once), `ui/.../context-usage-row.test.tsx`,
`native-session-runtime.test.ts` (context compaction), Codex driver and
runnerd transport tests, Rust
`codex_compaction_turn_binds_the_turn_reported_after_its_empty_response`.
Rust `codex_provider` suite: 115 passed.
- `pnpm test:run` locally: one failure in
`chat-channels.integration.test.ts`. It reads
`.claude/skills/slack/SKILL.md` from the main checkout, which is not
present in this worktree. It is not related to this change.
- Live, local worktree instance, Codex agent on the Paperclip runner:
clicked Compact now. The run succeeded. Context went from 35k to 9.4k
tokens, and later 9.4k to 9.2k. The task stayed In Review. The thread
shows "Context compacted · 9.4k → 9.2k tokens" and the meter dropped.
- Live Claude proof: pending. The local Claude agent needs an Anthropic
API key. claude-agent-acp handles `/compact` as an echo-less turn, and
the runner already maps its "Compacting completed." notice to
`context.compacted`.

## Risks

- A compaction run resumes the agent's session. If the session cannot
resume, the run fails with `native_context_compaction_unavailable` and
the task is unchanged.
- Compact now is not available while the agent runs on the task, because
compaction interrupts the active turn.
- The conversation view now returns context readings for runs that
compacted. Runs that never compacted return the same events as before.
- An old runner binary ignores `compact` and rejects the `turn.start`
because it has no text. It does not start an ordinary turn.

## Model Used

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

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:07:12 -06:00
DottaandClaude Opus 5.5 240d46e2ec fix(native-runtime): join the deferred observer seen when a duplicate append arrives (#19397)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The native runtime writes runner events through
`PaperclipControlPlanePort.appendEvent`.
> - A terminal event can defer its post-commit observer. A duplicate
append of the same event must join that observer. It must not run
`onDuplicateEvent`.
> - The duplicate looked up the observer only after its own event
transaction finished. If the original observer settled during that
transaction, the observer left the map. Then the duplicate ran
`onDuplicateEvent`.
> - This made the test "joins the exact deferred observer on duplicate
append..." fail on slow CI (job 127633060467, and once on Windows).
> - This pull request captures the pending observer when the duplicate
arrives. The join decision no longer depends on database latency.
> - The benefit is a deterministic join and a stable test.

## Linked Issues or Issue Description

**What happened?**
The server test `paperclip-control-plane-port.test.ts > joins the exact
deferred observer on duplicate append and replays it after a new port
opens` failed intermittently with `Error: must join the original owned
observation`. The test gives the duplicate append 30 ms and then
releases the original observer. On a slow runner, the duplicate's event
transaction took longer than 30 ms. The original observer settled and
its `.then` handler removed the map entry. The duplicate then found no
observer after its transaction and ran `onDuplicateEvent`. (The CI
failure took 90 ms.)

**Expected behavior**
A duplicate append that arrives while the original deferred observer is
pending joins that observer. Database latency does not change this
result.

**Steps to reproduce**
1. Add a 60 ms sleep inside the `appendEvent` transaction when the
receipt disposition is `duplicate`.
2. Run `pnpm vitest run
src/services/native-runtime/paperclip-control-plane-port.test.ts -t
"joins the exact deferred"` from `server/`.
3. The test fails with `must join the original owned observation`. This
is the same error and stack as CI.

**Paperclip version or commit**
`master` at d8b0eae687.

## What Changed

- `appendEvent` now reads the pending deferred observer before it opens
the event transaction. After the transaction, it uses that observer. If
the duplicate did not see an observer on arrival, it reads the map again
after the transaction, as before.
- A new regression test holds a `FOR UPDATE` lock on the run row from a
second transaction. This forces the duplicate append to wait in
persistence while the original observer settles. The test then checks
that the duplicate joined and that `onDuplicateEvent` did not run. No
sleeps or product hooks are necessary.

## Verification

- Red: without the product change, the new test fails (`expected 1 to be
+0`: `onDuplicateEvent` ran once).
- Green: with the change, `cd server && npx vitest run
src/services/native-runtime/paperclip-control-plane-port.test.ts` passes
95/95. The two "joins the" tests pass on 5 consecutive runs.
- Injected 60 ms delay on the duplicate transaction: the original test
fails without the fix and passes with the fix. The delay was local only
and is not in this PR.
- `pnpm --filter @paperclipai/server typecheck` passes.

## Risks

- Low risk. The change affects only a duplicate append that arrives
while a deferred observer for the same event is pending. Such a
duplicate now always joins that observer. Before this change, it joined
only when its transaction finished first. If the original observer
rejects, the joined duplicate gets the original rejection, as a fast
duplicate did before. Committed appends and duplicates with no pending
observer behave as before.

## Model Used

- Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, used through
Claude Code with tool use (shell, file edits, test execution).

## Checklist

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

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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
canary/v2026.1106.0-canary.3
2026-11-05 21:51:47 -06:00
Nicky Leach 879140cbbd Retain ACPX read events before idle sidecar replacement (#19290) 2026-11-05 19:34:59 -08:00
DottaandClaude Opus 5.5 958f9aa9f3 feat(issues): limit agent-created task trees to parent and child (#19312)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents split work into tasks. A task can have a parent task.
> - Today agents can nest tasks to any depth: parent, child, grandchild,
and deeper.
> - Deep agent-made trees are hard to follow, and an agent that works on
a child task makes grandchildren by default.
> - This pull request limits agent-created trees to two levels: a
top-level task and its children.
> - A request for a grandchild does not fail. The server puts the new
task beside the requester and tells the agent.
> - Board users keep full control. They can still nest to any depth.
Existing deep trees stay valid.
> - The pull request also records which task created each new task, so
the audit trail stays complete when the parent changes.

## Linked Issues or Issue Description

No public issue exists. This is the feature description. Related: #16853
(budgets that include descendant work) also reasons about task trees;
this change does not alter its traversal.

**Subsystem affected**

Issues (tasks): create, child create and move in the issue service and
routes. Agent tools, chat suggestions and creation provenance use these
paths.

**Problem or motivation**

Agents create subtasks as deep as they want. An agent that runs on a
child task gets that child as the default parent, so its follow-ups
become grandchildren. The trees become deep and hard to read. The parent
link is also the only record of which task created a follow-up, so that
record is lost when the parent changes.

**Proposed solution**

Limit agent-created trees to a parent and its children. When an agent
asks for a grandchild, put the new task under the requester's parent and
return `parentAdjusted`. Do not reject the request. Keep board users
unrestricted. Record the originating task in a new server-set
`origin_issue_id` column. Keep `origin_run_id` as the originating run.

**Alternatives considered**

Reject grandchild requests with an error. We did not do this, because
agents then fail mid-task. Add a database constraint. We did not do
this, because existing deep trees must stay valid and nesting can return
later.

## What Changed

- Add `MAX_AGENT_TASK_HIERARCHY_DEPTH` (2) and the
`IssueParentAdjustment` type in `packages/shared`.
- Add `resolveNewTaskParent` and `planIssueParentChange` in
`server/src/services/issues.ts`. `create`, `createChild` and `update`
use the agent limit by default (`parentDepthPolicy: "flatten"`).
- The issue create, `/children` and PATCH routes pass `allow_nested` for
board users. For agents, the routes check authorization against the
parent that the server actually writes.
- The requester keeps its behavior when the server moves a task beside
it: `blockParentUntilDone` blocks the requester, and the new task
inherits the requester's workspace and privacy.
- The server never moves a task above a low-trust boundary root that
contains the requester.
- These subsystems keep any depth, because they find their work by
parent: watchdog reviews, monitor recovery, email-channel placement, and
routines that a person configured.
- An agent move of a task that has children skips only the parent change
and returns `reason: "has_children"`.
- Add `issues.origin_issue_id` (nullable, `on delete set null`, indexed)
in migration `0434`. Only the server sets it. The `createdFrom` detail
field, the `createdFromIssueId` filter and task-creation provenance read
it first.
- Chat prompts now let a chat agent create one task, a task with
children, or separate tasks. The conversation is never a parent.
- The runner `create_task` tool accepts an optional `parentTaskId`. A
chat agent can create a top-level task and then put subtasks under it.
The run must be able to read and change the parent. A conversation is
never a parent. The depth limit still applies. Project tools map the
field to `parentId` through the issue route. The protocol manifest,
semantic contracts and eval fixture are regenerated.
- Watchdog runs that have an active watchdog keep their requested parent
on all create paths, because their access is limited to the watched
subtree.
- Fix a bug that the test drive found: the chat (MCP) `create_task`
ignored the requested `status`, so a `backlog` task became `todo` and
started to run. It now uses the same rule as the native tool.
- Update `skills/paperclip`, `doc/SPEC-implementation.md`, and add the
plan `doc/plans/2026-11-05-two-level-task-hierarchy.md`.

## Verification

- `cd server && npx vitest run
src/__tests__/agent-task-hierarchy-depth.test.ts
src/__tests__/agent-task-hierarchy-depth-routes.test.ts`: 19 new tests
for service and route behavior, all pass.
- Related suites (64 files: issues service, thread interactions,
privacy, watchdogs, provenance, runner tool authority, decisions,
routines, project tools): 1208 tests pass. Two tests changed because of
the intended behavior and pass now. Two `email-channels.integration`
tests fail locally because the test cannot find a bundled skill file
from this worktree path. This change does not touch that code.
- `packages/db`: migration safety, snapshot drift and journal tests
pass.
- Typecheck passes for `server`, `packages/shared`, `packages/db`, `cli`
and `ui` (local `ui` errors come only from generated Storybook catalog
files that are absent).
- Local test drive on an isolated instance with real Claude agents: 29
of 29 use cases pass, including an agent on a child task (follow-ups
placed beside it, with the notice) and a chat that creates a top-level
task with three subtasks.
- I did not run the full `pnpm test:run` or `pnpm build` locally. CI
runs them.

## Risks

- Behavior change for agents: some follow-ups that were grandchildren
are now siblings. The parent of the requester is woken when all its
children finish, which now includes these tasks.
- The runner `create_task` input contract adds one optional field, so
the protocol manifest and the pack contract change. The tool output does
not change: it shows the actual `parentId`, and the activity log records
`parentAdjusted`.
- Migration `0434` adds a nullable column, a foreign key and an index.
It does not backfill rows.
- `IssueCreationSource.run` is now nullable.

## Model Used

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

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 21:22:33 -06:00
Dotta d8b0eae687 test(evals): pin completed timeline command account for full-ten measurement (#19395)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The chat catalog needs fixed source and explicit conditions for each
measurement.
> - The latest timeline run delivered a complete account within its
original deadline.
> - Its assertion missed ordinary exitCode notation and a command with
an omitted subject inside a named worker bullet.
> - The reviewed source corrects those bounded textual checks while
retaining independent history and command proof.
> - This pull request pins that immutable source for one full-ten
measurement.
> - Original failed grades and unresolved Company timing remain visible.

## Linked Issues or Issue Description

Refs: #19392, #19086, #19388.

This follows the prior source pin. The latest timeline response
correctly described requirements, a real failed command, saved pause,
human reassignment, repair and successful verification. Its original
grade still failed one narrative assertion. This pin carries the
reviewed correction without merging the full catalog or its production
overlays.

## What Changed

- Pin source `936c3bbe0e83ca15616254f23d9023b2065fc5fb`, tree
`e5c64d67935497164b098e31e995f87ba7875d06`.
- Advance only the timeline oracle from v11 to v12 in the candidate
snapshot and manifest.
- Retain all 244 current queries, contexts, expected outcomes, model,
credentials, clocks and human/native accounting.
- Record one planned full-ten bounded measurement at native cap 2 and
zero automatic retries.
- Change only the manifest, its target assertion and the exact candidate
descriptor snapshot.

## Verification

- I verified the immutable source branch readback before I published
this pin.
- The new narrative control fails on the parent. The fixed source passes
300 timeline, native-proof and declaration controls, including all 242
lazy declarations with zero fixture callbacks.
- The response from result 5094 is accepted by the local diagnostic. The
original grade is retained as FAILED; this is not a new native PASS.
- Fresh full runner E2E types and the SDK guard over 506 inputs pass in
the qualified source graph. An earlier isolated worktree type attempt
had dependency-alias errors; the clean source qualification supersedes
that setup gap.
- Controller: `node --test
tests/runner-e2e/chat-full-catalog-testops-manifest.test.mjs
tests/runner-e2e/chat-full-catalog-testops-adapter.test.mjs
tests/runner-e2e/chat-full-catalog-testops-reconcile.test.mjs` — 40
controls pass.
- The snapshot equals the committed source descriptor bytes. All 244
rows compare equal to the prior pin except the timeline oracle version.
- Source descriptor SHA256:
`19185aa5298c93d159c3d8547661fd6ef22185e74be1c18b8497bae2ef8051c7`.
- Controller manifest SHA256:
`822494b170139a8204ff36b33891e2f4960ac0aba6800e9856f2d25dd24de7c6`.
- Launcher, protected evidence wire and the accepted 506 SDK inputs
remain unchanged. The source SDK registry stays
`efa70621e9fb42aa185f7ee616a1a726aa3eccb367e67e28817cedf26ef2ade8`.

- Exact pin head `31f47fcdfb600e6b15b35ed50c2a447d03681f06` has 56
successful checks and three configured skips. Both protected checks
pass. Greptile is 5/5 for this head with zero open threads. Paperclip
Review completed neutral at 5/5. GitHub reports CLEAN and MERGEABLE.
These checks do not qualify the unrun native experiment.

## Risks

- Original timeline result 5094 remains FAILED. All three native runs
succeeded and the account was complete in 248,876 ms, but the original
narrative check failed. Local calibration does not rewrite that grade.
- Original Company result 5093 ended without first-CEO completion. Its
model/runtime timing cause remains unresolved. This source makes no
Company runtime or latency fix claim.
- The full-ten measurement is unrun. It is a bounded experiment, not
proof of deterministic timing, reliability or an all-ten pass.
- Company retains two base human inputs plus at most one independently
verified UI approval, minimum two and maximum three native runs, and
480,000 ms. Timeline retains three native runs, one human input and
720,000 ms. No condition is waived.
- Secret journeys still require independent semantic safety review.
Metadata shape alone does not establish that a proposed name is
non-sensitive.
- The interaction-accept and API-search overlays remain separate,
reviewed, unmerged core PRs. This pin does not merge them or the full
catalog. The 196/244 admission gate stays fixed.

## Model Used

OpenAI Codex, GPT-6.1 Sol (`gpt-6.1-sol`), with reasoning, repository
tools and code execution. The host does not expose an exact
context-window size. No model or paid provider was called for local
controls.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Tasks page helps people inspect work across their company.
> - A new viewer currently starts in Mine and can miss other company
tasks.
> - The task table also shows an active sort header before the viewer
selects a sort.
> - This pull request starts new viewers in All tasks and keeps the
default sort header neutral.
> - People see company work in newest-update order and keep their saved
view choices.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The initial task view and the task table's default sort state.

**Subsystem affected**

ui/ — React board UI.

**Current behavior**

Tasks opens Mine when the viewer has no saved view. The table highlights
Last updated even when the viewer has not selected a column sort.

**Proposed behavior**

Tasks opens All tasks when no saved view exists. The table keeps
newest-updated tasks first without an active header or arrow. A column
selection or an explicit sort URL activates the selected header.

**Reason and benefit**

New viewers can inspect company work immediately. The header
distinguishes default ordering from an explicit sort choice.

**Breaking changes**

The first-visit view changes from Mine to All tasks. Saved views and
explicit view URLs keep priority. Existing Inbox and Needs attention
preferences retain their meaning. The change does not alter task access
or database contracts.

**Additional context**

Refs #19363. This change builds on the grouped task menu from that
merged pull request. It uses the existing menu and design tokens.

## What Changed

- Default the Coordinator and combined task surfaces to All tasks.
- Preserve saved personal views and migrate previous picker choices.
- Keep implicit Last updated descending order separate from active
header state.
- Test default ordering, explicit sorting, reset behavior, view
selection, and preference migration.
- Update the product and implementation docs.

## Verification

- Passed 118 focused tests across task routing, table sorting, views,
and collection preferences.
- Passed `pnpm check:token-gates`, `pnpm -r typecheck`, and `pnpm
build`.
- Full `pnpm test:run` is running. Its result will be updated here.
- Greptile passed the current head with 5/5 and no findings or open
review threads.
- Browser check: All tasks uses the grouped My work and Organization
menu. Last updated has no active highlight by default. Selecting the
header adds the sort URL and active direction. Removing the explicit
sort restores the neutral header.
- Manual check: Open Tasks with no saved task view. Confirm All tasks
opens. Select Mine, leave Tasks, and return. Confirm Mine remains
selected.

## Risks

- The new default shows more accessible company tasks on the first
visit. Saved view choices remain intact.
- Task tables share the sort-state change. Explicit sort links and
column clicks retain active styling. Default row order stays
newest-updated first.
- No schema, API, permission, or execution changes.

## Model Used

OpenAI Codex, GPT-6 family. This session does not expose the exact model
ID or context-window size. Used reasoning, code editing, terminal
commands, and browser verification.

## Checklist

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

---------

Co-authored-by: Scott Tong <scott@scottsmbpm5max.lan>
Co-authored-by: Paperclip <noreply@paperclip.ing>
canary/v2026.1106.0-canary.2
2026-11-05 18:44:59 -08:00
Nicky Leach 5ab7b78f2b Runner: retain the provider across hosted park and reclaim (#19140) 2026-11-05 18:40:44 -08:00