Commit Graph
2305 Commits
Author SHA1 Message Date
DottaandPaperclip aa6910939b test(runtime): share the projectless local lease acquisition (#16086)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 23:41:48 -05:00
DottaandPaperclip d6e4b36c8c test(server): move Git redaction proof to its owner (#16081)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 23:35:43 -05:00
76164b53cb fix(heartbeat): retry a legacy run that lost its lease before dispatch (#16358)
## Thinking Path

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

## Linked Issues or Issue Description

Refs #16084
Refs #16355
Refs #16359

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

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

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

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

## What Changed

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

## Verification

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

## Risks

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

## Model Used

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

## Checklist

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

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

---------

Co-authored-by: Devin Foley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:31:42 -07:00
DottaandPaperclip 3ea39f75c1 test: consolidate duplicate owner proofs (#16075)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 23:17:13 -05:00
DottaandPaperclip 5f160421e3 test(server): run status corpus fixtures once (#16078)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 23:13:31 -05:00
8b27af77db fix(heartbeat): wait for a live holder of a reused shared execution workspace (#16371)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The heartbeat pre-dispatch gate decides if a run can use a shared
workspace in its execution environment.
> - A child task can inherit the shared execution workspace of its
parent. The parent run is often still live when the child run starts.
> - On a sandbox environment, the gate failed the child run with
`configuration_incomplete`. It did this for every concurrency policy,
also for the default `auto` policy.
> - Recovery then blocked the task and told the operator to bind missing
secrets. That was not the real cause. An operator had to select a
different workspace by hand.
> - Shared project workspaces already use the `auto`/`serialize` policy
to wait through the `workspace_busy` retry path.
> - This pull request applies the same policy to a reused shared
execution workspace.
> - The benefit: the child task waits for the parent run and then
continues automatically. It does not fail and become blocked.

## Linked Issues or Issue Description

**What happened?**
A parent agent created a child task. The child inherited the parent's
shared execution workspace. The child agent used a sandbox environment.
The child run started about 1.5 seconds after the parent created the
task, while the parent run was still live. The run failed before the
adapter started with this error: "This environment cannot place
concurrent agents in one physical shared folder. Use local/SSH sharing
or isolated task workspaces." Recovery set the task to `blocked` with
the message "required secret/env bindings are missing".

**Expected behavior**
With the default `auto` policy, the child run waits for the parent run
(`workspace_busy`) and then continues automatically. This is the same
behavior as for a shared project workspace on a sandbox.

**Paperclip version or commit**
Staging release `8ffc5748`. The same code is on `master`.

**Deployment mode**
Hosted cloud (staging), agent on a sandbox execution environment.

**Steps to reproduce**
1. Use an agent with a sandbox environment.
2. Bind two issues to the same `shared_workspace` execution workspace.
Set `executionWorkspacePreference: reuse_existing` on the second issue.
3. Keep a live run on the first issue.
4. Wake the agent on the second issue. Before this change, the run fails
with `configuration_incomplete`. After this change, the run is deferred
with `workspace_busy`.


## What Changed

- `environment-selection.ts`: When a reused shared execution workspace
has a live holder on a non-local/non-SSH environment, the gate now reads
`sharedWorkspaceConcurrency`. With `auto` or `serialize`, it throws
`WorkspaceBusyDeferral`. Only `allow` still fails with
`shared_realization_unsupported`.
- `retries.ts`: Added `findExecutionWorkspaceHolder` next to
`findSharedWorkspaceHolder`. It keeps the old match rule. A running
holder that has had no output for more than one hour (the existing
staleness cutoff) is still returned, with `stale: true`. A live holder
is preferred over a stale one.
- `environment-selection.ts`: A stale holder still owns the folder,
because silence does not prove that the run stopped. The child run
therefore fails with `configuration_incomplete`
(`shared_realization_unsupported`, `holderStale: true`). It does not
share the folder with the holder, and it does not wait without limit.
This is the same result as before this pull request, with a more
specific message.
- `retries.ts`: `WorkspaceBusyDeferral` can now carry an
`executionWorkspaceId`. The run result, the run event, and the
cancellation reason name the execution workspace when it applies.
- `stranded-notice.ts`: Added a specific recovery notice for
`shared_realization_unsupported`, and a second notice for a stale
holder. They no longer tell the operator to bind missing secrets.
- `doc/experimental/issue-worktree-support.md`: Documented that `allow`
is only concurrent on local/SSH, and that a reused shared execution
workspace follows the same rules.

## Verification

- `cd server && npx vitest run
src/__tests__/heartbeat-workspace-busy.test.ts -t "reused shared
execution workspace"` covers 4 tests:
- `auto` on a sandbox with a live holder: the run is cancelled with
`workspace_busy`, a retry is scheduled, no Sentry run failure is
reported, and the issue does not become blocked.
- After the holder finishes, the scheduled retry is promoted and runs.
The adapter gets the inherited shared folder, and the issue keeps its
execution workspace.
- `allow` on a sandbox: the run still fails with the shared-folder
message and no retry is scheduled.
- A holder that is still running but silent past the staleness cutoff:
the run fails with `holderStale: true`, the adapter does not start, no
retry is scheduled, and the holder is not touched.
- `npx vitest run src/services/recovery/stranded-notice.test.ts
src/services/heartbeat/environment-selection.test.ts
src/services/heartbeat/retries.test.ts` covers the notice and the
selection unit tests.
- First commit (`d0b7a092`): the 3 original integration tests and the
unit suites passed locally, and all CI checks passed.
- Follow-up commit (`3c7e515`, which addresses the Greptile review):
these tests were **not** run locally. Dependency install was killed
twice by the sandbox's 4 GB memory limit. CI is the first run of the new
and changed tests.
- Known local failure: `does not hide ambiguous, malformed, provider, or
transport cancellations behind workspace_busy` fails in this sandbox
also on unchanged `master` (checked with `git stash`). This change does
not cause it.
- Not run locally: the full server `tsc --noEmit`, for the same memory
reason. CI runs the typecheck.

## Risks

- Behavior change: before, a run with a live holder of a reused shared
execution workspace on a sandbox failed at once. Now it waits with
`auto`/`serialize`. A long parent run can delay the child. If the holder
has no output for more than one hour, the child fails as before instead
of waiting without limit.
- An explicit `allow` policy keeps the old failure.
- Local and SSH environments do not change.
- No schema or migration change.

## Model Used

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

## Checklist

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

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

---------

Co-authored-by: Devin Foley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:45:29 -07:00
DottaandPaperclip 66724b8ce0 perf(auth): project only required run context fields (#16380)
Project only the required JSON fields for saved-run policy and direct-parent report authorization. Preserve current reads, JSON types, identity checks, permission decisions and legacy task-key precedence.

Validation: 186 affected cases; 17 database transfer-budget cases after the shared startup-timeout correction; full typecheck/build; exact-head full CI, E2E, Superagent and Apex 5/5.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 22:45:08 -05:00
DottaandPaperclip 1d5e2c89f0 test(server): retire duplicate guards and share issue seed fixtures (#16059)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Server tests protect task scheduling, session continuation,
transport decisions, and issue data.
> - Four cases repeat the same registered inputs and assertions as
existing owner cases.
> - Issue service tests also repeat 103 database seed statements.
> - Each distinct input, assertion, lifecycle, and security contract
must keep its proof.
> - This pull request removes the four exact duplicates and shares only
exact seed statements.
> - The benefit is 584 fewer test lines and four fewer registered
executions.

## Linked Issues or Issue Description

Refs #16047. This draft is stacked on
`codex/test-audit-round-six-delivery` at
`e868e6645ce0d598d756d3458f0b3d71afd6db4a`.

**What existing behavior does this improve?**

Maintenance of server owner tests. Production behavior does not change.

**Current behavior**

Four registered cases repeat identical executable bodies. The issue
service suite repeats 80 company rows, ten project rows, and thirteen
agent rows.

**Proposed behavior**

Keep each contract in its existing owner suite. Use three direct async
seed helpers for the exact repeated database rows. Preserve each
caller's database instance, IDs, awaited insert position, field order,
and fresh object allocations.

**Reason and benefit**

Remove 584 net test lines and four registered executions. No measured
wall-time improvement is claimed. No production, dependency, workflow,
telemetry, or skip change is included.

## What Changed

- Share exact company, Workspace project, and CodexCoder rows in
`issues-service.test.ts`. All 137 registered cases remain.
- Retire `heartbeat-scheduling-suppression.test.ts` → `suppresses
heartbeat scheduling while a task drain is active`. Keeper:
`heartbeat-task-drain.test.ts` →
`start_task_drain_suppresses_admission`.
- Retire `heartbeat-timer-wake-session-reset-pf4.test.ts` → `does not
reset for issue_commented (preserve continuation context)`. Keeper:
`heartbeat-workspace-session.test.ts` → `does not reset for comment
wakes`.
- Retire `setup-token-session.test.ts` → `denies a direct non-loopback
HTTP receive-token request, so it delivers no token (SR-6)`. Keeper in
the same describe: `denies a direct non-loopback HTTP request (SR-6)`.
- Retire `setup-token-session.test.ts` → `denies a spoofed forwarded
HTTPS receive-token request under a broad proxy setting (SR-7)`. Keeper
in the same describe: `denies a spoofed X-Forwarded-Proto when no
trusted proxy is configured (SR-6)`.

The retired receive-token cases only call
`evaluateConfidentialTransport`. They neither request a route nor
configure `TRUST_PROXY`. The keepers protect the exact guard inputs and
output. The route now uses the guard to select a transport advisory;
this PR makes no route-denial or token-delivery proof claim.

## Verification

- Complete matched six-file baseline: **414/414 pass**, no failures or
skips. Candidate: **410/410 pass**, no failures or skips.
- Run: `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/issues-service.test.ts
server/src/__tests__/heartbeat-scheduling-suppression.test.ts
server/src/__tests__/heartbeat-task-drain.test.ts
server/src/__tests__/heartbeat-timer-wake-session-reset-pf4.test.ts
server/src/__tests__/heartbeat-workspace-session.test.ts
server/src/services/setup-token-session.test.ts`.
- Expand the actual three helper bodies at all 103 call sites. The
complete issue-service AST is identical to the original after expansion.
Independent review checked the exact inputs, lexical bindings, awaited
order, and fresh nested configs.
- Deliberate regressions disabled task-drain suppression, forced comment
wakes to reset, and allowed the final insecure transport branch. All
four retained keepers failed at their intended equality assertions. Each
production file was restored byte for byte against HEAD.
- An optional project-inheritance mutation was ineffective because
another workspace path still derives project identity. It passed and is
excluded from accepted preservation evidence.
- `git diff --check` passes. Independent cross-review approved the exact
source and proof before this draft.
- Broad typecheck, full test, and build gates passed in PR CI on
`a9713432b1e498e023b4828ffa283a351abe856f`. All 52 check-runs concluded
success. Greptile completed on that exact head with confidence 5/5 and
no findings.

## Risks

- Low risk: only test code changes. Four exact duplicate bodies are
removed.
- The fixture helpers preserve all original IDs and DB operations. They
do not cache data or generate assertions.
- Same-named explicit-project and derived-project inheritance cases
remain because their inputs differ. Separate login PTY and duplex
lifecycle cases also remain.
- No wall-time speed gain is claimed from fixture line reduction.

## Model Used

- OpenAI GPT-6.1-Sol, exact model ID `gpt-6.1-sol`, through Codex. Tool
use, code execution, and reasoning assisted the audit. The runtime does
not expose its context-window size.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 22:38:16 -05:00
DottaandPaperclip d73d7a8651 perf(skills): batch repository dependency reads (#16377)
Batch dependency Markdown reads through the bounded Git snapshot reader and queue newly discovered dependencies once. Preserve package contents, limits, authorization, audits, and reference order.

Validation: 63 affected cases; full typecheck/build; exact-head full CI, E2E, Superagent and Apex 5/5. Local Git comparison: 502 individual reads reduced to three batches with identical entries.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 22:31:38 -05:00
275189d8d6 fix(native): report why an unclassified Codex turn failed (#16354)
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Native runs use the Paperclip runner to drive a provider session,
for example the Codex app server
> - When a Codex turn fails, two classifiers look for known failures: a
model rejection and a server capacity failure
> - When no classifier matches, the run fails with the generic message
`Native session failed` and the error code `adapter_failed`
> - The run failure report to Sentry then has no data about the cause,
because the server drops the Codex `codexErrorInfo` value
> - This pull request records a closed-vocabulary diagnostic for that
failed turn and sends it to the Sentry `provider_failure` context
> - The benefit is that operators can see which Codex failure kind
causes these generic failures, and then write a targeted fix

## Linked Issues or Issue Description

**What happened?**
Hosted staging reports many run failures with the title `Error: Native
session failed`. The events have `agent_adapter=paperclip_runner`,
`error_code=adapter_failed`, `driverKind=codex_app_server`, and
`exitCode=1`. The events do not show why the Codex turn failed.

**Expected behavior**
A run failure report for a failed Codex turn shows the failure kind, so
that an operator can find the cause. The report must not contain
provider response text, prompts, or model names.

**Steps to reproduce**
1. Start a native Codex run.
2. Make the Codex app server end the turn with `status: "failed"` and an
error that is not a model rejection or `serverOverloaded`. For example,
use `codexErrorInfo: "contextWindowExceeded"`.
3. Look at the Sentry event for the run failure. Before this change, the
`provider_failure` context is empty.

**Agent adapter(s) involved**
Codex (native runner, `codex_app_server` driver).

## What Changed

- `native-provider-failure.ts`: The provider failure observation now
records a diagnostic for each failed Codex turn. The diagnostic has only
closed values: `provider: "codex"`, `category: "turn_failed"`, a known
`CodexErrorInfo` kind (or `unrecognized` / `absent`), an HTTP status
from 100 to 599 for the HTTP variants, and a runner failure code from an
explicit allowlist of the codes the runner itself writes on a failed
Codex turn (any other string becomes `unrecognized`, because a provider
error object can carry its own `code`). It never copies the provider
message.
- New `unclassifiedTurnFailure()` gives this diagnostic only for a
failed terminal turn that `forTerminal()` does not classify.
`forTerminal()`, error codes, error messages, retries, and finalizer
decisions do not change.
- New `readNativeTurnFailureDiagnostic()` rebuilds a persisted value
from the closed vocabulary and rejects all other values.
- `native-session-executor.ts`: The adapter result keeps the generic
message. It adds `resultJson.nativeTurnFailure` when the turn failure is
unclassified.
- `run-failure-diagnostics.ts`: The run failure report copies the
validated diagnostic into the `provider` context. Sentry sends this as
`provider_failure`.
- Tests: unit tests for the vocabulary, HTTP status bounds, unknown
kinds, runner codes, and privacy; a diagnostics test; and an update to
the end-to-end executor test for the `unknown-error` scenario.

## Verification

- `pnpm exec vitest run
src/services/native-runtime/native-provider-failure
src/services/__tests__/run-failure-diagnostics.test.ts
src/services/__tests__/run-failure-report*.test.ts
src/__tests__/sentry*.test.ts` (in `server/`): 6 files, 432 passed, 5
skipped.
- `pnpm exec vitest run
src/services/native-runtime/native-session-executor.test.ts` (in
`server/`): 698 passed.
- I could not run the full `tsc --noEmit` in my sandbox: the memory
limit stopped the process, and the runner vendor build needs `cargo`. CI
runs the full typecheck.
- To check by hand: make a Codex turn fail with an unclassified error.
Then make sure that the Sentry event `provider_failure` context shows
`category: turn_failed` and the `codexErrorInfo` kind, and no provider
text.

## Risks

- Low risk. The change adds diagnostic data only. Run status, error
code, error message, retry, and finalizer logic do not change.
- Privacy: the diagnostic uses an allowlist of Codex enum names, a
bounded integer, and an allowlist of runner-issued failure codes. Tests
make sure that provider text does not get through.
- If Codex adds a new `CodexErrorInfo` kind, the diagnostic shows
`unrecognized` until the allowlist gets the new kind.
- This change does not fix the root cause. It gives the data that is
necessary to find the cause.

## Model Used

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

## Checklist

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

Related: #15347 (capacity classifier), #15304 (model rejection
diagnostics).

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

---------

Co-authored-by: devinfoley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:14:10 -07:00
3a440d28c4 fix(server): read existing grants before seeding role defaults (#16365)
`insertMissingPrincipalGrants` now reads the existing permission keys first and inserts only missing grants. In the steady state no INSERT runs, so cloud-tenant auth resync no longer waits on row locks held by concurrent grant edits until the statement timeout (Sentry PAPERCLIP-BACKEND-3S).

Co-Authored-By: Paperclip <Paperclip-Paperclip@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:01:34 -07:00
ccc77c5b5e fix(server): record legacy controller lease-loss timings (#16359)
## Thinking Path

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

## Linked Issues or Issue Description

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

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

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

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

## What Changed

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

## Verification

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

## Risks

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

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

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with
tool use (shell, file edits, Sentry and GitHub connectors). The exact
context-window size is not exposed in this session.

## Checklist

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

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

---------

Co-authored-by: Devin Foley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:52:23 -07:00
DottaandPaperclip 3dffd2197d test: consolidate owner fixtures and retain real regression proof (#16047)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Tests protect sandbox execution, run recovery, credentials and the
board UI.
> - Several owner suites repeat the same setup and prove the same result
more than once.
> - These copies add maintenance work and can hide weak assertions.
> - Each cut needs a retained test that proves every distinct contract.
> - This pull request consolidates repeated setup and removes twelve
redundant executions.
> - It also replaces two weak assertions with checks of real outputs.
> - The benefit is 2,831 fewer test lines with clearer regression proof.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The maintenance and regression strength of the existing Vitest owner
suites.

**Subsystem affected**

Sandbox adapter utilities, server heartbeat recovery and environment
capabilities, and the board UI.

**Current behavior**

Tests copy database rows, query builders, render calls and runtime
inputs. Some cases repeat an existing contract. One token test omits a
real bridge credential. A PostHog host comparison can compare absent
controls.

**Proposed behavior**

Use small local helpers with fresh values and the same action order.
Keep each distinct contract in an owner test. Check real bridge
credentials and fixed PostHog method states. Carry the exact persisted
first-run success assertion into the stronger cancellation keeper.

**Reason and benefit**

The ten test files shrink by 2,831 net lines. Twelve redundant
executions retire. All other case tables, inputs, security boundaries,
timeouts and cleanup rules remain.

**Breaking changes**

None. This round changes tests and audit evidence only.

**Additional context**

Refs: #15978, the merged first cleanup. Refs: #16022, the merged prior
cleanup. Its squash merge is `dabb8c5daf430a1987af611e2e7ca4c7b23ef7d4`.

Related open work: #14634 repairs feedback fixture completion; #14477
supplies explicit sandbox fixture tools; #14198 changes cleanup retry
behavior in another heartbeat file. This audit preserves those separate
action and cleanup contracts. It does not apply their changes.

This PR was first stacked on `codex/test-audit-followup`. After #16022
was squash merged, only the two Round 6 commits were replayed onto
merged master `dabb8c5daf430a1987af611e2e7ca4c7b23ef7d4`. The original
stack head is preserved in a local snapshot branch. The current diff
contains only ten test files and two evidence files. No merge is
requested.

## What Changed

- Consolidate repeated setup in three sandbox adapter suites. Remove
seven redundant executions and retain the real protocol, cleanup and
scratch-file checks. Save 875 lines.
- Consolidate heartbeat database fixtures and read queries. Retain exact
wake cancellation, total run count and the actual first-run success
receipt. Remove one duplicate execution. Save 1,372 lines.
- Remove two repeated rejected-capability cases. Carry the absent
snapshot assertion into the retained runner case. Save 22 lines.
- Consolidate UI fixtures and render calls. Remove two repeated
connection cases. Replace the weak PostHog comparison with fixed method
and required-key checks in both real hosts. Save 562 lines.
- Record audit scope, decision and expansion counts, all twelve keeper
mappings and deliberate mutation results in the [audit
report](https://github.com/paperclipai/paperclip/blob/codex/test-audit-round-six-delivery/doc/plans/2026-10-12-test-audit-round6.md)
and [controls
evidence](https://github.com/paperclipai/paperclip/blob/codex/test-audit-round-six-delivery/doc/plans/2026-10-12-test-audit-round6-controls.json).

## Verification

Source baseline: `a090035cafb29c06f6fca2dbe43561ad866b7c1b`. All ten
original test programs are byte-identical at the stacked base
`58c5c4aa241c85f4665a3159c192888bbb2a5cfa`.

- Baseline: 1,841/1,841 cases passed in the main thirty-file
owner/sibling run. Another 219/219 cases passed in nine extra files.
- Preservation: all four proposal packages passed independent review.
Whole-program helper expansion retains ordered inputs, callbacks,
awaits, queries, fresh allocations and hooks outside the declared
assertion migrations and repairs.
- Candidate: all 2,048 cases passed across 39 complete owner/sibling
files. The main run passed 1,831 cases. The extra run passed 217 cases.
Both runs had zero failures and zero skips.
- PostHog control: the repaired positive case passed. The original weak
case passed with authentication choices removed. The repaired case
failed at its fixed OAuth aria-checked assertion under the same
mutation.
- Eight accepted mutations failed at their intended assertions. They
cover bridge-token exclusion, fallback cleanup, READY bytes, real
scratch-file preservation, exact wake cancellation, the persisted
first-run success receipt, rejected capability snapshots and reachable
PostHog method controls. Two original weak cases passed with the same
defects. All production source bytes were restored.
- Rejected control: changing run status before legacy release timed out
before wake cancellation. This result is not counted as assertion proof.
Its replacement changed only the persisted receipt after real drain in
the existing transaction. It failed at the direct fixed succeeded-row
assertion.
- No new code comments or 1,000-line threshold crossings were
introduced. No production, schema, lockfile or workflow changes are
included.

On the exact final test sources, later committed as
`d8de9422f0703d4508a9dc880b67a07f13e0f110`, `pnpm -r typecheck`, `pnpm
check:token-gates` and `pnpm build` passed. The required full `pnpm
test:run` completed with **exit 1** after **52.14 minutes** on clean,
immutable `d8de9422f0703d4508a9dc880b67a07f13e0f110`. HEAD and tracked
sources remained unchanged.

The first general-server group ran 889 files: **882 passed, one failed
and six skipped**. Its 18,032 cases were **17,925 passed, two failed and
105 skipped**. The only failing file was
`server/src/__tests__/remove-test-home.test.ts`. On macOS, the read-only
runtime bundle and symlink cleanup cases returned `ENOTEMPTY`. The
permission-error case expects `EACCES` or `EPERM`. Four reported failure
entries cover these two cases, including their cleanup errors. The test
and helper are byte-identical to previous master
`4b766b872993045a2e787404f0475503683e5ded` and audit master
`e364b92c82`.

The default wrapper stopped after that group failed. **Later local
groups did not run.** The earlier public-MCP, HTTP log redaction and
workspace snapshot failures did not recur. The 39 focused owner/sibling
files and all 48 remote CI jobs completed independently. This result is
an unresolved macOS cleanup limitation, not full local success. No test,
skip, retry, timeout or cleanup rule was changed to hide it.

The three adapter files received a whitespace tidy after focused
execution. Full runtime AST and string/template/regex byte equality link
those tested sources to the final sources. All 134 adapter helper calls
were checked again. Replayed code commit
`3d3defd2a60595a623045d9e2c6c586f4acb3452` has the exact final test
bytes. Evidence commit `e868e6645ce0d598d756d3458f0b3d71afd6db4a` adds
the report and controls JSON, with one historical ancestry
clarification. All ten accepted test hashes and all seven
production-owner source guards match. No package, lockfile, workspace,
TypeScript, Rust or Vitest dependency manifest changed since the audit
snapshot.

The [original stack CI
workflow](https://github.com/paperclipai/paperclip/actions/runs/38312876910)
passed all 48 jobs on head `c171d6c0d603d22bb997c1f9a9bdcd5bcc5d22aa`.
Those results are historical stack proof.

The [fresh master-context CI
workflow](https://github.com/paperclipai/paperclip/actions/runs/38318223856)
completed successfully with **all 48 jobs passed** on current head
`e868e6645ce0d598d756d3458f0b3d71afd6db4a`. Its tested merge commit was
`41292af60c6eb60d477637d23822bb17b08dc07e`, combining workflow base
`88bf4c6c1d222f818faa56257efe79207c892a6c` with that head. The delivery
commits remain based on pinned master
`dabb8c5daf430a1987af611e2e7ca4c7b23ef7d4`. Both required master checks,
`ci / verify` and `ci / e2e`, passed. Two separate optional Storybook
checks were skipped by their scope gate.

[Fresh Greptile
review](https://github.com/paperclipai/paperclip/pull/16047#issuecomment-6133323165)
gives the current head **5/5**, with no findings, inline comments or
unresolved review threads. The [current-head commitperclip
review](https://github.com/paperclipai/paperclip/actions/runs/38318223666)
also completed successfully. This PR remains open and draft; no merge is
requested. The local macOS failure above remains unresolved.

## Risks

- A helper could share a value or capture the wrong binding. Independent
expansion and lexical binding checks guard the actual helper calls.
- A retired case could contain a distinct assertion. Each cut has an
exact keeper map and carried observations.
- A mutation could fail before the intended assertion. Controls reject
timeouts and earlier failures. The rejected receipt mutation remains in
the evidence.
- The delivery ancestry changed after the parent squash merge. Exact
source hashes preserve the audited tests and producer receipts. Fresh CI
and review passed in the current master context; the broad local run
remains evidence from its immutable audit snapshot.
- The complete local default failed in two unchanged macOS cleanup
cases. Later local groups were not run. This limitation remains visible
even though focused checks and remote CI passed.

## Model Used

OpenAI Codex, based on GPT-6, assisted with this work. The exact served
model ID and context window are not exposed in this session. It used
repository tools, code execution, AST analysis and parallel audit
agents.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 21:43:12 -05:00
cafa25029c fix(native-runtime): recover unclaimed remote startup (#16349)
## Thinking Path

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

## Linked Issues or Issue Description

**What happened?**

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

**Expected behavior**

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

**Steps to reproduce**

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

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

## What Changed

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

## Verification

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

## Risks

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

## Model Used

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

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Devin Foley <devin@paperclip.ing>
2026-10-14 19:32:39 -07:00
DottaandPaperclip ff0f976de1 fix(native-runtime): retain remote recovery failure reasons (#16341)
Preserve closed marker-read and process-verification reasons through native recovery failures and operator reports. Keep existing transport errors and reattachment ownership checks.

Add shell, process-probe, persistence and privacy regression coverage. All current-head CI checks pass and Greptile is 5/5.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 21:24:02 -05:00
1b58ac6ec9 build(deps): bump smol-toml from 1.8.0 to 1.9.1 (#16020)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Codex runner must set trust for the exact startup project.
> - smol-toml 1.9.1 returns tables with null prototypes.
> - Object spread replaces those prototypes and causes strict validation
to reject valid trust edits.
> - This PR keeps the parsed tables and adds tests for the dependency
update.
> - Codex can start while unrelated settings and trust boundaries stay
intact.

## Linked Issues or Issue Description

Refs #16028. That merged PR contains the same table-preservation fix and
tests for both prototype styles.
This branch retains that work and adds more regression cases.
Related dependency updates: #15293 and #15364.

**What happened?**

Eight startup trust tests fail after the update to smol-toml 1.9.1.
The error is `codex_startup_trust_cannot_preserve_configuration`.

**Expected behavior**

Set trust for the exact startup project.
Keep unrelated configuration bytes and sibling project trust unchanged.

**Steps to reproduce**

1. Check out the original PR head,
`90b133dfd963af4f19b4df11fdd6c11b4eeb2a2d`.
2. Install dependencies with `pnpm install --frozen-lockfile`.
3. In `packages/paperclip-runner`, run `pnpm exec vitest run
src/drivers/codex/codex-startup-trust.test.ts`.

**Paperclip version or commit**

The verified head is `1a8e8ad8b2e1049ebb68595d85c92e3c0621559d`.

**Deployment mode**

Built from source on Linux. The affected code runs before Codex starts.

## What Changed

- Keep Dependabot's update from smol-toml 1.8.0 to 1.9.1 in the runner
and server.
- Keep parsed project tables. Use `parse("")` to create missing tables
with the parser's prototype.
- Keep strict candidate validation and the upstream tests for both
prototype styles.
- Add tests for nested values, dates, times, special numbers,
prototype-named keys, sibling trust, and repeated calls.
- Preserve the original Dependabot commit and lockfile. The two
follow-up commits change only the trust source and tests.

## Verification

- All 28 startup trust cases pass at the verified head. Run the command
above.
- The focused strict TypeScript check passes. Use `tsc --ignoreConfig
--noEmit --module nodenext --moduleResolution nodenext --target es2023
--types node --esModuleInterop --strict --skipLibCheck
src/drivers/codex/codex-startup-trust.ts
src/drivers/codex/codex-startup-trust.test.ts`.
- The runner TypeScript build passes. The full local build stops because
the test workspace has no Cargo executable.
- The broader Codex suite passed 271 of 272 cases before the source
conflict resolution. One unrelated fixture expects the runtime's
workspace path to be absent.
- The repository type check exited 137 in the DB package. The full local
test command was stopped after resource exhaustion.
- The final files merge cleanly with master
`976cdc77ed564dc3e014621505890b647e554a7f`. `git diff --check` passes.
- [CI for the verified
head](https://github.com/paperclipai/paperclip/actions/runs/38664681165)
passed. All 55 active checks passed; two Storybook checks were skipped.
Snyk also passed. [Greptile cycle
3](https://github.com/paperclipai/paperclip/pull/16020#issuecomment-6182103775)
scored this head 5/5 with no actionable findings and no open review
threads.

## Risks

- This update changes the parser used by the runner and server. CI must
check both packages.
- The trust edit must preserve unrelated TOML values and bytes.
Regression tests cover this behavior.
- Local resource and tool limits prevent a complete local validation
result. The full remote CI run passed at the verified head.

## Model Used

OpenAI GPT-6 through Codex helped with the compatibility fix, tests, and
review.
The runtime does not expose the exact model snapshot or context window
size.
The agent used repository tools, code execution, and GitHub tools.
Dependabot produced the original dependency and lockfile update.

## Checklist

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

---

## Original Dependabot Update

Bumps [smol-toml](https://github.com/squirrelchat/smol-toml) from 1.8.0
to 1.9.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/squirrelchat/smol-toml/releases">smol-toml's
releases</a>.</em></p>
<blockquote>
<h2>v1.9.1</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: reject invalid extra exponent sign by <a
href="https://github.com/cyyynthia"><code>@cyyynthia</code></a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/squirrelchat/smol-toml/compare/v1.9.0...v1.9.1">https://github.com/squirrelchat/smol-toml/compare/v1.9.0...v1.9.1</a></p>
<h2>v1.9.0</h2>
<p><strong>Huge update</strong>!!! This is most likely the largest
update the library received since its release, with lots of new features
and improvements.</p>
<h3>Performance improvements</h3>
<p>Significant parts of the internal parse logic have been rewritten,
improving performance by <strong>1.5x-2x</strong>. The library was
already comfortably ahead of the others, but it is now faster than ever,
sitting at <strong>4x faster parse performance</strong> than the closest
maintained implementation.</p>
<p>Problematic code paths have also been replaced by safer
implementations, solving potential DoS vectors. See
GHSA-r4xh-jqrq-34v2.</p>
<p>Note: the objects returned by the library now have a <em>null
prototype</em>. This is a transparent change for 99.9% of users, and is
one of the most significant contributors to the major performance gains
in this version.</p>
<h3>Full Temporal support</h3>
<p>Version 1.8.0 brought support for Temporal in <code>stringify</code>;
now the library is also able to emit Temporal objects instead of its own
ad-hoc <code>TomlDate</code> object. It is not enabled by default, but
it will become the default in v2. Enable by setting <code>useLegacyDate:
false</code> in the parser's options.</p>
<h4>Better Temporal support in stringify</h4>
<p>Temporal support has been improved since it released: Temporal
objects that cannot be represented (such as
<code>Temporal.PlainMonthDay</code>) now throw an error (instead of
silently emitting a bogus object).</p>
<p>A new option has been added to <code>stringify</code> to disallow
Temporal objects that cannot be fully represented in TOML. This includes
<code>ZonedDateTime</code> objects with a IANA timezone attached instead
of a plain offset, and dates with a specific calendar value set. Enable
by setting <code>strictTemporal: true</code> in the options.</p>
<h3>Handling of unsafe keys</h3>
<p>Since its release the library has been protected against prototype
pollution attacks, setting properties like <code>__proto__</code> using
safe mechanisms that do not trigger prototype pollution. However, while
the returned objects are safe on their own, they may become problematic
if used carelessly.</p>
<p>Inspired by <code>secure-json-parse</code>, the library now offers a
way to either drop unsafe properties from the returned object, or to
throw an error and reject documents altogether. By default, these
potentially unsafe keys are preserved and returned.</p>
<h3>Miscellaneous updates</h3>
<ul>
<li>Unicode BOM is now gracefully accepted and ignored.</li>
<li>Table array headers are now properly checked again. Reported in <a
href="https://redirect.github.com/squirrelchat/smol-toml/issues/65">#65</a>.</li>
<li>Closed certain gaps where invalid whitespace would be accepted.
Reported in <a
href="https://redirect.github.com/squirrelchat/smol-toml/issues/61">#61</a>.</li>
<li>Bogus local date and local time values with a UTC offset are no
longer accepted.</li>
<li>Certain error messages are more accurate and handle errors at line
boundaries better.</li>
<li>The default export of the lib is now formally deprecated; use a
<code>import *</code> instead. Proposed in <a
href="https://redirect.github.com/squirrelchat/smol-toml/issues/50">#50</a>.</li>
<li>On Node 20+, strings that contain lone surrogates are now normalised
to well-formed strings.</li>
<li>On Node 20+, keys that contain lone surrogates are now
rejected.</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/squirrelchat/smol-toml/compare/v1.8.0...v1.9.0">https://github.com/squirrelchat/smol-toml/compare/v1.8.0...v1.9.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/squirrelchat/smol-toml/commit/69aa050fef018b71799519bb2cbb54f158a0c5f9"><code>69aa050</code></a>
fix: actually properly fix exponent issues</li>
<li><a
href="https://github.com/squirrelchat/smol-toml/commit/7be7084d24c85909cec84ecaafe39737ef4e4472"><code>7be7084</code></a>
chore: version bump</li>
<li><a
href="https://github.com/squirrelchat/smol-toml/commit/be3290da3309502447e020ce106bb8a11d99e580"><code>be3290d</code></a>
chore: upgrade deps</li>
<li><a
href="https://github.com/squirrelchat/smol-toml/commit/86c76134ad7a50e8e101ef7fd82cb0f155b278d8"><code>86c7613</code></a>
fix: reject invalid extra exponent sign</li>
<li><a
href="https://github.com/squirrelchat/smol-toml/commit/6f9739a5ab481f57c3112024bc4947534b580b58"><code>6f9739a</code></a>
fix: gate <code>[is|to]WellFormed</code> (Node 18 compat)</li>
<li><a
href="https://github.com/squirrelchat/smol-toml/commit/a73ca32c7c9237f744c880d900ad15307a0cef56"><code>a73ca32</code></a>
fix: no Temporal with toml-test when Node &lt; 26</li>
<li><a
href="https://github.com/squirrelchat/smol-toml/commit/7727890604580bd3fc2d7d2f79ad6e67b745ebe7"><code>7727890</code></a>
chore: version bump</li>
<li><a
href="https://github.com/squirrelchat/smol-toml/commit/641903d60d47900080602b6a83e919d2a526547f"><code>641903d</code></a>
chore: rewrite README.md</li>
<li><a
href="https://github.com/squirrelchat/smol-toml/commit/2df14c57945fec5a3737bc0cdce06a436bf9a77e"><code>2df14c5</code></a>
fix(types): make it work if Temporal doesn't exist</li>
<li><a
href="https://github.com/squirrelchat/smol-toml/commit/3eaa44ef54b859990969935664b12023b26d0eae"><code>3eaa44e</code></a>
chore: update benchmark harness</li>
<li>Additional commits viewable in <a
href="https://github.com/squirrelchat/smol-toml/compare/v1.8.0...v1.9.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=smol-toml&package-manager=npm_and_yarn&previous-version=1.8.0&new-version=1.9.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Devin Foley <devin@devinfoley.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 18:46:09 -07:00
Devin FoleyandClaude Opus 5.5 5e8956c207 fix(heartbeat): retry legacy runs a restart stopped before dispatch (#16355)
A hard server restart can stop a legacy run after it is claimed but before the adapter is dispatched. The next boot reaped it as failed/process_lost with no retry and held it for operator reconciliation, so the task wake was lost.

The legacy controller only writes executionStage "dispatching" in the lease renewal that admits the adapter. The orphan reaper now revokes the expired foreign-boot controller first, and uses the stage and process identifiers returned by that same UPDATE ... RETURNING. If the run is still "preparing" with no process identifiers, it records bootstrap evidence (providerWorkStarted: false) and uses the existing one-time process-loss retry. In "unexpected" mode, run-failure reporting does not send this retried restart interruption to Sentry. Runs that reached dispatching, native runs, and runs owned by the current boot are unchanged.

Verified exact head 91f1352097: full CI green, Greptile 5/5, no unresolved review threads, no conflicts. The tests were not run locally (sandbox memory limit); CI is the test run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:27:11 -07:00
Devin FoleyandPaperclip 6fae6f2cb8 fix(wake-queue): preserve pending work when a queued run is stopped (#16357)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The wake queue releases task locks and can promote saved follow-up
work when a run ends.
> - An operator can stop a queued run before dispatch selects its
runtime.
> - The legacy release path can promote the old follow-up after that
Stop.
> - For an obsolete routine cycle, this can conflict with the current
active cycle after cancellation has already committed.
> - This pull request preserves the deferred input without promoting it
at that release boundary.
> - The benefit is that a queued Stop stays stopped and does not return
an error from starting an obsolete follow-up.

## Linked Issues or Issue Description

Related: Refs #14300 and Refs #16339 describe other pre-dispatch
cancellation paths. Related PR #16238 changes former-owner wake
selection. This change addresses expected operator cancellation before
legacy runtime selection, without changing those contracts.

**What happened?**

Stopping an unstarted legacy run committed its cancellation, then tried
to promote a deferred wake. When another cycle of the same routine was
already active, the issue lock update failed against
`issues_open_routine_execution_uq`. The cancel request returned HTTP 500
even though the run was cancelled. Without a competing cycle, the Stop
launched the old follow-up.

**What did you expect to happen?**

Release the stopped run's own locks and preserve deferred input for a
later eligible instruction. Do not start the old queue as a side effect
of Stop.

**Steps to reproduce**

Create two cycles of the same routine. Keep the current cycle active.
Give the old cycle an unstarted legacy run with a verified bootstrap
record and a deferred wake. Cancel that run as a board operator, then
release its issue execution. The added database regression reproduces
the failed lock update before the fix.

**Paperclip version / commit**

Reproduced on parent commit `976cdc77ed564dc3e014621505890b647e554a7f`.

**Deployment mode**

A remote runtime exposed the symptom. The regression uses embedded
PostgreSQL and the real wake-queue adapter.

## What Changed

- Recognize an expected operator Stop only when the legacy run has no
runtime-selection time, start time, process-start time, or process
identity.
- Use the existing release boundary to preserve deferred input without
promoting it.
- Add database regressions for ordinary Stop, a competing routine cycle,
repeated release, provider cancellation, runtime selection, and
dispatched-run reconciliation.
- Document the behavior and update both recovery maps.

## Verification

- Before the fix, both queued Stop cases failed. The competing-cycle
case raised `issues_open_routine_execution_uq`. The three control cases
passed.
- `node node_modules/vitest/vitest.mjs run --project @paperclipai/server
server/src/modules/wake-queue`: 7 files and 214 tests passed, including
all five new database cases.
- `node scripts/check-maps.mjs` and `git diff --check` passed.
- `pnpm -r typecheck`, `pnpm build`, and all 10 map-script tests passed
locally. The complete CI suite is green on
`8e204eef87f1a593df5c183abef764e4b9eb5fd1`. The remaining serial local
full-suite run was stopped once that unchanged-head CI verification
passed; a local full-suite pass is not claimed. Apex review passed at
5/5 on the same head with no review threads.

## Risks

- The change is limited to recorded operator cancellation before legacy
runtime selection. Selected runtimes, dispatched work, provider
cancellation, and native stop acknowledgement keep their existing paths.
- Deferred work is retained. A later eligible instruction must still use
normal admission and all current gates.
- No schema migration or new authority is required. This regression is
not proof of a live provider stop.

## Model Used

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

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 18:18:26 -07:00
976cdc77ed fix(recovery): expose settled holds and their preserved decisions (#16169)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task recovery preserves work when execution or tool outcomes are
uncertain.
> - Automatic recovery can settle its record while keeping replay
blocked.
> - The recovery-actions API omitted those records unless they held
workspace files.
> - Operators and agent tools could therefore see an empty recovery
history for a task that still had an execution hold.
> - This pull request lists those holds and shows their matching
decision in the existing stopped-run notice.
> - Existing stop verification, continuation, and file repair gates
remain in force.

## Linked Issues or Issue Description

**What happened?**

A cancelled or failed task can retain a settled recovery record with
`automaticRecovery.replay: blocked`. The task remains held, but the
recovery-actions API returns no action. The board notice also lacks that
record's decision.

**Expected behavior**

List settled execution holds that still block replay. Let an authorized
board user inspect the current action and run in Recovery details.
Preserve the existing guarded continuation and required-file repair
paths.

**Steps to reproduce**

1. Stop a run whose outcome requires inspection.
2. Let automatic recovery settle its record with replay blocked.
3. Read the task's recovery actions and open its stopped-run notice.
4. Before this change, the action list is empty unless workspace
recovery is required.

**Paperclip version or commit**

This PR includes current master through `66424362a85`.

**Deployment mode**

Authenticated and local deployments with persisted execution recovery
holds.

The original API correction and route regression are by Tao Xie
(@TaoXieSZ). This update preserves those commits and adds the board
inspection path, current documentation, and regression coverage.

Related: #14840 describes controller lease loss. This change exposes
recovery records; it does not change controller lease renewal.

## What Changed

- Include settled execution reconciliation holds with replay blocked in
the company-scoped recovery-actions response.
- Read the matching action from the existing stopped-run notice. Show
its decision under Recovery details, including when active recovery is
empty.
- Reject mismatched historical records in the notice. Bind history to
the current action/run and refresh visible decisions every five seconds.
Keep history read errors visible beside the current guidance.
- Preserve existing run inspection, new-message continuation, and
required-file repair controls. No new reconciliation form is added.
- Update the execution documentation and both recovery capability maps.

## Verification

- Recovery route suite with real embedded PostgreSQL: 80 passed, none
skipped.
- Notice and retry-client suites: 36 passed.
- `pnpm -r typecheck`: passed.
- `pnpm build`: passed.
- `pnpm check:token-gates`: passed.
- `node scripts/check-maps.mjs`: passed. This checks structure only.
- `git diff --check`: passed.
- Full `pnpm test:run` was started. It is not yet a full local pass.
Initial database tests were affected by missing packaged macOS library
aliases; the aliases were restored in this worktree's dependencies, and
the complete recovery route suite then passed.
- All CI checks passed on `09e4461e4d3a0960a4dc8bc270a2a10c628bf524`,
including the full test matrix, typecheck, build and browser checks.
Optional Storybook jobs were skipped.
- Apex: 5/5 on the same head. Both review threads are resolved; the
reviewer confirmed the existing guarded continuation design.

## Risks

- The API response now includes settled records that still block replay.
Consumers must distinguish a settled record from permission to continue.
- Recovery history is supplemental. A failed history read keeps the
current blocker and guidance visible.
- The UI binds the decision to both the current action and run. It does
not certify tool outcomes or grant execution authority.
- No schema, dependency, telemetry, or runtime admission changes.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, tool use, and code
execution, assisted with the board inspection and verification
follow-up. The exact serving snapshot ID and context window size are not
exposed in this session. The original API work is retained from Tao
Xie's contribution.

## Checklist

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

---------

Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 17:32:35 -07:00
DottaandPaperclip 66424362a8 perf(runtime): skip discovery without a Browser Use connection (#16345)
Skip full tool discovery when no enabled, active Browser Use connection exists for the run company. Configured connections retain existing authorization and discovery.

Verified exact head 7fdf51eae8: full CI, E2E, Superagent and Apex 5/5; 29 affected lifecycle tests and full repository typecheck/build pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 18:18:00 -05:00
DottaandPaperclip c41e00bca4 fix(server): index workspace source task enumeration (#16342)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent startup checks access to every source retained by a workspace.
> - Repository admission repeats these checks while it holds a company
lock.
> - PR #16334 indexed the workspace privacy query. The next source-task
query still cast the task primary key to text.
> - That second query scanned company history for every workspace and
delayed other agent launches.
> - This pull request keeps the task column indexed and guards the
retained identifiers before conversion.
> - The benefit is faster startup with the same source validation and
access decisions.

## Linked Issues or Issue Description

Refs #16334.

**What happened?**

Agent launches still spent minutes preparing repositories after the
first authorization query fix. The source-task lookup scanned 14,191
task rows for one matching source.

**Expected behavior**

Source validation should use the task primary key and workspace-binding
index. It must still reject missing, malformed, noncanonical, and
foreign-company sources.

**Steps to reproduce**

1. Seed company task history and a workspace with attached and retained
source tasks.
2. Call `assertTaskWorkspaceAccess` and capture its source-task SQL.
3. Run `EXPLAIN (ANALYZE, FORMAT JSON)` on that query. The baseline
scans unrelated task history.

## What Changed

- Convert retained text identifiers with the existing canonical UUID
guard. Keep the indexed task column as a UUID.
- Exercise the real source-access service against PostgreSQL. Verify
complete source results, malformed and uppercase input behavior, and
rows examined by the plan.
- Document the second source-validation query and its regression
coverage.

## Verification

- Red: the new regression failed because the source lookup examined
1,202 rows for two sources.
- Green: four focused workspace suites passed, 147 tests total. The
final regression also checks malformed and uppercase query inputs.
- Live read-only comparison: two warmups, then five samples per variant
in alternating order. Median client query time changed from 4.88 ms to
0.35 ms. Complete result rows matched. Shared buffer hits changed from
3,984 to 5. These measurements cover this SQL statement, not total agent
startup.
- The complete source-access helper also passed for the same retained
reader in read-only transactions. Its median changed from 15.75 ms to
10.75 ms; repository admission still repeats other checks.
- `pnpm -r typecheck` and `pnpm build` passed locally. The staged Linux
build and all seven final regression cases passed on Node 24.20.0.
- All 55 current-head checks/statuses passed; two Storybook checks were
skipped. Greptile reviewed `6cfb755` at 5/5 with no findings.
- The full local test run encountered a tracing-test failure in
`native-session-executor.test.ts`. Both that test alone and the complete
file passed on rerun (698 tests). The remaining full-suite run is still
in progress.

## Risks

- This changes SQL planning on an authorization path. The canonical
guard preserves the former exact lowercase text match and avoids UUID
parse errors.
- Company filters, missing-source checks, project checks, and per-source
access decisions remain in place.
- No schema migration or new index is required. Repeated checks across
large repository histories remain a separate source of startup work.

## Model Used

- OpenAI Codex, GPT-6 family, with reasoning, shell tools, code
execution, and browser verification. The exact 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
#` OR (b) described the issue in-PR following the relevant issue
template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 18:15:04 -05:00
DottaandPaperclip 90a45ef254 perf(skills): skip inventory work for empty runtime selections (#16343)
Return before company skill inventory work for a known empty native or stored-key selection. Preserve nonempty stored-key precedence, current access/version checks and legacy automatic skills.

Verified head f3f89eedff: 88 affected tests, full typecheck/build, full CI/E2E, Superagent, legacy status and Apex 5/5; no unresolved findings.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 18:01:52 -05:00
Devin FoleyandPaperclip cda77edc60 perf(server): stop readiness waiting on startup housekeeping (#16329)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - On restart, `/api/health` reports `starting` until startup recovery
finishes, and deploy tooling waits for `ok`
> - After heartbeat recovery, readiness also waits for housekeeping that
every scheduler tick repeats and that serving requests does not need
> - #16330 added OpenTelemetry spans for the main startup phases; native
recovery and orphan reaping are the slow ones, and both run the native
finalization reconciliation pass, which has no inner breakdown
> - This pull request stops readiness waiting on that housekeeping, and
records the phases of slow reconciliation passes as spans
> - The benefit is faster readiness on every restart, and traces that
show where a slow reconciliation pass spends its time

## Linked Issues or Issue Description

Builds on #16330, which records `controller.startup.<phase>` spans.
Related to #16328.

**What existing behavior does this improve?**
Server readiness time after a restart, and diagnosis of slow native
recovery.

**Current behavior**
- After heartbeat recovery, readiness waits for the custom-image setup
cleanup, tool connection health (remote MCP checks), decision expiry,
the adapter and setup-token login reapers, environment lease cleanup
(provider calls) and the retention sweep. Each also runs on every
scheduler tick. On one instance they added 11–28 s to every measured
restart. A throw in any of them fails startup.
- On the same instance, native recovery took from 33 s to 11.5 min and
orphan reaping from 30 s to 4 min across restarts in one hour. Both run
the native finalization reconciliation pass. The #16330 spans time each
step as a whole, but not the phases inside that pass.

**Proposed behavior**
- The housekeeping steps run after readiness. The scheduler interval
still starts after them.
- With an OTLP endpoint, a reconciliation pass of at least one second
records a span with one child span per phase.

**Reason and benefit**
Readiness depends only on what serving requests and run recovery need.
Slow reconciliation passes become attributable to a phase in the
existing traces, without adding trace volume for the periodic pass.

**Breaking changes**
None. A failed housekeeping step no longer fails startup; it is logged
and the next step runs.

## What Changed

- `server/src/index.ts`: moves the housekeeping steps into
`runStartupHousekeeping`, which runs after readiness under
`trackHeartbeatSchedulerWork` (idle sleep and shutdown wait for it).
Each step catches and logs its own failure (`startup housekeeping step
failed`). The heartbeat scheduler interval starts when housekeeping
finishes, unless the scheduler was stopped, so the reapers still finish
before the first timer tick.
- New
`server/src/services/native-runtime/native-reconciliation-timing.ts`:
records the phase boundaries of one pass. When the pass took at least
one second and an OTLP endpoint is configured, it emits a
`controller.native_reconciliation` root span
(`paperclip.native-reconciliation`) with child spans
`legacy_workspace_exports`, `obsolete_policy_reviews`,
`automatic_completion_reviews`, `candidate_scan` and `candidates`, using
the recorded start and end times. It records `scoped` and the candidate
count only. A failed pass marks the pass span and the running phase.
Tracing failures are swallowed.
- `native-finalization-reconciler.ts`: the exported function wraps the
pass in that timing and finishes it in `finally`, so failed passes are
recorded; the original error is rethrown.
- `doc/execution-semantics.md`: what readiness waits for, and the
housekeeping order. `doc/observability.md`: the reconciliation spans.

## Verification

- `pnpm exec vitest run
src/__tests__/server-startup-feedback-export.test.ts
src/services/native-runtime/native-reconciliation-timing.test.ts
src/services/native-runtime/native-finalization-reconciler-timing.test.ts
src/startup-recovery-timing.test.ts` (in `server/`): 52 passed. New
tests:
- Readiness is `ready` while a housekeeping step hangs, and the
heartbeat interval starts only after the step settles.
- A failed housekeeping step is logged, and the scheduler still starts
after the remaining steps.
- Span timing: nothing for a fast pass or without an endpoint; a slow
pass emits the pass span and one child per phase with the recorded
times; a failed pass marks the running phase and the pass; a failing
tracer does not throw.
- The reconciler records its phases and a failed outcome when a pass
throws, and rethrows.
- Three existing startup tests settle housekeeping before they read the
scheduler interval or the setup cleanup.
- `pnpm exec vitest run src/services/native-runtime`: 4,399 passed.
- `pnpm typecheck` in `server/`: passed.

## Risks

- Housekeeping now runs concurrently with the first requests and with
`waitForExternalAdapters`. None of these steps is needed to serve
requests, and each one also runs on every scheduler tick.
- Readiness is reported earlier; the heartbeat timer starts at the same
point relative to housekeeping as before.
- The new spans exist only with an OTLP endpoint and only for passes of
at least one second.

## Model Used

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

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [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-10-14 15:55:13 -07:00
DottaandPaperclip aa04c5c2a7 fix(runtime): keep the copyback lock transaction active (#16340)
Keep the dedicated workspace-copyback transaction active with bounded SELECT 1 probes while filesystem work runs. Preserve existing connection, owner-token, takeover and publication fences.

Verified head 7fb0ad45bf: full CI, E2E, Superagent, legacy statuses and Apex 5/5; no unresolved review findings. Real PostgreSQL idle-timeout reproduction and 71 affected cases pass. Strengthened disabled-timeout controls fail when their guard is removed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 17:52:26 -05:00
DottaandPaperclip cc96c9f832 fix(server): probe libc asynchronously before serving requests (#16335)
Linux runner qualification generated a full Node diagnostic report to read one libc version field. Reverse DNS on open sockets could stop the event loop for minutes.

Replace report generation with a one-time asynchronous getconf probe during module initialization. Bound it to one second and 1 KiB of output, cache the measured facts, and preserve unknown-libc failure behavior.

Verification: 149 focused tests, repository typecheck and build, full CI, and fresh Greptile 5/5 with no comments. A timed-out local heartbeat fixture passed when rerun alone.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 17:46:50 -05:00
Devin FoleyandPaperclip 35b3345033 fix(server): support issue monitor deadlines in idle sleep reports (#16325)
## Thinking Path

> - Paperclip manages work for companies and AI agents.
> - Hosted workspaces can sleep when their required work has a safe wake
path.
> - The current idle check treats every open issue as background work.
> - Backlog, unassigned and human-waiting records can therefore keep an
idle process running.
> - Future automated monitors still need their saved check time and
execution claims.
> - This PR adds a signed host capability for passive issues and
verified monitor deadlines.
> - The host can save one alarm for the earliest required routine or
monitor time.

## Linked Issues or Issue Description

**What happened?**

A quiet workspace with a backlog or unassigned todo issue reports
background work as present. A future issue monitor also prevents sleep
even when the host can save a durable wake alarm.

**Expected behavior**

Proven passive records can sleep. A validated future monitor can sleep
only after the host saves its alarm and validates the same owned report.
Active, unknown and unresolved work stays awake.

**Steps to reproduce**

1. Create a quiet workspace with an unassigned todo issue and no other
work.
2. Acquire a bounded, signed Cloud idle hold.
3. Request the routine-only safety report with `scheduledWake=1`. The
issue blocks sleep.
4. Request the new capability with `scheduledWake=2`. A passive issue
can now report no work.
5. Add a valid future monitor to an assigned in-progress issue. The
second capability returns its saved deadline for host acknowledgement.

**Paperclip version or commit**

Base: `c30fb7d2b75ea30f72d63656b2a7cfff7fe0929d`.

**Deployment mode**

Cloud-managed authenticated instance. Local scheduling remains in the
existing scheduler.

Related protocol:
[#16215](https://github.com/paperclipai/paperclip/pull/16215). Open
scheduler and monitor PRs were checked for overlap. This change leaves
monitor admission, dispatch and quota recovery with their existing
owners. The roadmap already includes scheduled work; this is a focused
idle-safety extension.

## What Changed

- Add signed scheduling capability `2`; capability `1` retains the
routine-only rules.
- Allow untouched onboarding prompts with idle assignees to sleep using
the same awaiting-user predicate as recovery; typing, answering the
opening card or a prior run removes the exemption.
- Inspect passive issue state and future monitors in the same bounded,
read-only snapshot as other work.
- Check ownership, company scope, known states, claims, policy/deadline
agreement, attempts, timeout and dependency repair.
- Combine routine and monitor deadlines into a known proof kind and
catalog generation.
- Preserve other sleep blockers, including task watchdogs,
conversations, live runs, external credentials and recovery.
- Update the protected route, OpenAPI contract, documentation and
PostgreSQL regression tests.

## Verification

- Full workspace typecheck and build passed. Final server typecheck and
full build passed after the final guards.
- Initial safety, scheduling, protected-route and control-authentication
checks: 326 tests passed.
- Final safety and heartbeat scheduling checks: 253 tests passed.
Includes reporting a future deadline, restoring scheduling from
PostgreSQL and one dispatch across competing schedulers.
- Local general server checks: 20,260 passed; UI: 8,460 passed; CLI: 534
passed, including isolated reruns of two embedded-PostgreSQL startup
failures. All remaining workspace groups and 158 serialized suites
passed. The suite was completed in groups after the isolated
infrastructure retries; the final onboarding change also passed the
focused 674-test recovery/safety run. Earlier unchanged Git fixtures
failed under Xcode's system `init.defaultBranch=main`, reproduced on the
clean base and passed with a test-scoped fixture default; no source or
machine configuration was changed.
- Rebased issue, protected-route and scheduler checks: 115 tests passed;
server typecheck passed.
- The expanded change passed 674 idle-safety, scheduling and recovery
regression tests, including six onboarding admission/invalidation cases.
Final server typecheck and build passed.
- Current-head Apex is 5/5 with no unresolved threads. All 54
current-head checks passed, with two expected skips. Four jobs were
interrupted by runner shutdowns or lost communication on their first
attempts; their targeted reruns passed. Live hosting qualification
awaits the verified master release and matching host capability.

## Risks

This intentionally covers a limited set of passive states and valid
future monitors. Assigned active tasks without a proved waiting path,
unknown states, claimed monitors and quota recovery still block sleep.
It does not make every open issue safe to sleep.

A matching host must recognize `routines_and_issue_monitors`,
acknowledge the durable alarm and revalidate the exact owner, generation
and deadline before stopping. The report alone grants no stop authority.
Older hosts retain capability `1`. Independent database writers remain
outside the admission contract. Existing scheduler claims and execution
authority are unchanged. No schema migration is required.

Roll back host negotiation to capability `1` to restore conservative
admission, while retaining alarm delivery until outstanding alarms have
been handled.

## Model Used

- OpenAI GPT-6 (Codex), with repository editing, command execution and
GitHub tools. The exact deployment ID and context-window metadata are
not exposed in this session.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 15:39:46 -07:00
Devin FoleyandPaperclip aec80cbc8c fix(server): keep workspace compaction out of the startup sweep (#16328)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - When the server starts, `/api/health` answers "starting" until
startup recovery finishes, and deploy tooling waits for "ok"
> - Startup recovery waits for the first sweep of the terminal-workspace
worker
> - #16304 added workspace compaction to that sweep. Compaction walks
worktrees, runs Git, and deletes dependency trees, and it re-checks
every eligible workspace after a restart
> - Compaction is never needed to serve requests, so it should not delay
readiness
> - This pull request skips compaction in the startup sweep only; later
sweeps compact as before
> - The benefit is that a compaction backlog cannot make a restart
slower

## Linked Issues or Issue Description

Follow-up to #16304.

**What existing behavior does this improve?**
Server startup time after #16304.

**Current behavior**
`await terminalWorkspaceWorker.ready` blocks the readiness flip until
the first terminal-workspace sweep ends. That sweep compacts every
eligible workspace in its page. The per-cycle compaction memo is in
memory, so after each restart every eligible workspace is walked and
checked with Git again, and any directories found are deleted before the
sweep ends.

**Proposed behavior**
The first sweep skips compaction. Pending compactions keep their
deadlines, so the worker wakes and a later sweep compacts them.

**Reason and benefit**
Readiness depends only on work that is needed to serve requests. On one
measured instance the first sweep took 5–8 s with compaction; with a
larger backlog, compaction could add seconds per workspace.

**Breaking changes**
None. Compaction happens one sweep later after a restart.

## What Changed

- `sweepTerminalWorkspaces(limit, signal, { compact })`: `compact:
false` skips compaction for that sweep and keeps each pending compaction
deadline.
- `registerTerminalWorkspaceWork` passes `compact: false` to its first
sweep (the one readiness waits for) and `compact: true` after that.
- `server/src/index.ts` forwards the option through the logging wrapper.
- `doc/DEVELOPING.md`: the compaction section says the startup sweep
never compacts.

## Verification

- `pnpm exec vitest run src/__tests__/terminal-workspace-work.test.ts
src/__tests__/execution-workspace-compaction.test.ts
src/__tests__/workspace-compaction.test.ts` (in `server/`): 35 passed.
- New: the worker passes `compact: false` only to the startup sweep and
`compact: true` to the next one.
- New: a sweep with `compact: false` leaves the files in place, the
compaction deadline stays due, and the next sweep compacts.
- `pnpm typecheck` in `server/`: passed.

## Risks

- Low risk. After a restart, compaction runs a few seconds later than
before. Archive behavior does not change.

## Model Used

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

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [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-10-14 15:27:28 -07:00
DottaandPaperclip ebceb2a744 trace(server): time workspace, native setup and startup recovery (#16330)
## Thinking Path

> - Paperclip manages agents and their work.
> - The controller prepares workspaces before it starts a Runner.
> - A slow run can spend 30–80 seconds inside the combined workspace phase.
> - Startup recovery can also delay queued work for minutes.
> - Native runs also have gaps before Runner startup and after the provider result.
> - Existing traces do not identify the awaited steps inside those intervals.
> - This change adds closed timing boundaries around the existing operations.
> - Operators can locate the wait without changing execution or exposing content.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Operator-configured OpenTelemetry diagnosis of controller preparation and startup recovery. Related: #16178, #16236 and #16285 introduced the existing preparation timing and sampling contract. #16306 and #16312 reduce separate configuration and copyback work.

**Current behavior**

The workspace interval combines source resolution, trust checks, provisioning, database persistence, binding and repository admission. Startup awaits accounting and native recovery before queued work resumes, but neither has a separate duration. Native backend setup and post-turn cleanup also contain unmeasured awaits. CPU profiles and a fast database probe cannot identify which await is slow.

**Proposed behavior**

Record workspace steps and native controller setup and cleanup with the existing hashed run ID. Record startup phases as independent, operator-sampled root spans without custom attributes. Keep the same operation order, results, failures and authorization.

**Reason and benefit**

These boundaries distinguish source work, database writes, repository admission and restart recovery. This is a diagnosis change. It does not claim a response-time improvement or a solved latency budget.

## What Changed

- Add 14 closed workspace step names around 16 existing awaits. Repeated updates and bindings produce separate spans.
- Time startup tool reviews, gateway sessions, delivery-worker readiness, accounting, plugin readiness, native recovery, hot restart, orphan reaping, retries, queued work and goal recovery.
- Time native GitHub setup, wake binding, backend construction, connector assignments, MCP discovery, tool authority, companion selection and binary resolution. Time selected post-turn GitHub, coordinator and instruction cleanup awaits.
- Move preparation timing to a pure shared module. Keep the existing heartbeat import as an alias. Both heartbeat and native timing skip tracer lookup when the endpoint is unset or blank, including when another library registered a provider.
- Tracing lookup, callback, status and end faults cannot repeat work or replace its value or error.
- Add real PostgreSQL workspace timing coverage and silent no-op/fault controls. Update the tracing contract and paired run maps.
- Keep span attributes closed. Startup spans add none; workspace steps use only the existing run hash. No commands, paths, returned records or exception messages are recorded.

## Verification

- Diagnostic-gap baseline: the new real workspace test fails against the unchanged workspace implementation because the expected step spans are absent. It passes with the wrappers.
- Final affected coverage: **804 distinct cases pass** across seven complete suites: 698 native-executor cases, 39 real PostgreSQL workspace cases, 61 timing/context/startup cases, and six execution-configuration integration cases. The final native and unit invocations pass with no selected skips. The workspace suite passed in the initial six-file invocation on the same final workspace source.
- Preserve the initial six-file attempt as failed: **7 failed and 789 passed**. All seven errors occurred during embedded PostgreSQL startup. The log reports local macOS shared-memory exhaustion. A fresh 15-case database group passes, followed by the complete 698-case native suite. These runs overlap; do not add their counts.
- The new native timing assertion fails against unchanged native code because the expected spans are absent. It passes with the wrappers and retains the credential-isolation assertions.
- Full `pnpm -r typecheck` and `pnpm build`: passed on the final production source. Earlier nullable-closure errors are fixed with erased assertions inside existing guards. The failed attempts remain recorded.
- Normalized AST comparison: removing only timing wrappers, timing imports/local handles and erased assertions restores the original workspace, startup and native executor programs. This checks call order and policy expressions; it is not live acceptance.
- The expanded head first failed two configuration integration assertions because their test providers had no endpoint. Configure the endpoint explicitly in those two tracing tests, and restore it after each test. All six cases then pass with the original credential cleanup, redaction and exact span assertions. Production is unchanged by this repair.
- The first PR head failed one unchanged attachment test at its 15-second deadline in CI. The complete 32-case attachment suite passes locally with that deadline. Do not label the failed CI attempt green. The expanded head needs fresh full CI.
- `node scripts/check-maps.mjs`: passed, 35 areas and 176 capability IDs. `node scripts/storybook-catalog.mjs`: 283 files and 2,728 stories. Structural checks only; no browser, play or Storybook build claim.
- `git diff --check`: passed. No added prose comments, lockfile changes or new 1,000-line crossings.
- A duplicate full local test run was not started. Fresh exact-head full CI and E2E pass on `15a683bb2419960f611584f8c07cdcfc9ce3af0b`. All 56 check runs are successful or optional skipped. Superagent and legacy Snyk pass. Completed Apex review is 5/5 on this same head, with zero unresolved review threads.

Commands:

```sh
pnpm exec vitest run --project @paperclipai/server --maxWorkers=1 --fileParallelism=false server/src/services/native-runtime/native-session-executor.test.ts server/src/services/execution-preparation-timing.test.ts server/src/startup-recovery-timing.test.ts server/src/services/heartbeat/preparation-timing.test.ts server/src/services/heartbeat/preparation-timing-context.test.ts server/src/services/heartbeat/workspace-preparation.test.ts
pnpm -r typecheck
pnpm build
node scripts/check-maps.mjs
node scripts/storybook-catalog.mjs
```

## Risks

- Configured operators receive more spans. They still follow the configured sampler and exporter.
- Startup spans measure the awaited phase, including remaining readiness waits. They do not measure earlier worker construction or a provider turn.
- Workspace and native setup intervals overlap their containing phases. Do not add them to the parent duration. Native timing covers named awaits, not every statement or every error path.
- These spans are not yet deployed or tested against a live latency run. No first-party Telemetry event, run-log write, schema or API changes are included.

## Model Used

OpenAI GPT-6 through Codex, with tool use and code execution. The exact served variant and context-window size are not exposed to this task.

## Checklist

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

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 17:16:07 -05:00
DottaandPaperclip 445feaa7aa fix(server): index retained workspace task authorization (#16334)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Before an agent starts, repository admission checks access to
retained workspace files.
> - Admission holds a company provenance lock while it checks each
source and reader.
> - The retained-task query cast the indexed task ID to text. PostgreSQL
could evaluate privacy across company history before it matched one
source ID.
> - Repeated slow checks kept the lock for hours and blocked other agent
launches.
> - This change looks up the canonical source UUID through the existing
primary key before it checks privacy.
> - The query does less work and keeps the same access decisions.

## Linked Issues or Issue Description

**What happened?**

Nine runs appeared active but had no provider process or log file.
Read-only database inspection found a transaction holding the company
repository-provenance lock for more than ten hours. Eight other
transactions waited for that lock. The owner repeatedly ran workspace
authorization queries. The company had about 13,400 tasks and 8,500
workspaces.

**Expected behavior**

A retained-source check should find the referenced task before it
evaluates that task's privacy. Unrelated task history should not
multiply the privacy work for every source and reader.

**Steps to reproduce**

1. Run the new `workspace-authorization-query.test.ts` against
authorization code from `5c7a025032`.
2. The real PostgreSQL fixture has 1,200 unrelated tasks and one
retained source. The regression fails with 2,402 recursive privacy
traversals, above the bound of 100. The other five access tests pass.
3. Apply this change and run the same test. All six tests pass.

Related: Refs #16330 adds workspace timing evidence. This PR fixes a
measured SQL plan. It does not change those tracing boundaries.

## What Changed

- Compare the UUID task column with a guarded canonical UUID from the
retained-source JSON key.
- Reuse `canonicalExecutionUuid`. Invalid and noncanonical keys still
match nothing and cannot cause a UUID parse error.
- Add real PostgreSQL coverage for query work, missing and foreign
sources, malformed and uppercase IDs, both agent and sponsor grants, and
grant revocation.
- Add a reproducible baseline comparison and document its scope in
`doc/DATABASE.md`.

## Verification

- Red: the unchanged query fails the new work-bound regression. Five
access cases pass.
- Green: 101 tests pass across the new query suite, workspace binding,
and the production privacy review suite.
- Full `pnpm -r typecheck` and `pnpm build`: pass. Dependencies use the
repository's pinned pnpm 9.15.4.
- All current-head CI checks pass, including the full server, chat,
workspace, serialized, Runner, and eight E2E test lanes. Build,
typecheck, canary, and security checks pass. Head:
`ad907f6e14c2d773fa8c20692e5ccc10215c9297`.
- Greptile: 5/5 on that head, with no actionable findings or unresolved
review threads. The branch is mergeable.
- The local full `pnpm test:run` was started, then stopped after a
concurrent disposable-database check hit the macOS shared-memory limit.
This is not counted as a local full-suite pass. The benchmark passed
when rerun alone; the complete suite passed in CI.
- `git diff --check`: pass. The branch has no lockfile or workflow
changes.

Synthetic SQL comparison on PostgreSQL 18.1, with 13,429 tasks and one
retained source:

| Query | Median | Five measured samples | Recursive privacy traversals
|
| --- | --- | --- | --- |
| Baseline `5c7a025032` | 42.57 ms | 42.57, 47.71, 44.51, 41.29, 41.44
ms | 26,860 |
| Fixed | 2.37 ms | 2.76, 2.35, 2.37, 2.11, 3.33 ms | 4 |

Both versions return identical complete rows. The benchmark alternates
variants after two warmup rounds. It loads the baseline authorization
module with current dependencies; the UUID helper is unchanged. The
measured gain is about 18x for this SQL fixture.

The final committed benchmark also passed a second isolated run: 55.44
ms baseline and 3.26 ms fixed, with identical complete rows (about 17x).

Two read-only queries on the affected database also returned identical
complete rows. One changed from 129 ms to 7 ms; a workspace with 49
retained sources changed from 576 ms to 11 ms. These are single paired
measurements, not endpoint or startup benchmarks.

Reproduce:

```sh
pnpm exec vitest run server/src/__tests__/workspace-authorization-query.test.ts server/src/__tests__/task-workspace-binding.test.ts server/src/__tests__/privacy-production-review.test.ts
pnpm --filter @paperclipai/server exec tsx ../scripts/benchmark-workspace-authorization.ts --baseline-ref=5c7a025032
pnpm -r typecheck
pnpm test:run
pnpm build
```

## Risks

- A direct UUID cast could broaden or break access for malformed keys.
The existing canonical guard and regression cases preserve the former
text-match behavior.
- Planner choices can differ across PostgreSQL versions and data sets.
The regression bounds real privacy work rather than elapsed time or one
exact plan shape.
- Admission still checks every retained source and reader under its
existing lock. This removes a measured query cost; it does not claim a
bound for the full admission process.
- No schema migration, permission change, lock removal, or live
deployment is included. Recovery of the affected runs remains unverified
until rollout.

## Model Used

OpenAI GPT-6 through Codex, with tool use and code execution. The exact
served model variant and context-window size are not exposed to this
task.

## 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 `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 16:55:34 -05:00
Devin FoleyandPaperclip 8ffc5748a2 fix(server): stop libc detection from reverse-resolving every socket (#16333)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - #15683 made new Codex agents default to Paperclip Runner on Linux,
which needs the host's glibc version
> - `localAgentRunnerTarget()` reads that version from
`process.report.getReport()`
> - Unless `excludeNetwork` is set, Node's diagnostic report resolves
the endpoints of every open socket with synchronous reverse DNS on the
main thread
> - `GET /adapters` calls it once per adapter on every request, so on a
host whose proxy address has no fast PTR answer, the whole server stops
responding
> - This pull request excludes network data from the report and detects
libc once per process
> - The benefit is that runner selection costs a few milliseconds once,
instead of blocking the server on every request

## Linked Issues or Issue Description

Regression from #15683.

**What happened?**
After #15683, a server running behind a reverse proxy stopped
responding. Requests, database replies, log output and health checks all
stalled for minutes. The Node main thread was idle on CPU but blocked in
a synchronous DNS read: reverse (PTR) lookups of the proxy's address,
each waiting for the resolver timeout.

**Expected behavior**
Detecting the C library must not block the event loop on DNS, and must
not repeat on every request.

**Steps to reproduce**
1. Run the server on Linux where the reverse DNS lookup of a connected
peer's address is slow (for example, a proxy address whose PTR lookup
times out).
2. Open the board, which polls `GET /adapters`.
3. The server stops answering. Each `getReport()` call reverse-resolves
every open socket synchronously.

Measured on Node 24.21 with one open TCP connection to such a peer:
`process.report.getReport()` took **9,774 ms** with network data and **4
ms** with `excludeNetwork`. A busy server has hundreds of open sockets.

**Paperclip version/commit**
`master` after #15683.

## What Changed

- `server/src/lib/agent-runner-platform.ts`:
- Generates the report with `process.report.excludeNetwork = true` and
restores the previous value in `finally`.
- Detects libc once per process and caches the result, because the C
library cannot change while the process runs. A failed report also
caches "unknown", so it is never retried per request.
  - Adds `resetLocalAgentRunnerTargetCacheForTests()`.
- Reads `excludeNetwork` through a narrow typed view, because the
installed Node type definitions do not declare it yet.
- New `server/src/__tests__/agent-runner-platform.test.ts`.
- `agent-runner-selection.test.ts` and
`dev-runner-native-binary.test.ts` reset the cache between cases,
because they change the mocked glibc version per case.

## Verification

- `pnpm exec vitest run src/__tests__/agent-runner-platform.test.ts
src/services/agent-runner-selection.test.ts
src/__tests__/dev-runner-native-binary.test.ts
src/services/local-ai-browser-login.test.ts` (in `server/`): 165 passed.
New tests:
- The report is generated with `excludeNetwork` set, and the previous
setting is restored.
  - Five calls generate one report.
- A failing report gives `libc: "unknown"` once and restores the
setting.
  - No report is generated off Linux.
- `pnpm typecheck` in `server/`: passed.
- Measured on Node 24.21 (above): 9,774 ms → 4 ms for one report with
one slow-PTR socket open.

## Risks

- Low risk. The detected glibc version is the same; only the network
section of the report is skipped, and nothing reads it.
- The cached result does not change until restart. The C library cannot
change while the process runs.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), 1M context, through Claude Code
with tool use (shell, file edits, test runs, live diagnosis of the
stalled process).

## 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-10-14 16:48:05 -05:00
DottaandPaperclip 5c7a025032 fix(ui): clear consumed messages from the pending queue (#16317)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task conversations keep follow-up messages pending while an agent
works.
> - The screen combines optimistic sends, queue snapshots, and run input
records.
> - A late send response can restore a local queue flag after a run
takes the message.
> - A stale queue snapshot can then keep the same message in pending.
> - This pull request matches the input record to its source queue and
clears stale pending flags.
> - The benefit is that consumed messages leave pending while unsent
messages stay visible.

## Linked Issues or Issue Description

**What happened?**

A follow-up can remain in the task composer's pending row after a run
takes it as input. Local queue state can outlive the send request. The
old projection cleared that state for steering, but not for ordinary
run-start input. A stale queue read can also retain the consumed entry.

**Expected behavior**

A message that a started run takes as input appears once in history and
leaves pending. Other pending messages remain available. A message
assigned to a run that has not started remains pending.

**Steps to reproduce**

1. Send a follow-up while a task has a live run.
2. Let a started run record that message as input before or after the
send response arrives.
3. Let a queue read still include the consumed message.
4. Observe the message remain pending on the base revision.

**Paperclip version or commit**

Reproduced against master at `33d24e48ad` with controlled request
ordering.

**Deployment mode**

Source checkout. Browser verification used the production task screen
with a simulated native runner and delayed API responses. It did not run
a live model or change a deployed instance.

Related: [PR
#15030](https://github.com/paperclipai/paperclip/pull/15030) addresses
acknowledgement matching in the runner console. This change addresses
the task-detail composer and does not modify that console.

## What Changed

- Clear local pending flags when a started run records the comment as
input.
- Use live-run input IDs while the run-history query catches up.
- Match stale queue entries by their primary or coalesced source wake
ID, even when the snapshot targets the previous run or has no target.
- Retain coalesced source queue IDs in run context and include receipt
IDs in compact task run reads.
- Keep unstarted run inputs and authoritative retries pending. Cover
send ordering, same-run and successor delivery, queued snapshots,
coalesced inputs, and retry consumption.
- Add the delivery-race checks to the steering feature map.

## Verification

- Before the fix, the first four request-order regressions failed with
the consumed message still in pending. Coverage now includes 16
combinations of receipt source, send-response order, and stale snapshot
kind.
- Focused UI checks: 5 files and 233 tests passed. These cover task
detail, queue projection, optimistic comments, queued-message controls,
and the task-chat adapter.
- `pnpm -r typecheck` passed.
- Server receipt checks: 5 files and 139 tests passed with
`PAPERCLIP_TEST_POSTGRES_TEMPLATE=0`. These exercise real database
reads, coalesced heartbeat input, and polling routes. The default
cached-template path timed out during setup on this host; the
fresh-cluster path passed without longer timeouts.
- Token gates and `node scripts/check-maps.mjs` passed.
- Browser: sent a follow-up with a delayed response in the production
task screen. The consumed message moved into history. A second unsent
follow-up stayed queued. Reload preserved both states. The API and
runner were simulated.
- `pnpm build` passed.
- The full local repository test run was started, then stopped to
isolate database setup timeouts. All 55 GitHub CI checks passed on
`4ec6820cf1f89880b801fd5f938d72f54591c5e2`, including the full test
matrix. One serialized server shard passed on its single rerun after a
15-second route-test timeout; the same 97-test route suite also passed
locally.
- Greptile reviewed the final head at 5/5. All review threads are
resolved.

## Risks

- Queue display depends on matching comment IDs, source queue IDs, and a
recorded start time. Rows without start evidence stay pending. Newer
queues remain visible until their own input receipt arrives.
- Run context gains additive receipt metadata. No schema migration is
required. Dispatch order, stored queue state, company access, and
steering authority stay unchanged.
- Browser evidence covers controlled response ordering. It does not
establish deployment or live-provider behavior.

## Model Used

- OpenAI Codex, GPT-6, with reasoning, tool use, and code execution. The
exact serving model ID and context-window limit 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 `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 16:11:29 -05:00
Nicky LeachandPaperclip c30fb7d2b7 refactor(core): share Runner execution lifecycle (#16318)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Core starts Runners in local, SSH, and sandbox environments.
> - Coding runs, Dot runs, and setup probes repeat connection setup
code.
> - These paths must keep the same preparation, authentication, and
cleanup order.
> - This pull request gives them one execution lifecycle contract and
one server connection driver.
> - Core keeps the existing PRP control plane after connection.
> - The benefit is one place to maintain environment routing before
adding another execution provider.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Runner environment preparation and connection setup across local, SSH,
and sandbox execution.

**Current behavior**

The coding transport owns the environment hook type. Dot borrows that
type. The server repeats connection selection, registration, ingress
activation, and release in task execution and setup probes. Changes to
one path can miss another path.

**Proposed behavior**

Use a shared `RunnerExecutionDriver` contract. Route built-in task
execution and remote setup probes through
`createNativeRunnerExecutionDriver`. Keep provider session setup,
process supervision, PRP commands, and recovery logic on their existing
paths.

**Reason and benefit**

Environment code can prepare resources and provide a connection without
adding another PRP execution path. The same driver preserves local
loopback, remote outbound WSS, and authorized sandbox ingress.

**Breaking changes**

None intended. Existing flat SDK options remain compatible. This is an
in-process contract. It does not add a plugin wire contract or Launcher
APIs.

**Additional context**

Related: #15406 proposes plugin-provided Runner execution. This change
starts from `master` and does not depend on that PR. It extracts the
existing lifecycle before adding another provider. Related: #16125
covers remote environment support for external adapters; this PR does
not implement that feature.

## What Changed

- Extract and export `RunnerExecutionDriver` from the coding transport.
Use it for coding and Dot options.
- Share server connection negotiation across coding runs, Dot runs, and
remote setup probes.
- Preserve outbound validation before staging, capability checks before
ingress, process dispatch before ingress connection, and verified
process readiness.
- Preserve warm run registration, restart guards, checkpoint hooks, and
cleanup ownership.
- Add 13 focused driver tests and document the lifecycle boundary.

## Verification

- Targeted server execution and recovery tests: 728 passed, 35 skipped.
These cover `native-session-executor`, `native-ssh-command-runner`, the
new driver, warm restart transitions, and Runner restart recovery.
- Targeted setup and driver tests: 109 passed after the review fix.
Twelve driver tests overlap with the preceding count. The added test
verifies that failed CA staging cannot leave a PRP route behind.
- Runner connectivity tests: 11 passed.
- Runner package lifecycle tests: 287 passed across coding transport,
Dot, restart continuation, and settlement. These exercised the compiled
Rust Runner.
- `pnpm -r typecheck`: passed.
- `pnpm build`: passed.
- `git diff --check`: passed. Local secret-pattern scan: clear.
- The PRP transport runtime and remote process launcher remain
unchanged. A byte comparison against the base confirmed this.
- CI passed on `b10789dfce30a837ce890f44e352064c7a74366f`: all 55 active
checks are green, including the full test shards, Runner checks, browser
tests, typecheck, build, and aggregate gate. Greptile scored this commit
5/5 with no unresolved findings.
- An earlier broad local shard run was not green: 34,888 passed, 12
failed, and 4,125 skipped. Failures were ten timeouts and two Git
fixture assertions affected by local `init.defaultBranch=main`. Isolated
retry passed all six non-route timeout suites (427 tests), two route
suites, and both Git assertions with a test-process-only
`init.defaultBranch=master`. The agent live-run route retry still timed
out. No unrelated source changes were made. Further broad local runs
were stopped at the user's request.

Targeted commands:

```sh
cd server
node ../node_modules/vitest/vitest.mjs run src/services/native-runtime/native-session-executor.test.ts src/services/native-runtime/native-ssh-command-runner.test.ts src/services/native-runtime/native-runner-execution-driver.test.ts src/services/native-runtime/native-warm-transition-restart.integration.test.ts src/services/native-runtime/native-runner-restart-recovery.integration.test.ts --config vitest.config.ts
node ../node_modules/vitest/vitest.mjs run src/services/native-runtime/setup-readiness.test.ts src/services/native-runtime/native-runner-execution-driver.test.ts --config vitest.config.ts
cd ../packages/paperclip-runner
node ../../node_modules/vitest/vitest.mjs run src/live/runnerd-codex-transport.test.ts src/drivers/dot/runnerd-dot-driver.test.ts src/live/runnerd-restart-continuation.test.ts src/live/runnerd-codex-transport-settlement.test.ts --maxWorkers=2
cd ../..
node node_modules/vitest/vitest.mjs run packages/adapter-utils/src/runner-connectivity.test.ts
```

## Risks

- Startup ordering and connection cleanup are sensitive. Focused tests
cover local registration, outbound validation, CA mapping, ingress
ordering, release, and warm authority guards.
- No live SSH host or sandbox provider was used. Existing mocked remote
tests and real local Runner tests passed.
- No database, PRP wire protocol, or Launcher API changes are included.

## Model Used

OpenAI GPT-6 via Codex, with tool use and code execution. The served
model variant and context window were not exposed to this task.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 12:58:05 -07:00
DottaandPaperclip 51b56706fb fix(grok): verify file completion and recover failed sessions (#16305)
## Thinking Path

> - Paperclip manages work and authority for AI agents.
> - Grok Bot runs outside Paperclip and uses authenticated upload grants
for files.
> - Native completion requires a verified file publication receipt.
> - The upload saved the file but did not create that receipt. Real Grok
work could not finish.
> - A failed run also left an assignment that the Stop action could not
fence.
> - This pull request connects verified uploads to normal completion and
repairs that recovery action.
> - The result preserves file integrity and current work-session
authority.

## Linked Issues or Issue Description

Refs: #16219, #16274.

**What happened?**

A real Grok Bot saved a task document and CSV. Semantic completion
rejected the CSV because its upload had no verified publication receipt.
After the managed run failed, Stop rejected its remaining assignment as
a changed session. The driver also discarded the remote monitor's known
failure reason.

**Expected behavior**

An authenticated upload of the exact requested bytes can satisfy normal
completion. Stop can fence the remaining assignment of the selected
failed run. An old Stop action cannot stop newer work. Known remote
lease and reconnect errors remain visible without raw diagnostics or
credentials.

**Steps to reproduce**

1. Connect an experimental Grok Bot agent and verify its routine.
2. Assign a task that requires a document and a file.
3. Upload the file through its accepted assignment and cite the
attachment in semantic completion.
4. Observe the missing publication receipt error on the previous
implementation.
5. For recovery, select Stop after that run fails but its assignment
remains active.

**Paperclip version or commit**

The live defects were observed after #16274. The current candidate
merges master `739f9e40a4` and is
`26c5b9601d1cb2b8f5bb7fcf730f019523763f5b`.

## What Changed

- Record a server-owned publication receipt in the verified upload
transaction. Bind it to the real native Grok run and exact stored file
metadata.
- Validate external publication against the actual upload and admitted
assignment. Keep existing native and person upload rules.
- Recover the committed receipt after a lost response. Reverify the same
bytes when an older upload lacks proof.
- Hold assignment authority through publication with
grant-before-assignment and task-before-run lock order. Disconnect does
not wait for remote file storage.
- Allow Stop to fence the exact lingering assignment of a terminal run.
Reject stale Stop calls for successor work.
- Preserve only known lease/reconnect failure codes from the remote
monitor. Do not expose raw stderr.
- Extend real Rust, overlapping document/upload and disconnect/upload,
file retry, completion and Stop regressions. Document setup, files and
recovery.

## Verification

- The real Rust Grok regression requests a file, checks an overlapping
native document save, concurrent retries and lost-response recovery,
rejects missing delivery evidence, then uses normal semantic completion
and finalization.
- Focused tests cover person MCP, native file handoff, completion, Stop
and both external driver providers.
- Local repository typecheck and production build pass on the current
candidate. The five focused protocol suites pass (264 tests); all 17
external Runner driver tests pass, with the driver unchanged since the
master merge.
- Token gates and Storybook build pass. Nine existing Grok fixture
journeys pass their browser interaction assertions; the static probe
separately records five fixture API routes returning 404. This is UI
fixture evidence only.
- Full repository coverage passed in CI. The duplicate local `pnpm
test:run` was interrupted after CI completed; its partial log is
retained and is not reported as a local full-suite pass. The earlier
aggregate invocation was interrupted for the second review fix.
- All CI gates pass on `26c5b9601d1cb2b8f5bb7fcf730f019523763f5b`: 55
successful checks and two skipped Storybook jobs (no UI diff). This
includes the complete sharded repository suites, native Runner, release
checks, build and security scans.
- Current-head Greptile is 5/5 with zero unresolved findings. The PR has
no merge conflicts.
- The prior live attempt is failed acceptance evidence. Real Grok
requalification, proactive work, controller restart and active-session
Stop remain release gates. This PR does not claim those passed.

## Risks

- Upload and completion now share a durable run receipt. Lock order and
current assignment authority must remain consistent with native
mutations.
- Publication fails closed if the file, run or grant changes. A retry
must keep the same upload identity and bytes.
- Person connections gain no agent authority. No database migration is
required.
- Known exit diagnostics do not prove global Grok termination. Grok
spending remains unreported.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository tools, code
execution and computer use. The exact deployment model ID and
context-window size are not exposed in this session.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 12:54:05 -05:00
DottaandPaperclip ad0bd26bb7 perf(heartbeat): avoid Git inspection for stored workspace reads (#16306)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Heartbeat selects and restores a saved execution workspace before
dispatch.
> - These reads used the workspace detail reader, which also scans Git
and resolves delivery state.
> - Configuration reads do not use those live display fields. Each extra
process can delay other controller work.
> - This pull request adds a stored-record reader and uses it in
configuration, environment selection, and finalization.
> - It also derives ancestry from the existing ahead/behind result and
skips delivery HEAD reads for unfinished tasks.
> - The benefit is fewer Git processes while live execution and cleanup
guards remain in place.

## Linked Issues or Issue Description

Related: #16282, #16302, #13619. Those changes reduce background or
display scan demand. This change removes live display inspection from
execution configuration reads.

**What existing behavior does this improve?**

Heartbeat workspace configuration reads and workspace close-readiness
checks.

**Current behavior**

Reading saved workspace configuration also loads runtime health, Git
status, ancestry, and delivery state. Close readiness walks history
twice to determine ancestry.

**Proposed behavior**

Execution reads stored configuration through a company-checked method.
Live branch validation and destructive cleanup still inspect current Git
state. Close readiness uses its ahead/behind result to determine
ancestry.

**Reason and benefit**

A synthetic benchmark of the actual service returned identical stored
fields for 20 workspaces. Detail reads launched 60 Git commands and took
689 ms. Stored reads launched zero Git commands and took 8 ms. This was
a local macOS measurement, not an end-to-end cloud latency claim.

**Breaking changes**

None to public APIs or saved data. Workspace detail responses keep their
live fields.

## What Changed

- Add `getStoredById(companyId, id)` and use it in three heartbeat
reads.
- Preserve the access-denied error for a foreign-company workspace.
- Remove the extra `merge-base` process and the unused HEAD read for
unfinished tasks.
- Add recovery, company-boundary, ancestry, and cleanup regressions.
- Add a repeatable database/Git benchmark and document the read
boundary.

## Verification

- The new environment-selection regression failed before the fix with
`Live Git inspection is unavailable` and passes after it. All eight
environment-selection tests pass.
- On the final rebased commit, workspace service, environment selection,
and branch finalization: 89 tests passed. Branch-containment,
runtime-execution, and display-cache suites also passed during
development.
- `pnpm -r typecheck`: passed.
- `pnpm build`: passed.
- `pnpm test:run`: started locally, then stopped after all full-suite CI
test shards passed on the final commit. The complete local run was not
finished; CI provides the full-suite result.
- Final commit `9a13ef0481b7bf6a60b2b7ac13f35f78a14e232c`: 55 successful
checks and 2 expected skips. Greptile: 5/5, no actionable findings.
- `pnpm --filter @paperclipai/server exec tsx
../scripts/benchmark-workspace-record-reads.ts`: identical stored
fields, 60 to zero Git commands, 689 ms to 8 ms for 20 reads.

## Risks

- Execution callers must use saved fields only. Detail readers and live
branch and cleanup guards remain separate.
- A failed ancestry comparison stays unknown. Automatic cleanup still
rejects unknown delivery state and rechecks Git before deletion.
- This patch has not been deployed. It does not prove the full slow-run
latency is resolved.

## Model Used

OpenAI Codex, GPT-6. The exact model identifier and context window are
not exposed in this session. Used reasoning, repository inspection, code
editing, and local command execution.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 12:53:41 -05:00
DottaandPaperclip 4460785534 fix(skills): preserve referenced files in large repository imports (#16308)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents use skills imported from project repositories.
> - Large repositories fall back to isolated skill folders.
> - That fallback drops shared files and turns advisory links into
import failures.
> - This pull request retains bounded referenced files and keeps missing
references advisory.
> - Users can import valid skills such as test-audit without editing the
source repository.

## Linked Issues or Issue Description

Refs #16066. Refs #16256. Refs #16288. Related draft #15730 adds
optional reference selection to individual skill packages. This fix
applies automatic repository fallback and does not add that selection
UI.

**What happened?**

A project source showed 86 warnings. It rejected 23 skills solely
because references were absent from the isolated package. It also
treated web routes and inline Markdown examples as files. For
test-audit, repository scripts were resolved inside the skill folder.

**Expected behavior**

Retain referenced files that fit the package limits. Missing references
must not block an otherwise valid selected skill. Preserve content
validation and installed shared files.

**Steps to reproduce**

Import paperclipai/paperclip at commit
09a2f0b7b0 as a repository source. Select
test-audit. Inspect its references and saved files. Repeat with a skill
whose optional reference is missing.

## What Changed

- Add a bounded fallback that keeps skill folders and transitive
Markdown references with their original repository paths.
- Resolve existing repository-root resource paths. Ignore site-relative
web links and inline Markdown examples.
- Keep missing references advisory, including explicit imports and
automatic project imports.
- Share fallback with file previews. Reinspect cached dependency
failures at an unchanged upstream commit.
- Retain existing versions when fallback cannot preserve installed
shared files. Keep file limits, content audits, authorization and work
budgets.
- Update product contracts and Skills maps. No schema or public API
changes.

## Verification

- Red/green: three original regression cases failed before the fix and
passed afterward. Two review controls also reproduced omitted directory
siblings and duplicate blob reads before their fixes.
- Focused scanner, repository package, persistence, project
synchronization and GitHub access tests: 126 cases passed across local
and isolated Linux runs.
- Full recursive typecheck and `pnpm build` passed locally on
`555bd3fc90314dbf23ed0ffe7034429946c1a471`. The [full Paperclip CI
workflow](https://github.com/paperclipai/paperclip/actions/runs/38596749890)
passed on that head, including all general, serialized, Runner and eight
browser test shards. The duplicate local `pnpm test:run` was stopped
after CI completed (exit 143); it is not claimed as a completed local
full-suite pass.
- Real GitHub scan at the pinned public commit: 77 skills, 454 retained
files, 9,117,635 bytes. test-audit has no missing references. The full
source reports 23 warnings, including remaining file validation and
genuine missing-reference notices.
- Local browser: scanned the public repository, selected test-audit and
create-issue-interaction-ui, and imported both. The second skill
retained an advisory missing-reference warning. Both remained installed
after reload. The saved package exposes `scripts/run-vitest-stable.mjs`
(25,813 bytes) and `scripts/general-server-shard.mjs` (2,813 bytes). No
GitHub responses were mocked. No model execution was needed for this
import fix.
- Map checks and Git whitespace checks passed. Greptile is 5/5 on the
current head with both review findings resolved. The external Superagent
security scan concluded neutral/inconclusive after its agent exceeded
the prompt deadline. A check-run retry request returned HTTP 404. All
Paperclip CI gates passed, but merge is held pending a conclusive
security scan; this is not an all-checks-green claim.

## Risks

- Dependency detection covers explicit Markdown links and resource
paths. It does not infer all script imports or runtime dependencies.
- Referenced content can exceed package limits. The existing bounds
still apply, and warnings disclose omissions.
- Content validation still rejects invalid entrypoints and omits
rejected support files. This change does not make every upstream skill
importable.
- No migration is required. Existing sources with the old dependency
error are inspected again on refresh.

## Model Used

OpenAI Codex, GPT-6 family. The runtime did not expose the exact serving
model ID or context window. Used reasoning, code editing, terminal
execution and browser tools. No subagents were 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 12:53:04 -05:00
Devin FoleyandPaperclip 5b29b9062e fix(server): compact finished task worktrees and shorten the reaper cooldown (#16304)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Each task can run in its own isolated Git worktree, and each
worktree installs its own dependencies
> - The terminal-workspace reaper removes a worktree only when the task
tree is finished, the delivery is merged, the files are clean, and a
7-day cooldown has passed
> - A recurring task that runs every hour keeps a week of worktrees, and
each worktree holds gigabytes of `node_modules`. A finished task with no
merged pull request, or with leftover agent files, keeps its worktree
forever
> - On a busy instance this fills the disk in days, and a larger volume
only delays the next failure
> - This pull request adds compaction: one hour after a task tree
finishes, the reaper removes regenerable directories from the worktree
and keeps the workspace, the branch, and all source changes
> - The benefit is that finished tasks stop holding gigabytes of disk,
and no work is lost because a resumed task regenerates the files when
its workspace is provisioned again

## Linked Issues or Issue Description

Refs #2022 (retention for workspace cleanup). Related: #11936 and #14608
add external reclaim scripts (a scheduled tool and a disk-guard step).
This pull request does the reclaim inside the server's
terminal-workspace reaper instead. That way it uses the reaper's
lifecycle lock, issue-tree checks, and live-run checks, and needs no
extra scheduler.

**What existing behavior does this improve?**
Disk retention of execution workspaces after their tasks finish.

**Current behavior**
A finished task keeps its full worktree, including installed
dependencies and build output, for at least 7 days. If the delivery is
not merged, or the worktree has uncommitted or untracked files, the
reaper never removes it. On one instance, an hourly recurring task
created about 22 worktrees per day at about 3 GB each (about 2.8 GB of
that was `node_modules`). Together with finished tasks that had no
merged pull request, they filled a 100 GB volume in about two days.

**Proposed behavior**
One hour after a task tree becomes terminal, the reaper removes
regenerable directories (installed dependencies, build output, tool
caches) from the worktree. It keeps the worktree, the branch, the
commits, and every tracked, untracked, or other ignored file. When the
task resumes, the existing reuse path runs the provision step, which
reinstalls the dependencies. Clean, merged worktrees are archived after
1 day instead of 7.

**Reason and benefit**
Most of a finished worktree's size is regenerable. Compaction removes
that part without the risk of deleting work, so it can apply to
workspaces that the reaper must keep (no merged pull request, leftover
files). The shorter cooldown limits the cost of the source checkouts
that stay.

**Breaking changes**
None. The default archive cooldown changes from 7 days to 1 day.
`PAPERCLIP_WORKSPACE_REAPER_COOLDOWN_DAYS` still overrides it.
`PAPERCLIP_WORKSPACE_COMPACTION_ENABLED=false` turns compaction off.

## What Changed

- New `server/src/services/workspace-compaction.ts`:
- Finds directories with a regenerable name (`node_modules`, `dist`,
`build`, `coverage`, `storybook-static`, `target`, `.cache`, `.next`,
`.nuxt`, `.parcel-cache`, `.svelte-kit`, `.turbo`, `.vite`).
- Keeps only the directories that the repository's own `.gitignore`
files ignore and that hold no tracked file. A negated pattern
(`!build/`), a global excludes file, and `.git/info/exclude` do not
count. Each candidate is checked per path, so dependencies below an
ignored parent (`vendor/tool/node_modules`) are found.
- Searches inside a regenerable-named directory that does not qualify
(for example a tracked `build/`), so its ignored dependencies still go.
- Never enters `.git` or `.paperclip` (the worktree instance data), and
never follows a symlink.
- Each Git read has a 60-second deadline and stops when the sweep is
aborted.
- Moves the directories into `.paperclip-compaction-trash` beside the
worktree with a same-filesystem rename, and can restore them.
- `sweepTerminalWorkspaces` compacts each terminal workspace once per
terminal cycle, after the compaction delay and before the archive
cooldown check:
- Only isolated linked Git worktrees qualify. A shared workspace, a
project's main checkout, or a row whose `.git` is a directory is never
compacted.
- The move runs inside a transaction that holds the workspace lifecycle
lock. The guard checks a fresh row and the same lifecycle generation. It
also checks that the issue tree is terminal, that no queued or running
run works on the tree or the workspace (by checkout or execution run, or
by run context), and that no runtime service runs from the workspace.
- The guard runs before the move and again after it. If the second check
fails (for example, the task reopened or a run started), the directories
move back. Otherwise the trash is deleted.
- The worker wakes at the compaction deadline, not only at the archive
deadline.
- Stale trash from an interrupted compaction or a failed delete is
purged by later sweeps. The sweep checks each worktree parent at most
every 30 minutes, whether or not anything is left to compact.
- New counters `compacted` and `compactionFailed` in the sweep result.
The activity log gets an `execution_workspace.regenerable_files_removed`
entry with the removed paths.
- Config: new `PAPERCLIP_WORKSPACE_COMPACTION_DELAY_HOURS` (default `1`)
and `PAPERCLIP_WORKSPACE_COMPACTION_ENABLED`. The default for
`PAPERCLIP_WORKSPACE_REAPER_COOLDOWN_DAYS` changes from `7` to `1`. Both
new keys are added to the run-failure diagnostics allowlists.
- Docs: `doc/DEVELOPING.md` documents the new variables and has a new
"Terminal workspace compaction" section. `doc/SPEC-implementation.md`
describes compaction.
- `execution-workspaces.ts` was already above 1,000 lines. The
file-system logic is in the new module. Only the guarded orchestration,
which uses the existing lifecycle lock and run predicates, is in the
service.

## Verification

- `pnpm exec vitest run src/__tests__/workspace-compaction.test.ts
src/__tests__/execution-workspace-compaction.test.ts` (in `server/`): 27
passed. `workspace-reaper-cooldown-config.test.ts`: passed.
- Unit tests: directory selection (ignored, untracked, regenerable name
only, `.env` and local data kept, tracked `coverage` kept, `.paperclip`
skipped, symlinks not followed), nested candidates under a source
`build/` and under an ignored `vendor/`, a negated `!build/` kept,
global and `info/exclude` rules ignored, glob characters in names,
abort, main checkout vs linked worktree, move and restore, restore that
keeps a recreated directory, and stale-trash purge.
  - Embedded Postgres tests:
- A no-PR, unmerged, dirty workspace is compacted. HEAD, the branch ref,
`git status --porcelain --untracked-files=all`, untracked notes, `.env`,
local data, and `.paperclip` data stay the same, and the row stays
active.
    - A delivered workspace inside the cooldown is compacted.
    - The delay is respected, and the worker wakes at the delay.
- An active checkout run, a queued run found through its context, an
unfinished child issue, and a running runtime service all block
compaction.
- The task reopens during the move: the files are restored and
unchanged.
    - A run starts during the move: the files are restored.
    - A shared workspace and a main checkout are never compacted.
- Compaction runs once per terminal cycle and again after a later cycle.
    - Disabled compaction does nothing.
- End to end: `realizeExecutionWorkspace` creates a worktree, the sweep
compacts it, and a second `realizeExecutionWorkspace` reuses the same
worktree. The provision command regenerates `node_modules`, and the
untracked notes and the unmerged commit are still present.
- Leftover trash is purged when the workspace has nothing left to
compact.
- Mutation checks: when the post-move guard is disabled, both race tests
fail. Without the negation fix or the repository-only source fix, their
tests fail.
- `pnpm exec vitest run` for `execution-workspaces-service.test.ts`,
`terminal-workspace-work.test.ts`, `execution-workspace-reopen.test.ts`,
`server-startup-feedback-export.test.ts`, and
`workspace-runtime.test.ts`: 335 passed. Two `workspace-runtime.test.ts`
cases fail locally with and without this change (`does not diagnose a
missing remote branch as a rejected credential`, `rejects explicit main
on a master-only remote ...`). They also fail on unmodified `master` on
the same machine, so the cause is the local Git environment.
- `pnpm typecheck` in `server/`: passed. `pnpm check:module-boundaries`:
passed.

## Risks

- A repository can keep something that is not regenerable in an ignored
`dist`, `build`, `target`, or `.cache` directory. Compaction removes it.
The fixed name list, the ignore check, and the tracked-file check limit
this. `PAPERCLIP_WORKSPACE_COMPACTION_ENABLED=false` turns compaction
off.
- A repository without a provision command does not get dependencies
back automatically. This is the same state as a new worktree for that
repository, where the agent already installs them.
- A resumed task pays for a dependency install again. This happens only
for tasks that finished more than an hour earlier.
- The shorter default cooldown removes clean, merged worktrees after 1
day instead of 7. An operator who wants the old hold can set
`PAPERCLIP_WORKSPACE_REAPER_COOLDOWN_DAYS=7`.
- Self-hosted instances get the same behavior. There is no schema
change.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), 1M context, through Claude Code
with tool use (shell, file edits, test runs). A separate Claude agent
read the reaper code during the diagnosis.

## 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-10-14 09:49:04 -07:00
DottaandPaperclip adb69e8f09 feat: default new Codex agents to Paperclip Runner on Linux and macOS (#15683)
## Thinking Path

> - Paperclip is the open source control plane for agent companies.
> - People choose a harness when creating an agent.
> - The server should resolve that harness to a qualified runner.
> - Codex is the first harness in the staged default rollout.
> - Saved agents must retain their execution choices.
> - New Codex agents therefore use Paperclip Runner on qualified
macOS/Linux targets, with an explicit Legacy override.

## Linked Issues or Issue Description

Refs #16028, #15555, #15673, #15422, #15631, #15926, #15932, #16149.

The runtime/install prerequisite and release repair are merged. This
slice owns Codex creation, setup, configuration and preservation. Other
harness defaults remain unchanged. It adds no database migration;
incoming master migrations, required maps and regression coverage are
retained.

## What Changed

- Resolve `runner: auto | paperclip | legacy` at the common creation
boundary across onboarding, API/CLI, hires, built-ins, plugins and
imports. Freeze pending hires and export resolved execution choices.
- Preserve existing runners on upgrades, unrelated edits and import
updates, including compatible accounts, workspace settings, Codex effort
and intentional clears.
- Use the underlying harness picker and branding; put the runner
override in Advanced. Use the shared dropdown and update
production-component stories and creation contracts.
- Support qualified Apple Silicon, Intel Mac, Linux x64 and Linux ARM64.
Linux requires GNU libc 2.39+. Disabled, external, unsupported and
unqualified combinations retain their applicable Legacy execution.
- Test the selected runtime/account/model/environment. Ordinary Connect
probes eligible immutable managed targets and refreshes scoped
discovery. Creation/discovery do not provision. Auth, dependency,
incompatible-setting and cleanup failures remain actionable without
changing runners.
- Keep setup probes read-only, preserve local/SSH isolation and resolve
packaged assets before staging credentials. Bound cleanup reads by the
remaining absolute deadline; physically close every owned ingress socket
before endpoint release. Unconfirmed cleanup retains recovery state.
- Preserve validated completed-onboarding callbacks through startup
recovery. First file-producing work belongs to the child task and uses
the existing dependency/block handoff and task-scoped registration.
- Reuse existing E2E fixtures, artifact checks, reporting and controls.
Keep Docker/Cargo/native compilation remote. Paperclip tarballs contain
no Codex payloads; official optional npm platform dependencies remain.

## Verification

Current tested head: `1c03a8815672d82c264a9b08d4ea1984e8fae107`.
**Merge-ready: required CI, fresh review, package qualification, Mac
core acceptance and Test task/follow-up acceptance pass.** The existing
attachment and native-continuity oracles independently verify both Test
downloads, run attribution and unchanged native/provider/workspace
identities. The maintainer authorized a code-owner-review exception for
this PR only. The first Test setup failure and normal Board
completion-review friction are retained below. Actual post-merge
packages and private Cloud distribution remain separate qualification
gates.

- [Current
CI](https://github.com/paperclipai/paperclip/actions/runs/38564241625):
all 48 ordinary jobs and required verify/E2E pass. Attempt 1 retained
six simultaneous host-shutdown cancellations. One failed-only retry
passes those six lanes and two aggregates, preserving 40 successes.
Actual integration checkout `53104c1` combines this head with master
`c80`; it is not relabeled as the branch tree. Fresh exact-head Greptile
5/5, 30 resolved threads, no actionable findings.
- [Exact-source package
qualification](https://github.com/paperclipai/paperclip/actions/runs/38565287239):
nine applicable jobs pass, nine conditional jobs skip. Four remote
native producers; Linux x64 clean install; Intel Mac, Apple Silicon and
Linux ARM64 native startup/physical cleanup controls. All 18
tarballs/four daemon source/hash/manifest bindings and 343 migrations
through 0344 verify. No Codex payload is packed. These hosted tarballs
are not a published npm canary.
- Normal Apple Silicon installation and credential-free native
startup/cleanup pass using the packaged daemon, with no source build or
provider turn. Fresh installed DB initialization passes. Upgrade
651a→1c03 preserves all three complete execution configs, accounts, ten
recorded run profiles, paused budgets, active incidents, cost summaries
and two historical unpriced events. Historical recovery state is
retained.
- Cleanup regressions: four delayed-read baseline failures become 15
passing deadline/authority controls. No-acknowledgement and prior-error
socket ownership regressions become 12 passing controls. Direct
runtime/server types, maps: 35 areas / 176 IDs, diff checks and
independent reviews pass. Permission/auth/reconnect policy and the
10-second setup budget remain intact.
- Existing source coverage includes creation/selection/imports, pending
approvals, recovery, account/lifecycle handling, mounted forms,
dropdowns and E2E support. Full typechecks/tests/build/token and
applicable Storybook checks pass in the recorded current CI; no
duplicate suite or weakened grader.
- Literal 9546 live Mac proof: ordinary Auto Codex onboarding, useful
document, real completed-child callback/reply, seven successful native
runs, manual follow-up and independently downloaded1,101-byte child
file. Existing Legacy title edit/reload preserves execution; two real
Legacy runs produce independently checked15/34-byte files. These remain
historical source records.
- Current literal 1c03 Mac core acceptance passes independent checks:
ordinary Auto native Codex onboarding without runner override; only
timeout 180 seconds/concurrency 1 saved; six successful native runs and
six serialized turns (maximum overlap 1); both same-task continuity
checks and five mechanical callback checks; actual rendered parent reply
and exact child downloads. The useful handoff advances from revision 1
to 2. Both files are 48 bytes, SHA-256
`8b742bd4e8123a7c078f0a8d9f3449fbfa8a320d7d90be641a2818db3c53d92c` and
`f42db9774fa06e804ab7d596fc8b922a2b638921c418b87d0111151d290a6afe`;
exactly one byte changes and old history remains. The agent is paused
with zero active runs/errors. Both artifact work products have
`isPrimary=false`, and descriptive titles fail a stricter title-helper
assumption; these retained results are scoped editorial follow-up, not
new merge gates. Six source-bound screenshots are uploaded and verified
in the private primary QA report.
- [Literal 9546 managed
proof](https://d1p6rlowie26tp.cloudfront.net/runner-e2e/campaigns/gha-38485488621-1/index.html):
original 19 assertions and completed judge pass, four native turns,
child-bound240-byte file and session/workspace continuity. Checkout
reports a verified generated target-lock mutation; full final porcelain
was not retained. Signed execution-image identity remains separate from
Core source.
- Actual 651a Test deployment succeeds on one owned/pinned target. One
ordinary Auto API-key Connect then fails native cleanup before hire.
Controller turn/drain/suspend completion and service-recorded
environment cleanup are observed; they do not certify physical deletion
or native authority retirement. Failure/recovery/unknown holds are
retained. No Legacy switch or unchanged paid retry. The two source
repairs above require actual treatment.
- [Current immutable Core
preview](https://github.com/paperclipai/paperclip/actions/runs/38570365725)
and matching DB/shared artifacts pass. One owned, pinned Test target
serves exact `1c03`; identical migration history correctly skips the
migrator. Ordinary Auto Codex Connect succeeds and hires native; saved
native selection and QA bounds survive reload. The initial child
preparation attempt fails before provider work, with actual stdout
identifying `renewal_deadline`; its trigger remains unknown and the
failure is retained. Normal Board **Try again** and **Resume** recover
the child to Done, producing a browser-verified 44-byte file, SHA-256
`0a8458b1efcc2d7a0d815689458c4caab039a46c7c7a3e5805de6bc8b1a96ecc`. The
actual parent callback reports the completed child document/file. The
existing validator requires a parent-owned attachment, so recovery
enters normal Review; after byte checking, normal **Approve completion**
reaches parent Done. No forced status or duplicate parent file is used.
The validator is unchanged from integrated and current master.
Autonomous initial callback-to-Done is not proven. The same-child
follow-up completes document revision 2 and publishes a second
independently downloaded 44-byte file, SHA-256
`22022257300fae762784632d08d3bbe0bab61e2ce81f91864a14dcd7aa464591`; only
the revision digit changes. The unchanged existing native-continuity and
attachment oracles pass. All ten recorded runs are terminal: nine native
successes and the retained pre-provider setup failure. The QA agent is
paused with zero active runs. The pre-existing parent evidence scope
mismatch is a separate follow-up, with ordinary completion review
exercised rather than a forced status or relaxed validation. This
unsigned Core preview remains distinct from post-merge private Cloud
composition.

## Risks

- Saved agents are not migrated. Explicit runner changes use normal
configuration/session rules; historical runs retain their recorded
execution.
- Native readiness depends on verified assets and server-owned target
qualification. A Legacy probe, HTTP200 or successful account save cannot
prove native readiness.
- Recovery exceptions remain restricted to validated
completed-onboarding reports; process-loss, ownership and terminal-state
safeguards remain active. Unexecuted live journeys stay open.
- Linux ARM paid tasks, Windows, Alpine and 32-bit ARM remain deferred.
Cold Cargo cache misses are recorded; no warm-cache speedup is claimed.
- Historical invoices/physical cleanup remain unconfirmed and held.
After merge, exact published npm canary, signed standard image/migrator,
private composition and serving/live acceptance still require
qualification. Production deployment/default promotion is not
authorized.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, tools, code execution and
parallel agents. Exact serving snapshot and context-window size are not
exposed.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used with 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 linked public issues or PRs above
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change and contains no internal
ticket id
- [x] I have run the stated focused tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation
- [x] I have considered and documented risks
- [x] All Paperclip CI gates are green on the current candidate
- [x] Greptile is 5/5 with no open findings on the current candidate
- [x] I will address all reviewer comments before requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 11:33:02 -05:00
DottaandPaperclip 21ee5c9c85 feat(sentry): add opt-in run failure reporting with saved run references (#16300)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent runs retain execution results and output in the instance.
> - Operators can report run failures to Sentry for investigation.
> - A backend Sentry DSN currently enables those reports with no
separate choice.
> - A repair bot also needs the exact instance, company, agent, and
saved run.
> - This pull request adds an explicit reporting mode and authenticated
run references.
> - The benefit is that operators can investigate every failure on
selected deployments while keeping other deployments quiet.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Optional server reporting of terminal agent run failures to Sentry.

**Current behavior**

A backend DSN enables run failure reports. Known account, quota, and
connection failures are excluded. Reports omit company and agent
references. Runs without a task are skipped. Tool-name collision errors
omit the conflicting bindings.

**Proposed behavior**

Set `PAPERCLIP_SENTRY_RUN_FAILURES=unexpected` to retain the existing
filters. Set it to `all` to report every failed or timed-out run,
including known setup and quota failures. The default is `off`. Expected
stops stay excluded. Each report supplies identifiers and API paths that
a bot can use with its own authorized Paperclip connection.

**Reason and benefit**

Operators can enable broad failure reporting on one staging deployment.
A bot can fetch the saved run, events, and logs instead of relying on a
short error label or copying complete transcripts into Sentry.

**Breaking changes**

A backend DSN alone no longer enables run failure reports. Existing
operators must set `PAPERCLIP_SENTRY_RUN_FAILURES=unexpected` to retain
the previous policy. Ordinary server exception capture remains enabled
by the DSN. Failure grouping gains the phase and original cause code
when available.

Refs #16281, #16297, #16298. These cover related tool discovery and
failure diagnostics. This change retains their existing boundaries and
does not depend on their open changes.

## What Changed

- Add `off`, `unexpected`, and `all` modes. Missing and invalid values
keep reporting off before diagnostic reads.
- Keep the Sentry backend DSN and audited optional SDK requirements.
- Include company, agent, run, and optional task identifiers. Include
versioned paths for the saved run, events, log, and task. Add absolute
run URLs only for a valid public HTTP(S) origin.
- Report company runs without a task. Prefer the first-class issue
binding when selecting the task reference.
- Select saved native provider/model settings, timestamps, and original
failure codes. Preserve gateway reason codes through redacted exception
projection.
- Group by error code, adapter, and available phase/cause. Keep run IDs
and error text out of grouping.
- Include the tool names and connection bindings in collision errors
without copying tool schemas or provider metadata.
- Document configuration, upgrade behavior, bot access, redaction,
retention, and best-effort delivery.

## Verification

- All 56 checks passed for `cccb47a721eaa1cc36e63278fc177b9101477541`.
Two unrelated Storybook jobs were skipped by their path filters. No
checks remain pending.
- Complete [Paperclip CI
run](https://github.com/paperclipai/paperclip/actions/runs/38591422284):
passed. This covers general and serialized tests, Runner checks, browser
E2E shards, typecheck, build, release-registry checks, canary dry run,
and Docker checks.
- Greptile reviewed that exact head: 5/5, with no unresolved threads or
actionable findings. Superagent, Socket, and Snyk checks passed.
- Local focused reporting, diagnostics, collision, and real SDK contract
tests: 410 passed across five suites. This includes the external-wake
filter in both reporting modes.
- The audited `@sentry/node@10.71.0` tests use an in-memory transport.
No events were sent to Sentry.
- `pnpm -r typecheck` and `pnpm build`: passed locally after the final
rebase.
- `node scripts/check-module-boundaries.mjs` and `git diff --check`:
passed.
- The full local `pnpm test:run` command was stopped to avoid
duplicating the complete CI shards. It did not finish locally. The full
suite passed in CI.

## Risks

- The new default disables existing run failure exports until the
operator selects a mode. The upgrade setting is documented.
- `all` can increase event volume and include known user-action
failures. Secret redaction and diagnostic limits still apply.
- Reporting remains asynchronous and best effort. This PR adds no
durable delivery queue and does not install a bot or Sentry alert rule.
- API references grant no authority. Existing company, private-task, and
run-output checks still apply. Complete transcripts stay in Paperclip.
- This changes optional Sentry error reporting. It changes neither
first-party Telemetry nor OpenTelemetry span exports. It adds no
database migration.

## Model Used

- OpenAI GPT-6 through Codex, with reasoning, shell, file editing, and
test execution. The exact backend deployment ID and context window are
not exposed in this session.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 10:56:40 -05:00
DottaandPaperclip 09a2f0b7b0 refactor(server): isolate provider message admission and runtime policies (#16289)
## Thinking Path

> - Paperclip helps people manage AI agents for work.
> - Chat channels turn external messages into shared task workflows.
> - The main service still contains some provider decisions.
> - These decisions belong with the existing channel modules.
> - This pull request isolates provider message-admission and runtime
policies.
> - Shared authorization, persistence, and runtime ownership keep their
current order.

## Linked Issues or Issue Description

Refs: #16235. Stack part 3/3. Base: `master`. #16276 and #16283 are
merged.

**What existing behavior does this improve?**

Provider ownership and readability in the chat channel service.

**Current behavior**

The entry point combines shared workflows with provider decisions.

**Proposed behavior**

Provider modules own their decisions. Shared modules own product state
and authorization.

**Reason and benefit**

Maintainers can find provider behavior in its channel folder.

**Breaking changes**

None intended. Public service methods and durable data shapes stay
compatible.

## What Changed

- Move Discord SDK admission and its barrier to the Discord message
module.
- Move Photon live-message eviction and startup recovery policy to its
runtime callbacks module.
- Move Slack mention eligibility and Teams setup-channel eligibility to
their message modules.
- Keep Photon deferred ingress and Speko conversation policy with their
provider modules.
- Keep ordered SDK message batching in a shared runtime module with a
live per-message ingress callback.
- Keep receiver leases, cache lifetime, startup failure cleanup,
authorization locks, and transaction ownership shared.
- Reduce `chat-channels.ts` from 3,570 to 3,548 lines.

## Verification

- The same 46 runtime/admission integration cases and all 18 Photon
channel cases passed before and after in the owned Linux container.
- The existing first-channel Teams orphan-admission case passed before
and after moving its eligibility predicate.
- The same 94 SDK adapter and lifecycle-normalization tests passed
locally.
- Server typecheck and module-boundary checks passed.
- Token comparison confirms ordered SDK batching and every processor
argument, plus Photon invalidation and its live runtime lookup.
- The shared factory creates no cache or worker. It reads the ingress
policy for each message and awaits processing in order.
- The extraction rebased without conflicts onto master after #16276 and
#16283 merged. Git range-diff reports the same patch. Final head
`7b63b569e2a0dbcc959310f565f77f367fa64aa0` has the same patch by
range-diff. The 94 SDK/lifecycle tests, server typecheck, and boundary
check passed on the final head. All CI gates pass on that exact head.
Greptile reviewed it at 5/5 with no inline findings or unresolved review
threads.
- One Runner CI test failed its original short timing window. The
unchanged test passed earlier-head CI and local reproduction. The single
failed-job retry passed.
- Cumulative `pnpm -r typecheck` and `pnpm build` passed. All 1,321
channel integration cases passed. The normal UI, CLI, database, adapter,
plugin, and serialized API test selections ran.
- The first Linux `pnpm test:run` returned 1. Original-source runs
reproduced the local failures. Container-only repairs supplied missing
tools, process reaping, and sufficient Node heap. The existing
mocked-fetch CLI telemetry fixture passed with the image opt-out flag
overridden only for that fixture.
- One local venue limitation remains: adapter-utils T24 fails unchanged
on both original and final source in the Linux container. Its entire
56-case suite passes before and after on the host. All other failed or
aborted suites passed in repaired reruns. No test assertion or timeout
changed.
- These fixtures prove application integration. They do not prove live
provider execution.

## Risks

- A captured option value or changed callback order could affect
deferred ingress or stale-runtime handling.
- The ingress callback and runtime getter remain live. Core maps and
receiver lease operations keep one owner.
- No schema migration or public API change is included.

## Model Used

OpenAI GPT-6, running in Codex with reasoning, tool use, and code
execution. The exact deployment 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-10-14 10:50:34 -05:00
DottaandPaperclip 7df55f3dcc refactor(server): isolate provider webhook lifecycle decisions (#16283)
## Thinking Path

> - Paperclip helps people manage AI agents for work.
> - Chat channels turn external messages into shared task workflows.
> - The main service still contains some provider decisions.
> - These decisions belong with the existing channel modules.
> - This pull request moves webhook recovery and payload-scope decisions
to their provider modules.
> - Shared authorization, persistence, and runtime ownership keep their
current order.

## Linked Issues or Issue Description

Refs: #16235. Stack part 2/3. Base: `master`. #16276 is merged.

**What existing behavior does this improve?**

Provider ownership and readability in the chat channel service.

**Current behavior**

The entry point combines shared workflows with provider decisions.

**Proposed behavior**

Provider modules own their decisions. Shared modules own product state
and authorization.

**Reason and benefit**

Maintainers can find provider behavior in its channel folder.

**Breaking changes**

None intended. Public service methods and durable data shapes stay
compatible.

## What Changed

- Move GitHub revoked-installation recovery classification to
`github/webhook-scope.ts`.
- Move Teams payload-scope validation to its message module.
- Record normalized GitHub, Telegram, and Teams lifecycle events through
one shared durable call.
- Keep Slack callback ordering and persistence failures outside parser
catches.
- Reduce `chat-channels.ts` from 3,598 to 3,570 lines.

## Verification

- The same 86 selected webhook/lifecycle integration cases passed before
and after in an owned Linux container.
- The same 17 selected Telegram edit and Teams edit/delete/restore cases
passed before and after in that container.
- The same 94 SDK adapter and lifecycle-normalization tests passed
locally before and after.
- Server typecheck and module-boundary checks passed.
- Token comparison confirms the shared recording payload and runtime
fence, plus the Teams scope and parser inputs.
- The host database baseline failed before assertions because System V
shared-memory IDs were exhausted. No other PostgreSQL server or host
setting was changed. Database verification used an isolated container
under its unprivileged user.
- The extraction rebased without conflicts after #16276 merged. Git
range-diff reports the same patch. The 94 SDK/lifecycle tests and server
typecheck passed on this base. All CI gates pass on rebased head
`d835d7c3815c168226fa646aec6370b6a8ac94c1`. Greptile reviewed that exact
head at 5/5, with no inline findings or unresolved review threads.
- Cumulative `pnpm -r typecheck` and `pnpm build` passed. All 1,321
channel integration cases passed. The normal UI, CLI, database, adapter,
plugin, and serialized API test selections ran.
- The first Linux `pnpm test:run` returned 1. Original-source runs
reproduced the local failures. Container-only repairs supplied missing
tools, process reaping, and sufficient Node heap. The existing
mocked-fetch CLI telemetry fixture passed with the image opt-out flag
overridden only for that fixture.
- One local venue limitation remains: adapter-utils T24 fails unchanged
on both original and final source in the Linux container. Its entire
56-case suite passes before and after on the host. All other failed or
aborted suites passed in repaired reruns. No test assertion or timeout
changed.
- These fixtures exercise application state and the service entry point.
They do not prove live provider execution.

## Risks

- A scope or ordering error could record an event for the wrong provider
context.
- The selected parser returns normalized data. The shared service keeps
durable recording and runtime fencing.
- No database migration or API change is included.

## Model Used

OpenAI GPT-6, running in Codex with reasoning, tool use, and code
execution. The exact deployment 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-10-14 10:38:12 -05:00
DottaandPaperclip 74ba530b9e fix(workspaces): keep cleanup Git scans fresh and dedupe page reads (#16302)
## Thinking Path

> - Paperclip manages execution workspaces for agent tasks.
> - It must preserve work when it closes a workspace.
> - The terminal-workspace worker checks Git before it decides whether a
workspace can close.
> - A request for fresh Git status could join a scan that started before
a file changed.
> - Several records for one directory can also repeat the same Git work
in a bounded page.
> - The worker already reads the task tree before Git, then reads it
again for delivery assessment.
> - This pull request fixes the fresh-read boundary and removes those
repeated reads.

## Linked Issues or Issue Description

- Refs #14759 and #14423 for the background Git scan load.
- Refs #16282 for the merged event-driven terminal-workspace worker.
- Refs #13619 for the original community investigation and tests. Thanks
to @fayonation for identifying the shared-directory and in-flight scan
cases. This implementation starts from current master and adapts those
ideas to the new worker.

## What Changed

- Give correctness-sensitive Git status requests their own scheduler
scan. They skip completed cache entries and older scans in progress.
- Share compatible Git readiness results across workspace records in one
bounded worker page. The key includes company, path, branch, base ref,
and runtime ownership. Each record keeps its own task and delivery
assessment.
- Reuse the task tree already read by the worker during delivery
assessment.
- Add regression tests for a file created during an older scan,
compatible scan sharing, and company and branch separation. Update
developer and workspace map documentation.

## Verification

- `pnpm exec vitest run
server/src/services/workspace-git-operation-scheduler.test.ts
server/src/__tests__/execution-workspaces-service.test.ts` — 92 tests
passed.
- `pnpm -r typecheck` — passed with the repository's pnpm 9.15.4.
- `node scripts/check-maps.mjs` — passed.
- `pnpm build` — passed.
- The full local `pnpm test:run` was interrupted when its original
worktree disappeared. All PR test shards passed in GitHub CI.
- Latest commit `e7622127c7d978a9a90857112675834725965d72`: 53
successful PR checks, 2 expected skips, and no failures. Greptile
reviewed this commit at 5/5 with no open findings.

## Risks

- A shared page inspection can become stale if the directory changes
before the next record is assessed. The final cleanup still starts a
separate Git scan and checks current task, ownership, and lifecycle
state under the existing guards.
- The new scan key changes request coalescing for correctness-sensitive
callers. They still use the scheduler's concurrency and queue limits.
There is no database migration or API 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

- OpenAI GPT-6 through Codex, with reasoning, code editing, and shell
tools. The exact serving model ID and context window are not exposed in
this session.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 10:36:41 -05:00
bcfc03bc94 fix(sentry): keep provider-rejected Grok wakes out of run failure reports (#16297)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Paperclip Runner can run an external Grok bot. Paperclip offers
the bot an assignment and sends a webhook wake. The bot must accept the
assignment in 10 minutes.
> - When the Grok endpoint rejects every wake with HTTP 4xx, the bot
never sees the work. The offer expires and the run fails with `Dot
assignment acceptance or execution authority expired.`
> - Paperclip already records each wake in `grok_bot_dispatches` with
its HTTP status. The operator sees the rejection and can retry it. Thus
Paperclip operates correctly in this case.
> - But `reportRunFailure` sends this run to Sentry as an unexplained
backend error. This makes noise and hides real defects.
> - This pull request skips the Sentry report only when durable receipts
prove that the Grok endpoint rejected every wake.
> - The benefit is a cleaner Sentry signal. Local defects, uncertain
delivery, and accepted work stay visible.

## Linked Issues or Issue Description

Sentry issue `PAPERCLIP-BACKEND-3M` (staging, 1 event).

**What happened?**
A Grok bot run on staging failed after 10 minutes in the `preparing`
stage. Sentry got the error `Dot assignment acceptance or execution
authority expired.` with the code `native_provider_terminal_failed`. The
stored receipts show two `mailbox_updated` wakes for the assignment. The
Grok endpoint rejected both wakes with HTTP 400: the first wake and one
operator retry. The assignment was never accepted. Wakes with the same
shape got HTTP 200 before and after this run.

**Expected behavior**
The run fails, and the operator sees the rejected delivery. Sentry does
not get a backend error for a proven external rejection.

**Steps to reproduce**
1. Connect a Grok bot agent and verify its routine.
2. Make the Grok endpoint return HTTP 400 for work wakes.
3. Assign a task to the agent and wait 10 minutes.
4. See that the run fails and that a Sentry event is sent.

**Paperclip version or commit**
`f69c6a8` (master).

## What Changed

- `server/src/services/run-failure-report.ts`: Add
`isRejectedExternalBotWake`. It returns true only when all of these
conditions are true:
- The run failed in native mode with `native_provider_terminal_failed`.
- The finalization `failureDetail.originalFailureCode` is
`dot_assignment_expired`.
- The run has exactly one Dot assignment, and that assignment was never
accepted.
- The assignment has at least one `mailbox_updated` wake, and each wake
has the status `rejected` with an HTTP 4xx status.
- `captureTerminalRunFailure` skips the Sentry capture when the check is
true. If the receipt check itself fails, it logs a warning and the
failure stays reportable. The run status, the task, and the operator
recovery do not change.
- `packages/db`: Add the index `grok_dispatch_assignment_kind_idx` on
`grok_bot_dispatches (assignment_id, kind)` (migration `0346`). The
receipt lookup and the dispatcher's per-assignment checks use this
index.
- `server/src/services/__tests__/run-failure-report.test.ts`: Add one
test for the skipped case. Add nine tests for cases that must stay
reportable: no wake, a local rejection with no HTTP status, a delivered
wake, uncertain delivery, accepted work, a different provider failure
code, no finalization, more than one offer, and a receipt read that
fails.

## Verification

- `pnpm --dir server exec vitest run
src/services/__tests__/run-failure-report.test.ts`: 208 tests pass.
- I removed the new catch and ran the tests again. The
receipt-read-failure test failed.
- `pnpm --filter @paperclipai/db run check:migrations` and
`src/migration-snapshot-drift.test.ts` pass.
- I removed the source change and ran the new tests again. The
skipped-case test failed and the six reportable cases passed. This shows
that the test proves the change.
- I did not run the full `pnpm -r typecheck` locally. The server
`typecheck` script needs `cargo` to build the Runner binary, and the
sandbox stopped `tsc` with SIGKILL. CI runs the full typecheck.

## Risks

- Low risk. The change only removes one Sentry report. It does not
change run state, recovery, or what users see.
- The check uses durable receipts, not message text. A wake with no HTTP
status (local validation or credential failure) stays reportable, so a
Paperclip defect cannot hide behind this rule.
- For failed native runs with `native_provider_terminal_failed`, the
check adds up to three indexed reads. Other runs do no extra reads.
- Migration `0346` builds one index in a transaction.
`grok_bot_dispatches` was created in `0344` and is small, so the build
lock is short. The table is not on the migration gate's large-table
list.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic. Agentic tool use with
shell, code search, and test execution.

## Checklist

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

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

---------

Co-authored-by: devinfoley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:32:48 -07:00
DottaandPaperclip f16c0ad5c8 refactor(server): isolate chat conversation and control policies (#16276)
## Thinking Path

> - Paperclip helps people manage AI agents for work.
> - Chat channels turn external messages into shared task workflows.
> - The main service still contains some provider decisions.
> - These decisions belong with the existing channel modules.
> - This pull request separates conversation binding and Telegram
control policy.
> - Shared authorization, persistence, and runtime ownership keep their
current order.

## Linked Issues or Issue Description

Refs: #16235. Stack part 1/3. Base: `master`.

**What existing behavior does this improve?**

Provider ownership and readability in the chat channel service.

**Current behavior**

The entry point combines shared workflows with provider decisions.

**Proposed behavior**

Provider modules own their decisions. Shared modules own product state
and authorization.

**Reason and benefit**

Maintainers can find provider behavior in its channel folder.

**Breaking changes**

None intended. Public service methods and durable data shapes stay
compatible.

## What Changed

- Keep shared conversation lookup and completion in
`inbound/conversation-binding.ts`.
- Move Photon binding-completion policy to its messages module.
- Move Telegram command classification, forum behavior, and chronology
exemptions to its commands module.
- Keep the original conversation generation separate from the retained
task binding.
- Reduce `chat-channels.ts` from 3,649 to 3,598 lines.

## Verification

- The same 62 selected Telegram/Teams integration cases passed before
and after extraction.
- All 18 Photon channel integration cases passed before and after
extraction in separate test processes.
- The initial combined baseline command had a Photon database-startup
hook timeout. No Photon assertion ran in that invocation. The unchanged
Photon suite then passed in isolation with its existing timeouts.
- Server typecheck and the module-boundary check passed.
- A token comparison confirms the moved lookup, completion, and
generation logic, after normalizing the explicit policy callbacks.
- All current-head CI checks passed. Greptile reviewed this exact head
at 5/5 with no findings.
- Cumulative `pnpm -r typecheck` and `pnpm build` passed. All 1,321
channel integration cases passed. The normal UI, CLI, database, adapter,
plugin, and serialized API test selections ran.
- The first Linux `pnpm test:run` returned 1. Original-source runs
reproduced the local failures. Container-only repairs supplied missing
tools, process reaping, and sufficient Node heap. The existing
mocked-fetch CLI telemetry fixture passed with the image opt-out flag
overridden only for that fixture.
- One local venue limitation remains: adapter-utils T24 fails unchanged
on both original and final source in the Linux container. Its entire
56-case suite passes before and after on the host. All other failed or
aborted suites passed in repaired reruns. No test assertion or timeout
changed.
- These fixtures exercise the assembled service and persisted state.
They do not prove live third-party execution.

## Risks

- Changing query order or losing the original generation could bind a
message to the wrong task.
- The workflow retains the original lookup result and all existing query
and write conditions.
- No schema migration or API change is included.

## Model Used

OpenAI GPT-6, running in Codex with reasoning, tool use, and code
execution. The exact deployment 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-10-14 10:21:48 -05:00
DottaandPaperclip bd64eb27b0 perf(recovery): use indexed execution identity lookups (#16291)
## Thinking Path

> - Paperclip manages AI agents and their execution.
> - Recovery must keep old execution ownership visible before new work
starts.
> - Ownership reads filter large run contexts through a combined text
expression.
> - Recovery and status delivery also cast indexed UUID columns to text.
> - These reads can scan history before finding the requested execution.
> - This pull request preserves identity and ownership rules while using
existing indexes.
> - The benefit is less database work during recovery and task polling.

## Linked Issues or Issue Description

Refs #14406. Related community report: #15275. This is an independent
implementation. It does not use that pull request's code or commits. It
covers the reported execution joins and the additional ownership query
hotspot.

**What happened?**

Accumulated staging query statistics showed 11,782 ownership reads at 51
ms each. A paired recovery-action read averaged 22 ms. Both queried
large run JSON. Text casts also prevented indexed UUID lookups in
recovery and status delivery.

**What did you expect to happen?**

Recovery must find the same execution evidence through existing indexes.
Malformed identifiers must match nothing without a cast error. Native
issue bindings must retain precedence. Missing termination evidence must
continue to block new work.

**Steps to reproduce**

Run the disposable benchmark below. It loads the original service
modules from the baseline revision. It compares their exact ownership,
blocker action and status-delivery SQL with the current modules on 1,400
synthetic runs, 350 historical recovery actions and 192 KiB contexts.

## What Changed

- Convert saved text to UUID only after checking its canonical lowercase
form.
- Keep indexed run and task UUID columns unchanged in execution joins.
- Split native and legacy issue predicates so existing indexes can
filter ownership candidates.
- Preserve company scope, native precedence, process and lease holds,
recovery eligibility and delivery receipts.
- Add real PostgreSQL identity and ownership regressions, a reproducible
benchmark and database documentation.

## Verification

- Local service SQL benchmark: ownership 78.30 ms to 1.14 ms, with the
same two complete rows. Blocker action read 76.94 ms to 22.55 ms, with
the same complete row. These are synthetic SQL timings, excluding
process probes, provider calls and HTTP.
- Status delivery returned the same 100 rows. Timing was neutral on the
small-task fixture: 7.85 ms before and 8.30 ms after. No speedup is
claimed for that case.
- 231 local tests passed across six server suites. They cover malformed
identifiers, canonical spelling, company scope, native precedence,
retained processes, workspace restore, native replacement, conversations
and delivery.
- `pnpm -r typecheck` and `pnpm build` passed.
- `git diff --check` passed.
- All current-head CI and security checks passed, including the full
general and serialized suites, browser shards, Runner verification and
build. Greptile reviewed the exact commit at 5/5 with no actionable
findings.
- The older full local sweep in the companion worktree was stopped after
final-commit complete CI passed. It is not claimed as a completed local
pass. The 231 focused tests above passed locally on this branch.

```sh
pnpm --filter @paperclipai/server exec vitest run src/services/conversation-continuation.test.ts src/services/legacy-execution-recovery.test.ts src/__tests__/legacy-workspace-restore-recovery.test.ts src/__tests__/chat-completion-delivery.test.ts src/__tests__/agent-conversations.test.ts src/services/native-runtime/native-safe-replacement.test.ts
pnpm --filter @paperclipai/server exec tsx ../scripts/benchmark-recovery-queries.ts --baseline-ref=1e731520bb
```

## Risks

- PostgreSQL accepts more UUID spellings than the old text joins. The
explicit guard preserves the old lowercase spelling requirement.
- Query plans depend on the data distribution. Small task sets can show
neutral status-delivery timing. The benchmark is not a production
latency claim.
- Recovery ownership checks remain conservative. Process, lease,
workspace and no-replay rules are unchanged.
- No migration, new index, cache, cleanup change or lock removal is
included.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository inspection, shell
tools and local code execution. The runtime does not expose a more
specific backend model ID or context-window size.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 10:14:58 -05:00
DottaandPaperclip ebeac833a6 perf(runs): bound history and reduce repeated context reads (#16286)
## Thinking Path

> - Paperclip helps operators manage AI agents and inspect their work.
> - Run history supplies recent execution records to the board and API
clients.
> - The list repeats eight reads of a large stored JSON context.
> - A caller can also omit the limit and read the complete history.
> - This pull request expands context fields once and bounds every list
read.
> - The benefit is lower database cost and bounded redaction work.

## Linked Issues or Issue Description

Refs #958. Related community reports: #13962 and #10631. This is an
independent implementation. It does not use their commits or code. It
covers the history bounds reported in both. Process memory monitoring
from #10631 is outside this change.

**What happened?**

The run-history query repeatedly extracts fields from large context
JSON. Requests without a limit also load every historical run before
authorization and redaction. Accumulated staging query statistics showed
271 ms for summary history and another 58 ms for its redaction lookup.

**What did you expect to happen?**

Normal reads must use a bounded recent history. Context projection must
preserve the existing values and privacy behavior with less repeated
database work.

**Steps to reproduce**

Run the disposable benchmark below against the baseline revision. It
creates 1,400 synthetic runs with 192 KiB contexts. It compares the
exact service SQL and every returned column for both summary and full
history.

## What Changed

- Expand context fields with one lateral record projection. Preserve
non-object legacy contexts and the first-class issue binding.
- Enforce a 200-row default and a 1,000-row maximum in the route and
service.
- Apply the default in the shared UI client, including both agent detail
implementations.
- Preserve response arrays, authorization, result projection and secret
redaction.
- Fetch older linked runs by ID in both agent pages. Check company and
agent scope, navigation races and visible retry errors.
- Update OpenAPI, data-path documentation and capability maps.
- Add real PostgreSQL and HTTP regressions plus a reproducible
benchmark.

## Verification

- Local benchmark: summary history 37.72 ms to 19.34 ms; full history
37.84 ms to 19.01 ms. All 200 complete positional rows match in each
comparison. These are synthetic local SQL timings, excluding HTTP and
redaction.
- History, route, privacy and redaction suites: 160 server tests passed
across six files. Twenty-two UI API, selection, run-panel and
log-visibility tests passed. Both agent-page variants render an older
linked run with an empty history page on desktop and mobile.
- `pnpm -r typecheck` and `pnpm build` passed.
- Map validation and `git diff --check` passed.
- All current-head CI and security checks passed, including the full
general and serialized suites, browser shards, Runner verification and
build. Greptile reviewed the exact commit at 5/5 with no remaining
findings.
- The full local sweep began before the final compatibility fixes. It
was stopped after the final commits passed complete CI. The focused
local suites listed above passed on the final code. Before interruption,
the broader local sweep passed 31,743 tests across the general server,
UI and several package groups, with no reported failures. The full local
command is not claimed as a completed pass.

```sh
pnpm --filter @paperclipai/server exec vitest run src/services/heartbeat/run-state.test.ts src/__tests__/heartbeat-list.test.ts src/__tests__/agent-live-run-routes.test.ts src/__tests__/run-secret-redaction.test.ts src/__tests__/heartbeat-run-privacy-routes.test.ts
pnpm --filter @paperclipai/ui exec vitest run src/api/heartbeats.test.ts
pnpm --filter @paperclipai/server exec tsx ../scripts/benchmark-run-history.ts --baseline-ref=1e731520bb
```

## Risks

- Clients that relied on an unbounded array now receive 200 rows by
default. Explicit limits allow up to 1,000. This change does not add
pagination.
- Legacy context conversion must stay identical. Real database
regressions cover null, array, scalar, nested values and native issue
precedence.
- No schema migration or persistent cache is introduced. Redaction and
access checks remain active.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository inspection, shell
tools and local code execution. The runtime does not expose a more
specific backend model ID or context-window size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 10:14:41 -05:00
03fcd3c1fb fix(native-runtime): report which close barrier failed (#16298)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native agent runs use the Paperclip runner. When a run ends, the
controller closes the session. Close must prove that the runner durably
suspended before the controller releases its checkpoint.
> - If close cannot prove this, the run fails with
`provider_transport_failed: runner did not durably suspend before
checkpoint`.
> - The runner knows which barrier was not proven. It writes that
settlement only to the instance run log.
> - The Sentry report for the failed run has no settlement evidence.
Operators cannot tell a stuck suspend command from an undrained
provider, an unreadable provider state, or unsettled tool results.
> - This pull request sends that settlement to the run failure report as
closed labels and counts.
> - The benefit is that the next occurrence shows which barrier failed,
so the root cause can be fixed instead of guessed.

## Linked Issues or Issue Description

No public issue exists. This describes the bug.

**What happened?**

Hosted staging native Codex runs failed with `provider_transport_failed:
runner did not durably suspend before checkpoint`. This happened on two
runs, on 2026-09-30 and 2026-10-14. The second run used a release from
2026-10-14. The Sentry events contain only the error code, run phase,
driver kind, and duration. They contain no stack and no settlement data.
The native executor calls `reportRunFailure(db, run)` without the caught
error, and the runner settlement is never read.

**Expected behavior**

A failed close report should tell an operator which barrier was not
proven: runner suspension, provider drain, provider state, or semantic
tool settlement. It must not expose tool identities, digests, provider
turn IDs, or unredacted error text.

**Steps to reproduce**

1. Run a native Codex session whose close cannot prove durable runner
suspension. For example, the runner suspend command does not complete
before the close deadline.
2. Observe the Sentry run failure event. Before this change,
`run_execution` has no close settlement fields.

## What Changed

- `collectRunFailureDiagnostics` accepts a new `nativeCloseFailure`
option. It reads only a `native_session_close_unrecoverable` error,
directly or up to four `cause` levels deep.
- It adds closed labels and counts to `run_execution`:
`closeRunnerSuspended`, `closeProviderDrained`,
`closeSuspendCommandStatus`, `closeRunnerLifecycle`,
`closeRunnerIdentityMatches`, `closeProviderState`,
`closeProviderPendingEventCount`, `closeProviderTurnActive`,
`closeSemanticResultPersistenceFailed`, `closeSemanticToolsPending`,
`closeSemanticToolDeliveriesOpen`, and `closeSemanticResultFailures`.
- Values outside the known vocabularies are dropped. Getters that throw
are contained.
- The native session executor passes the caught error as
`nativeCloseFailure` when it reports a terminal run failure. It does not
pass the error as `error`, because the raw error text and stack bypass
the executor's identity redaction.
- `doc/observability.md` documents the new fields and the privacy
boundary.

## Verification

- `cd server && npx vitest run
src/services/__tests__/run-failure-diagnostics.test.ts
src/__tests__/run-failure-sentry.test.ts`: 130 tests pass.
- New tests check the full label set, a wrapped cause, null suspension
and provider states, unrelated error codes, hostile and unknown values,
and a missing settlement. They also check that no tool identity, digest,
turn ID, or error text leaves the module.
- Negative control: with the reader disabled, the new main test fails.
- A scoped `tsc` run on `run-failure-diagnostics.ts`, its test, and
`run-failure-report.ts` passes. The full server `tsc` and `pnpm build`
were not run locally. The sandbox has a 4 GiB memory limit and no
`cargo` for the runner binary. CI runs the full checks.

## Risks

- Low risk. The change only adds Sentry context fields for one error
code. Close, quarantine, retry, and recovery behavior do not change.
- Sentry grouping does not change. The fingerprint stays `[errorCode,
agentAdapter]`, and no exception chain is added.
- The data path is Observability (Sentry error reporting, gated by the
operator DSN). It does not change Telemetry. The fields contain only
booleans, fixed labels, and counts. The DSN gate is unchanged.
- These fields are observations. They do not prove a root cause by
themselves.

> 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`), run through Claude Code
as an agent with tool use (shell, file edits, test execution, Sentry and
GitHub connectors). Context window and reasoning mode 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

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

Co-authored-by: Devin Foley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:57:21 -07:00
DottaandPaperclip df4ee06e2f feat(skills): select and validate tracked GitHub branches (#16288)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Skills can track a branch in an authorized GitHub repository.
> - Sources previously offered only a default-branch repair action.
> - People need to select a listed branch or enter a branch that GitHub
has not listed.
> - The server must validate the selected ref with the caller's
authorized connection.
> - This pull request connects the approved branch picker to production
APIs.
> - Installed skills remain usable while the source changes its tracked
ref.

## Linked Issues or Issue Description

**Subsystem affected**

Skills UI, shared API contracts, GitHub source services, and agent API
discovery.

**Problem or motivation**

The default-branch repair action does not let people choose another
branch. The settings service also validates the old ref when a caller
supplies a new ref other than HEAD. A missing tracked branch can prevent
connection repair even when the selected account has repository access.

**Proposed solution**

Load a bounded branch list with the authorized connection. Mark the
default branch. Search listed branches or accept a typed ref. Validate
the selected ref before saving. Keep the chosen value after a load
error, invalid ref, or concurrent update. Save the selected connection
and branch together during connection repair.

**Alternatives considered**

A default-branch button does not support a named branch. A text field
alone does not help people find the repository default or available
branches.

**Roadmap alignment**

This improves the existing Skills source manager. It does not add a new
skill import system.

**Additional context**

Refs #16066 and #16256. This is the production implementation of the
approved branch-selection Storybook journeys.

## What Changed

- Add an authorized branch-list endpoint and shared response/query
types. Register it in agent API discovery.
- Validate the requested ref, including the returned commit SHA.
Preserve source leases, revision checks, project ownership, skill
identities, selections, and installed versions.
- Add the searchable branch picker to Sources and connection repair.
Allow typed refs during branch discovery and after discovery fails.
- Keep save errors in the dialog. Retain the selected branch through a
concurrent source update.
- Use the production components and API client in Storybook. Add
keyboard, touch, loading, retry, invalid-ref, connection-repair, and
concurrent-update browser journeys.
- Update product documentation and the feature/access maps.

## Verification

- Red/green: two new database regressions fail against the previous
service and pass against the implementation.
- Linux database/service suite: 85 tests passed. This includes source
visibility, human/agent authorization, selected connections, immutable
versions, and invalid refs.
- Rebased branch and review fixes: 94 focused UI, route,
branch-validation, and fixture tests passed. Two conflict-message
regressions and the stale-retry fixture regression fail before the fix
and pass after it.
- Storybook build passed. All 26 focused browser journeys passed.
- Repository typecheck and build passed. Final UI typecheck passed after
the review fixes.
- The full local Vitest run was interrupted when the checkout and its
dependencies disappeared. It reported missing working-directory/Postgres
files and worker startup errors. Its two timed-out tests pass after the
checkout was restored. The complete CI suite passed on the final head,
`cf5d4c8f0a`: [CI
run](https://github.com/paperclipai/paperclip/actions/runs/38571068332).
One unchanged workspace timestamp test required a single rerun. Its
release-minus-one-millisecond value can equal the original confirmation
time. The rerun passed.
- Live acceptance passed on implementation head `5dd935bf14` in an
isolated macOS arm64 instance with Node 24.21.0 and an authorized
private GitHub repository. The UI loaded the actual default branch,
saved branch and selected connection together, and preserved the
installed skill/version IDs after reload. An invalid typed ref retained
the draft and showed the provider error. A valid typed HEAD ref then
saved successfully. The owned runtime and database were stopped after
the test. Focused unit, Linux database, and browser suites passed again
after the error-handling review fixes.
- Greptile reviewed final head `cf5d4c8f0a` at 5/5. Both review threads
are resolved; the fresh review has no remaining findings.
- Token gates and map/catalog checks passed.
- Review entry points: `Skills / Project repository skills / 03 Branch
selection components` and `04 Branch selection journeys`. Run `pnpm
storybook` to inspect them.
- Browser command: `pnpm exec playwright test --config
tests/storybook-visual/playwright.config.ts
tests/storybook-visual/skill-source-branch-selection.spec.ts
tests/storybook-visual/project-repository-skills.spec.ts`.
- Manual check: open an independent GitHub source, choose a new branch,
select the default or type a ref, save, and reload. Try an invalid ref
and confirm the selection remains in the dialog. For a project-bound
source, change its ref in project settings.

## Risks

- GitHub discovery is bounded to 100 listed branches. The default branch
is always included. Typed refs remain available.
- A source can change while the dialog is open. The API rejects a stale
revision and the UI keeps the draft for retry.
- A ref change schedules the existing refresh pipeline. Installed
versions remain available until a successful refresh imports new
content.
- No database migration or credential storage change is required.
Existing HEAD requests remain supported.

## Model Used

OpenAI Codex, GPT-6 family. The serving model's exact identifier and
context-window size are not exposed in this session. Used reasoning,
repository tools, code execution, and browser tools.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 09:55:43 -05:00
DottaandPaperclip d4892d4761 fix(observability): sample native runs independently of triggers (#16285)
Start native background run traces under the operator's root sampling policy and link them to valid trigger contexts. Preserve child parenting and silent unconfigured tracing.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 09:49:08 -05:00
DottaandPaperclip f69c6a8a35 fix(github): retry and recover review publication (#16284)
## Thinking Path

> - Paperclip manages agent work and its published results.
> - GitHub review bots publish task-bound assessments through governed
tools.
> - A review can survive a caller timeout because its publication intent
is durable.
> - A transient provider failure still returned a failed receipt before
recovery.
> - Gateway replay could return an empty cached result, and publication
retries had no dedicated scheduler deadline.
> - This pull request adds bounded retries, receipt recovery, and an
independent durable worker.
> - A temporary GitHub failure can recover without another review or
duplicate publication.

## Linked Issues or Issue Description

Refs #16275. That change increased the GitHub tool deadline. This change
repairs retry and recovery behavior.

**What happened?**

A one-off GitHub HTTP 503 or 429 left the caller with a failed
publication receipt. The saved retry did not have its own scheduler
worker. An identical gateway call after a timeout could return an empty
cached result instead of the saved publication receipt.

**Expected behavior**

Temporary failures should retry under current authority. Pending work
should resume at its saved deadline after restart. Exact retries should
return the current receipt. Lost responses must not create duplicate
comments, findings, or checks.

**Steps to reproduce**

1. Admit an advisory PR review request and begin its assessment.
2. Return HTTP 503 or 429 for the first summary-comment PATCH, then let
GitHub succeed.
3. Submit the assessment through the run-scoped tool gateway.
4. On master, the result says publication failed. With this change, the
same call returns the processed receipt and neutral check.
5. For receipt recovery, use a shorter caller deadline and repeat the
exact call after publication finishes.

**Paperclip version or commit**

Reproduced on master at `6cb6ac3b8e40cb90d734ba115a2f22f850ff9477`.

## What Changed

- Classify transient transport, server, and rate-limit failures. Honor
Retry-After and primary reset deadlines. Stop retries for permanent
permission and validation errors.
- Retry safe reads and installation-token issuance within a bounded call
window. Retry publication writes through the existing outbox and
provider receipt checks.
- Wait for up to three publication attempts within a 45-second retry
window. Keep the durable eight-failure cap and enforce saved deadlines
on explicit retries.
- Register an independent GitHub publication worker with
transaction-owned wake notifications, startup recovery, and per-PR lease
deadlines.
- Recover exact comment, progress, and assessment gateway calls from
their stored publication. Require the same company, agent, task, run,
connection, tool, arguments, and current policy. Clear stale timeout
errors after recovery. Approved exact retries read the current receipt
through the existing signed replay path; the worker retains the verified
approval for deferred writes and still rejects revocation.
- Add regression coverage and update the connector guide and
feature/access maps.

## Verification

- Red: both one-off HTTP 503 and 429 regressions failed on master with a
failed publication receipt.
- Green: the same tests passed after the change.
- GitHub review workflows: 133 covered against isolated PostgreSQL 18.
The final combined run passed 132 and hit one fifteen-second timeout
under concurrent build load. All three variants of that workflow passed
in isolation. The earlier combined run passed all 131 workflows before
the two approval regressions were added.
- Retry policy and delivery coordinator tests: 35 passed.
- Additional focused checks passed for the actual eight-failure cap,
exact-argument conflicts, policy revocation, and timeout receipt
recovery. The approved deferred-publication and revocation regressions
failed before the review repair and passed after it, with one approval
request preserved.
- `pnpm -r typecheck`: passed. `pnpm build`: passed. `node
scripts/check-maps.mjs`: passed.
- The broad local `pnpm test:run` path remains blocked by the host's
exhausted PostgreSQL shared-memory slots. The changed database-backed
workflows use an isolated Docker database instead. All Paperclip CI jobs
passed at a6d81b67cb, including the required verify and E2E gates.
Greptile is 5/5 at the same head, with the approval-recovery finding
resolved. The external Superagent Security Scan also passed.
- Live acceptance passed on a dedicated single-repository local test App
and native Paperclip Runner. A test-only preload returned one HTTP 503
before the final summary PATCH. One submit_review call completed in
17.305 seconds; the bot published its 3/5 advisory review and neutral
check, then automatically finished the task. [Published
review](https://github.com/paperclipai/paperclip-permissions-smoke-20260926-pap57-fee7428e/pull/9#issuecomment-6168809007),
[neutral
check](https://github.com/paperclipai/paperclip-permissions-smoke-20260926-pap57-fee7428e/runs/115863564014).
This live test ran at de6862072c; approval-specific recovery is covered
by the later regressions. The preload is excluded from the repository
and removed from the running local instance.

## Risks

Retries make additional bounded provider requests. A provider write with
a lost response is recovered through its existing App-owned identity
before another write is permitted. The worker rechecks authority and the
PR head; revocation and superseded work stop publication. Calls can
still exceed their deadline, and repeated failures eventually exhaust
the retry cap. A pending publication is not a completed task. The agent
still owns task completion after receiving a confirmed receipt. No
schema migration is needed.

## Model Used

OpenAI Codex (GPT-6 family), with code inspection, editing, and test
execution. The exact deployment model ID and context window are not
exposed in this session.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 08:40:32 -05:00
DottaandPaperclip 7c016c049b perf(workspaces): wake terminal cleanup from committed changes (#16282)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents use execution workspaces to retain their files and Git
branches.
> - A reaper closes eligible workspaces after their task tree is
terminal.
> - The reaper scanned every heartbeat tick and read Git before cheap
eligibility checks.
> - Permanent scans also query idle databases when no cleanup work
exists.
> - This pull request wakes cleanup after transaction settlement and
restores bounded work at startup.
> - The benefit is fewer scans while fresh cleanup guards continue to
protect retained work.

## Linked Issues or Issue Description

Related work: Refs #13619, Refs #11753. Those PRs bound scans or back
off unchanged candidates. This PR uses the transaction work coordinator
to remove the permanent reaper tick. It overlaps their early
terminal-state filtering but does not change their runtime restart
behavior or introduce a second Git scheduler cache.

**What existing behavior does this improve?**

Terminal workspace cleanup and Git inspection for workspace detail
reads.

**Subsystem affected**

Server orchestration, execution workspaces, and idle sleep admission.

**Current behavior**

The heartbeat tick scans workspace candidates every thirty seconds. It
runs Git before rejecting nonterminal trees, cooldowns, and active runs.
Display inspections expire after five seconds.

**Proposed behavior**

Recover workspace rows in bounded startup pages. Wake after relevant
issue, workspace, and pull-request work-product transactions settle.
Schedule the terminal tree cooldown or a five-minute eligibility retry.
Back off unchanged candidates across unrelated wakes, and invalidate
their backoff after relevant committed changes. Park empty and
nonterminal-only lanes. Reuse display inspections for thirty seconds.
Read fresh Git before destructive cleanup.

**Reason and benefit**

Avoid recurring database and Git work when no cleanup is possible.
Retain transaction admission holds, restart recovery, and fresh deletion
checks. Retention eligibility can wait for the next host wake; active
and failed recovery work still blocks sleep.

**Breaking changes**

No schema or API shape changes. Display Git state can lag by thirty
seconds. Cleanup eligibility retries can take five minutes.

## What Changed

- Add a terminal-workspace worker to the existing transaction work
coordinator and scheduler.
- Signal relevant writes before mutation on the owning transaction.
Outer transaction settlement wakes cleanup.
- Derive cooldown deadlines from the full company-scoped terminal task
tree. Continue bounded recovery pages without a permanent sweep.
- Bound unchanged candidate inspections to five minutes, retain at most
256 entries, and invalidate affected entries with scoped transaction
hints. Repeated wakes preserve the earliest inspection deadline.
- Collect deadlines from task trees already read by bounded pages.
Restore the cursor after a failed page and rescan commits received
during a rotation.
- Check terminal state, cooldown, and active runs before Git. Preserve
lifecycle locks, generation fences, and fresh final checks.
- Keep failed or incomplete recovery visible to idle admission. Preserve
the passive retention exception.
- Extend the bounded display cache to thirty seconds and document the
scheduling and wake limits.

## Verification

- Focused workspace, coordinator, cache, and local sleep suites: 111
tests passed with real local PostgreSQL and Git worktrees where
applicable.
- Final worker, display cache, and local sleep suites: 28 tests passed.
- The regressions prove three ineligible workspaces invoke zero Git
scans, cached clean display state cannot remove new untracked files, and
real producer notifications wait for outer transaction settlement.
- `pnpm -r typecheck` and `pnpm build` passed locally. Map and
module-boundary checks passed.
- Work-product and sleep-safety suites: 221 tests passed. Reopen and
issue-tree control suites: 46 tests passed. Final backoff, workspace,
worker, and work-product suites: 87 tests passed.
- Final workspace and worker suites: 81 tests passed. Deadline selection
performs zero database reads. A failed full page retries its unread
terminal workspace before recovery can park.
- Full CI coverage passed on head `2c397970f7`: general tests,
serialized server suites, runner verification, E2E shards, typecheck,
build, and the final verification gate. The duplicate serial local `pnpm
test:run` was stopped after full CI coverage passed; it is not reported
as a completed local pass.
- Greptile: 5/5 with both findings resolved. The security scan also
passed. No failing or pending PR checks remain.

## Risks

- Display state can be stale for thirty seconds. Destructive guards
bypass the display cache.
- Undelivered or otherwise ineligible terminal work retries after five
minutes instead of each heartbeat tick.
- Transaction signals cover the owning `createDb` scope. Direct SQL and
independent server processes require startup recovery.
- Local cooldown timers are maintenance hints. They do not establish an
external wake alarm or whole-instance sleep safety. Existing blockers
remain in force; failed bounded recovery keeps the host awake.

## Model Used

OpenAI GPT-6, model ID `gpt-6`, through Codex. Reasoning, repository
inspection, shell execution, and browser tools assisted the change. The
runtime does not expose the context-window size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 08:28:45 -05:00