391 Commits
Author SHA1 Message Date
DottaandPaperclip c740cb9ded fix(ui): dismiss skipped confirmations when sending a message (#19496)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Tasks and Agent Chat show saved requests above the message composer.
> - A person can send a separate message while a confirmation is open.
> - The old code closed only question forms, after the message save
finished.
> - A confirmation could stay open or open again after a refresh.
> - This change closes the form when sending starts and keeps the
request in the feed.
> - People can open the request later and make the original decision.

## Linked Issues or Issue Description

**What happened?**

A confirmation stayed open when a person sent a separate message.

**Expected behavior**

The form closes immediately. The request stays in the feed. A person can
open it later. Sending a message does not record an approval.

**Steps to reproduce**

1. Open a task with a pending confirmation.
2. Send a separate message below the form.
3. Observe the form while the message save is pending.
4. Reload the task and open the request from the feed.

**Paperclip version or commit**

Reproduced at a434edf991.

**Deployment mode**

Source checkout and isolated Storybook fixtures.

## What Changed

- Close the composer request before the message save finishes.
- Keep earlier saved requests in history after a newer human message.
- Save browser dismissal IDs in one batch after a successful send.
- Restore the previous form after a failed send, including a reopened
request after an optimistic comment changes automatic selection.
- Track deliberate selection changes separately from automatic
filtering. Keep a newer request open when an earlier send fails.
- Allow a new request to open while an earlier message is being saved.
- Add tests for confirmation and checkbox forms in task and Agent Chat
modes.
- Add Send Instead Of Confirming and Reopen Skipped Confirmation
stories.
- Update the existing answer-later story to check the current answer
receipt.
- Update the behavior and Storybook documentation.

## Verification

- TDD: Six new cases failed before the main fix. Four more cases
reproduced the review finding before the recovery fix. They cover failed
sends with an optimistic comment and its rollback in both task and Agent
Chat modes.
- Component checks: 518 passed, one existing test skipped. Run `pnpm
--filter @paperclipai/ui exec vitest run
src/components/TaskChatThread.test.tsx
src/components/task-chat/TaskChatComposer.test.tsx`.
- UI typecheck passed after preparing the local plugin SDK dependency.
- Token gates passed.
- Catalog structure checks passed. The three catalog test files passed
all 30 tests.
- Six Playwright cases passed in an owned Storybook server. They cover
the initial form, sending, manual reopening, light theme, mobile, and
answering later. Play assertions passed. Accessibility reports had zero
violations.
- The stories use the production thread with simulated saves and
decisions. Existing optional fixture endpoints return 404. These do not
prevent the selected journeys.
- No visual baseline comparison was possible. The baseline manifest has
no published snapshots.
- `pnpm verify --explain` requested broader CI coverage. [Required
CI](https://github.com/paperclipai/paperclip/actions/runs/42253585800)
passed on fda44f2648.
- Greptile and Paperclip Review both report 5/5 with no findings on the
final head.
- The remote commit tree matches the tested local tree:
7e22201513.

## Risks

- Older pending requests now stay in history after a newer human
message. People must select the feed entry to open them.
- Dismissal changes presentation. Server approval checks and request
decisions keep their existing behavior.
- Browser fixtures do not prove live provider continuation or staging
behavior.

## Model Used

OpenAI GPT-6 Codex. The exact backend model identifier and context
window were not exposed to this run. Used reasoning, repository
inspection, edits, shell tools, component tests, 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 <[email protected]>
2026-11-07 08:34:14 -06:00
Devin FoleyandPaperclip 51ab7ebdc6 fix(recovery): admit recovered scheduled preparation retries (#19802)
Allow explicit Retry after a recovered scheduled setup failure.

Recognize the exact host scheduled-retry promotion only when its saved
parent, attempt, deadline and reason match the source run and context.
Keep changed, extra or foreign evidence held. Preserve the existing
provider-absence, stopped-source, recovered-file and authorization gates.

Keep refusal tests receipt-only until they change saved proof, then
invoke the real retry handler. Positive tests retain real HTTP delivery.

Verification: 217 unique tests passed across five recovery suites. The
12-test affected suite and its typecheck layer passed again after the
test-race fix. All applicable CI passed; exact-head Apex review was 5/5.

Co-Authored-By: Paperclip <[email protected]>
2026-11-07 02:20:02 -08:00
131b566ca4 fix(workspaces): report safe Git restore diagnostics (#19736)
Return the restore stage and failure category with the existing HTTP409 rejection. Preserve the unsaved comment and editable draft. Keep nested saved-commit and clone-timeout diagnostics through preparation wrappers, and distinguish local Git file errors from authentication failures. Retain only safe process flags privately; no raw command cause is exposed.

All seven affected local suites pass (288 tests) and the layered server typecheck passes on supported Node25.6.1. Reviewed head 022eb7c2eb has Greptile Apex5/5, zero unresolved findings and all applicable CI passing (55 successful checks and4 intentional skips).

Co-Authored-By: Sentry Bot <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Paperclip <[email protected]>
2026-11-06 21:33:59 -08:00
18be7ac696 fix(server): retire idle native sessions before tenant sleep (#18988)
## Thinking Path

> - Paperclip lets people manage AI agents and their work.
> - Native agents can retain a warm session between useful turns.
> - That owner must close and save its files before the tenant can
sleep.
> - A failed close must keep the owner visible until retirement
succeeds.
> - This PR prepares idle resources under the exact current admission
hold, then takes a fresh safety snapshot.
> - Busy turns, uncertain closure and changed holds still prevent sleep.
> - CI also exposed a chat authorization race. The fix repeats full
authorization after the exact delivery commits.

## Linked Issues or Issue Description

Refs #18639 for local work and incoming wake boundaries. Related #18829
covers suspension receipts after admission.

**What happened?**

Idle preflight did not retire warm Native owners. Three failed-close
paths could forget an owner without proving that its provider stopped.
CI also found a false chat rejection when a delivery changed from
processing to processed between two validation reads.

**Expected behavior**

Retire only idle owners under the exact bounded hold. Wait for closure
and file collection. Retain failed owners. Scan safety after
preparation. Keep diagnostic reads passive. A newly committed chat
delivery must pass the full current authorization checks before
attestation succeeds.

**Steps to reproduce**

Finish a warm Native turn. Start an idle drain and request safety with
its owner ID. Delay or reject provider closure, or replace the hold
during preparation or scanning. The report must keep the instance awake.
For the chat race, commit the exact delivery between the first binding
read and its fallback read.

**Paperclip version or commit**

Current head `7f6f02b611dcf5a9a2b05173a44e9631eaf78058` normally
integrates actual master `1aa0c3639714f4976cfa56f95aae6e2b7d1235ac`. The
merge preserves every unrelated master path. Fresh exact-head CI and
complete review remain required.

## What Changed

- Retain uncertain Native owners after failed restart, idle timeout and
failed-turn closure.
- Prepare Native retirement through the existing heartbeat facade. Reuse
provider closure and stopped-file collection.
- Track preparation under the exact idle owner and expiry. Reject new
activity, changed holds and expired holds before and after awaited work.
- Take the final durable and local safety snapshot after preparation.
Keep diagnostic inspection passive.
- Cover ephemeral workspace retirement and useful next use, supported
plugin drain and SDK persistence, busy owners, failed closure and hold
replacement.
- Keep dedicated recovery storage explicit. Use the same validated
options for support and storage. Probe-free helpers retain the original
implementation bodies and preserve default callers.
- Complete the Native fixture through real retirement and budget
evaluation. Keep its rejection, file, credential, accounting and strict
NONE assertions.
- Repeat the complete chat authorization once if the fallback sees the
exact batch now processed. Preserve owner locks, principal and binding
checks, denial handling and the original retry deadline.

## Verification

- The support-mode regression reproduces one failure with four passes
under the old policy, then passes all five controls. It retains prior
allocation and cleanup cases. The explicit mode does not probe embedded
PostgreSQL or use an admin alias.
- The deterministic chat race baseline reproduces two authorized
failures with all four current-authority denial controls passing. Both
Slack and Discord use real PostgreSQL and the real authorization
function. The negative setup commits before the attestation transaction;
it does not claim to revoke access through a row lock.
- The product fix passes normal own App declaration emission with no
public declaration changes, plus both current normal owning compiler
programs (399 and 611 roots). The same six controls pass after the fix.
The full original file plus these controls passes all 30 tests with no
skips.
- The original controlled Native continuation passes: one test, no
failures or skips, 1.718 seconds in its body and 23.138 seconds in its
launcher. The original 40-second case and 180-second outer bounds are
unchanged. It verifies held closure, strict NONE, an ordinary authorized
comment, saved session/files, useful output, encrypted credentials,
accepted results and settled accounting. All ten observed processes and
groups close; worker connections and listeners reach zero. A separate
post-owner audit finds zero sessions; the successful owned database is
normally removed.
- Earlier preparation, HTTP, lifecycle, workspace and plugin controls
remain source-specific evidence. They are not a claim that every
final-tree test ran locally.
- The original CI assertion log is retained. The controlled race proves
a possible cause; the original CI SQL schedule was not captured. Two
other jobs lost their self-hosted runners and provided no test verdict.
Earlier asset timeouts, readiness failures and unexplained later
recovery remain separately recorded. No timeout, failure predicate or
authority check was weakened.

Focused public commands:

```sh
pnpm exec vitest run server/src/__tests__/idle-sleep-preparation.test.ts server/src/__tests__/instance-settings-routes.test.ts --maxWorkers=1 --no-file-parallelism
pnpm exec vitest run server/src/__tests__/heartbeat-reviewed-chat-binding.integration.test.ts --maxWorkers=1 --no-file-parallelism
```

Local database qualification uses fresh owned test databases on existing
PostgreSQL, bounded pools and one worker. The local allocation adapter
changes only database allocation for this test module; it runs normal
migrations and client cleanup. It is not part of the published source.
No new cluster is created.

Local acceptance measured the `0a69efa6` integration with the same
committed authorization bytes. The later normal master merge adds
startup-timeout classification and recovery changes. Those paths are
outside the controlled healthy-backend witness; retirement and
authorization hunks are preserved. Exact-head remote CI at
`7f6f02b611dcf5a9a2b05173a44e9631eaf78058` has completed with 56
successful contexts and four skips. The complete current Greptile
summary is5/5 with no new actionable findings; all review threads are
resolved. Ponytail’s current turn is explicitly paused without a
current-head verdict; earlier actual findings are fixed and accounted
for. No whole final-tree local repository build/test run, live-provider
restore or deployed suspension is claimed.

Actual master subsequently advanced to
`d5c4f7dd02d408a261cedd9a1926b9900a4aedc4`. Its local normal-merge
projection with this reviewed head preserves all20 owned paths and every
unrelated master blob and mode. Current Runner TypeScript, App
declarations and their full owning compiler programs pass. Focused
Native cleanup and warm-continuation composition checks are being
completed before merge; the new upstream suspension-retry behavior is
not inferred from the older controlled-backend witness. The remote PR
head remains unchanged.

## Risks

- Privileged idle preparation can close idle warm Native sessions. It
requires the exact current hold.
- Failed closure retains its owner and can keep the tenant awake until
normal retry succeeds.
- A changed hold cannot authorize sleep. Started cleanup remains tracked
until it settles.
- The chat repair can perform one extra full authorization read. It
keeps the existing deadline and does not grant authority from the
fallback query.
- Unsupported resources and active work retain their existing blockers.
This PR does not certify every transport, plugin or deployed host.

## Model Used

OpenAI `gpt-6.1-sol` produced the delegated changes and verification.
OpenAI Codex, based on GPT-6, performed managing review and integration.
Both used repository tools and code execution. The coordinator's exact
backend snapshot and context-window limits were not exposed.

## Checklist

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

---------

Co-authored-by: Paperclip <[email protected]>
Co-authored-by: Codex <[email protected]>
2026-11-06 20:49:30 -06:00
Nicky LeachandClaude Opus 5.5 0b73c805e8 feat(environments): fill an unset instance default with a managed plugin environment (#19551)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A managed instance gets its instance-level environments from the
`PAPERCLIP_MANAGED_CONFIG` document, which can declare plugin
environments next to the managed sandbox
> - A platform that runs managed instances wants a new instance to start
with one of those plugin environments as its default, without changing
the default of any instance that already has one
> - #19632 made boot fill the instance default only when it is unset,
local or deleted, in one locked transaction, and removed the revert, so
every existing managed instance keeps a set default
> - This pull request adds an `instanceDefault` flag to a plugin entry.
That entry fills an unset default through the same locked fill, before
the managed sandbox does
> - The fill follows the same readiness rule as an admin's default
write, so it never moves ready agents onto a plugin that has not
prepared them
> - The platform also needs a way to know whether a release understands
plugin entries, so the release now ships a small capabilities file
> - The benefit: new instances get the plugin environment as their
default, and existing instances and admin choices are never changed

## Linked Issues or Issue Description

Refs #19632 (the locked fill-if-unset rule this builds on), #19508
(plugin entries in the managed config)

**Subsystem affected**
Server environments: the managed config parser and the managed
environment bootstrap.

**Problem or motivation**
The managed config can declare plugin environments, but it can't make
one the instance default on a new instance. An external platform also
has no way to tell whether a given release supports plugin entries
before it sends one.

**Proposed solution**
- Add `instanceDefault: true` to at most one plugin entry. Right after
that entry's boot ensure succeeds, it fills the instance default through
`fillUnsetInstanceDefault`. The managed sandbox fill runs after it.
- On a new instance the plugin environment wins. On an existing instance
the default is already set, so neither fill changes it.
- The fill declines while an active agent that follows the default
hasn't been prepared by that plugin. This is the rule the
instance-settings route enforces. When the plugin fill declines, the
managed sandbox fills as usual.
- Ship `server/src/services/managed-config-capabilities.json`, which
lists `pluginEnvironments`.

**Alternatives considered**
- A recorded one-time decision with a retry worker. It needed pending
states and extra checks for many writer and trigger combinations.
Filling only an unset default, under the existing lock, needs none of
that.
- Rejecting the fill whenever agents exist. That is stricter than the
admin route and blocks harmless cases, such as agents that are paused or
already prepared.

**Roadmap alignment**
This extends existing managed-environment behaviour. It adds no UI.

## What Changed

- `server/src/services/managed-config.ts`:
- `ManagedPluginEnvironmentSpec.instanceDefault` (boolean, default
`false`).
- The parser refuses `instanceDefault` on more than one plugin entry, on
the sandbox entry (even `false`), or as a non-boolean.
- `MANAGED_CONFIG_CAPABILITIES` and
`MANAGED_CONFIG_CAPABILITIES_SCHEMA_VERSION` constants. A plugin entry
is refused unless `pluginEnvironments` is listed.
- `server/src/services/managed-config-capabilities.json`: `{
"schemaVersion": 1, "capabilities": ["pluginEnvironments"] }`. A test
pins it to the constants and to this literal contract.
- `server/src/services/managed-environments.ts`:
- New `fillPluginInstanceDefault`. It runs right after a successful boot
ensure of the flagged entry and logs a fill or a decline. Recovery after
boot never fills.
  - The managed sandbox fill now runs once, after the plugin entries.
- `server/src/services/managed-instance-default.ts`:
`fillUnsetInstanceDefault` runs `agentsNeedSetupForEnvironmentChange`
inside its locked transaction and returns `declined:
"agents_need_setup"`. A sandbox target never needs agent setup, so the
managed sandbox fill behaves as before.
- `server/src/services/plugin-environment-driver.ts`: the readiness
query is factored into `agentsNeedSetupForEnvironmentChange`.
`assertAgentsReadyForEnvironmentChange` wraps it and still throws the
same conflict.
- `doc/DEVELOPING.md`: documents `instanceDefault`, the fill order, the
readiness decline, and the capabilities file.

## Verification

- `cd server && npx vitest run
src/__tests__/managed-plugin-environments.test.ts` (embedded Postgres),
under `instanceDefault (fill only an unset default)`:
  - A new instance gets the plugin row as its default.
  - An existing sandbox default is kept, and so is an admin's choice.
- If the plugin was down at the first boot and the sandbox filled, a
later recovery and boot keep the sandbox default.
- An admin-cleared or local default is declined while a ready or
verifying agent follows the default without the plugin's setup. The
managed sandbox then fills it.
- It fills when the agents that follow the default are paused or already
prepared, or are pinned to another environment.
  - Without the flag, nothing happens.
- A new agent that follows the default gets the plugin in
`requiredPluginIds`.
- `npx vitest run src/__tests__/managed-instance-default.test.ts`
(embedded Postgres): the earlier fill, lock and overlap cases pass. A
sandbox fill also succeeds while active agents follow the default.
- `npx vitest run
src/services/managed-environments-plugin-entries.test.ts`:
  - The fill order is plugin then sandbox.
  - When the plugin is down there is no fill, and recovery never fills.
- A failed ensure gives only the sandbox fill, and a failed fill keeps
the ensure counted.
  - When the plugin fill declines, the sandbox still fills.
- `npx vitest run src/__tests__/managed-config.test.ts
src/services/managed-config-capabilities.test.ts`: parser refusals and
the capabilities contract.
- `npx vitest run src/services/managed-environments.test.ts
src/__tests__/instance-settings-routes.test.ts
src/__tests__/environment-routes.test.ts`: pass. The route still throws
the same readiness conflict.
- `pnpm --filter @paperclipai/server typecheck` passes.
- I did not run the full `pnpm test:run`, `pnpm -r typecheck` or `pnpm
build`. CI covers them.

## Risks

- An instance whose default is unset at the first boot after this
release gets the plugin environment, if its agents are ready for it.
That covers a new instance, and an instance whose admin cleared the
default or chose the local environment. This is the intended behaviour.
- The readiness check doesn't lock agent rows. If an agent becomes ready
during the boot fill, it can miss the check, which is the same race the
admin route has. Agents created after the fill are prepared for the
plugin, because setup reads the current default.
- If the plugin is down at a new instance's first boot, the instance
keeps the managed sandbox default from then on.
- No schema change.

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

## Model Used

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

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 16:48:13 -08:00
Nicky LeachandClaude Opus 5.5 fb3cf1a072 feat(ci): retry a failed Vitest case once in PR CI and report each retried pass (#19637)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Contributors wait for PR CI before merge, so CI wall-clock time sets
the pace of work
> - PR CI runs Vitest shards with 4 workers on 4-vCPU runners, so
timing-sensitive tests sometimes time out under load
> - One such timeout fails the whole run, and a re-run costs another 15
minutes or more
> - In one 7-hour window on 2026-11-06, about 30 failed jobs were test
flakes on commits that passed on a later attempt
> - This pull request retries a failed Vitest case once in PR CI only,
and reports every pass that needed the retry
> - The benefit is fewer whole-run re-runs, while each flaky test stays
visible so that we can fix it

## Linked Issues or Issue Description

**What existing behavior does this improve?**

PR CI fails the whole run when one Vitest case fails once because of
runner load.

**Subsystem affected**

CI: the PR Vitest lanes (general, serialized, workspace and Runner
package lanes).

**Current behavior**

Vitest uses `retry: 0` everywhere. One timeout under load fails the run.
A person must notice it and re-run the failed jobs. About 20% of
recently merged PRs needed at least one re-run.

**Proposed behavior**

PR CI retries a failed Vitest case one time. A test that passes only on
the retry gets a GitHub warning annotation and a row in the job summary.
Local runs, release, cloud readiness and master keep `retry: 0`.

**Reason and benefit**

A re-run of one test case costs seconds. A re-run of the CI run costs 15
minutes or more. The report keeps flaky tests visible. Separate pull
requests fix the known flaky tests.

**Breaking changes**

None.

## What Changed

- Add `scripts/ci/vitest-retry.mjs`. It sets 1 retry when
`GITHUB_ACTIONS=true` and `GITHUB_WORKFLOW=PR`, and 0 in other contexts.
`PAPERCLIP_VITEST_RETRY` overrides the value.
- Add `scripts/ci/retry-reporter.mjs`. It writes a `::warning`
annotation and a step-summary table row for each test that passed only
on a retry.
- `scripts/run-vitest-stable.mjs` passes `--retry` to `vitest run`. The
`vitest list` collection step does not retry.
- The root `vitest.config.ts` and
`packages/paperclip-runner/vitest.config.ts` add the retry reporter
beside the existing reporters. The Runner config sets `retry` from the
same gate.
- `.github/workflows/pr-trusted.yml` reads the `CI_VITEST_RETRY`
repository variable. Set it to `0` to turn retries off without a code
change.
- Add `scripts/ci/retry-reporter.test.mjs` to `test:release-registry`,
and document the behavior in `doc/DEVELOPING.md`.

## Verification

- `node --test scripts/ci/retry-reporter.test.mjs`: 11/11 pass. The
tests cover the gate matrix, the reporter output, a real Vitest run with
a test that fails once, the wrapper arguments, and the Runner config.
- `node --test scripts/__tests__/run-vitest-stable-shard.test.mjs`:
39/39 pass.
- `node --test .github/scripts/tests/*.test.mjs`: 744/744 pass.
- Manual check: a temporary test that fails once ran through
`run-vitest-stable.mjs` and through the Runner package config. With the
PR environment it passed and wrote the annotation and the summary row.
With `GITHUB_WORKFLOW=Release` it failed. The temporary test is removed.

## Risks

- A retry can hide a flaky test. The annotation and the summary row
reduce this risk.
- A test that fails for a real reason now runs twice in PR CI, so a
failed run takes slightly longer.
- `pr.yml` loads `pr-trusted.yml` from master, so the `CI_VITEST_RETRY`
switch works only after merge. The retry itself is in the scripts and
starts with this pull request.
- Low risk otherwise: release and master verification are not changed.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use and
code execution. A Claude sub-agent wrote the change, and the parent
session reviewed it.

## Checklist

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

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

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 16:43:02 -08:00
DottaandPaperclip 752cb8d3da fix(ui): contain patch previews and highlight diff source (#19478)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Tasks show documents and file attachments in cards and file views.
> - Patch source needs clear added and removed lines in each view.
> - Long patch lines can exceed the width of an inline preview.
> - This pull request keeps the preview inside the card and adds patch
colors.
> - Users can read a compact patch preview and open the full source.

## Linked Issues or Issue Description

**What happened?**
Patch text can exceed the inline document box. Patch files and patch
code blocks show plain text in the document view.

**Expected behavior**
Keep source inside its preview. Show added lines in green and removed
lines in red. Keep headers, source markers, copy controls, line numbers,
and downloads.

**Steps to reproduce**
1. Open a document card with a diff code block that has long unbroken
lines.
2. Open a `.patch` or `.diff` attachment in a task tab.
3. Open the same patch in the workspace file view.

**Paperclip version or commit**
Source checkout based on `e82210386f1e`.

**Deployment mode**
Built from source. The browser checks use Storybook fixtures.

Related work: #19269 adds inline diffs for file edits. This change uses
its line classifier for document and file source views.

## What Changed

- Share the unified diff line classifier with file and document views.
Track hunk lengths so later file headers keep their metadata style.
- Add safe patch source rendering for patch files and `diff` or `patch`
code blocks.
- Clip long lines with an ellipsis in compact cards. Wrap the full
source in file views.
- Add a Preview patch action for small `.patch` and `.diff` attachments,
including generic binary MIME types. Opening a large artifact list does
not fetch patch source. Bound the actual response bytes. Reject remote
preview URLs and redirects. Keep download and retry fallbacks.
- Add patch text colors with sufficient contrast in both themes. Keep
source-line selection visible over patch row colors. Copy the original
patch text without layout line breaks.
- Add five production component stories and browser checks at two widths
in both themes.
- Document the focused Storybook browser command.

## Verification

- Final base qualification at
`a120e0ada3f1eee9746f47a10384b5aa95f79824`: normally integrated the
already-merged #19610 model-fixture repair
(`8621bcf53ee2e4bbefb7d193add42dd544ca06e8`), preserving the reviewed
patch preview changes. Official preflight passes (1.8s); all 129 focused
tests pass again (23.67s), with UI typecheck and token gates passing.
Current revision CI and reviews are pending; prior-head review receipts
below are not current-head approval.

- Conflict qualification at `befc954a6116f7292506200b96623b673f9385b5`:
normally merged current master
`c66b4a55efdc5675b5a5146197078b94de6407da`, resolving the append-only
`ui/src/index.css` conflict by retaining both the patch rules and all
current agent identity rules. No feature behavior was changed during
resolution. Official isolated preflight passes. The seven focused
component/parser suites pass 129 tests; UI typecheck and token gates
pass. Fresh head CI and reviews are pending.
- Qualification setup limits: the first focused command used the
repository root instead of the UI working directory, so setup failed
before any test ran. It was corrected to the UI directory and actual
attachment-test path. The first UI typecheck found the unbuilt local
plugin SDK; building that checkout’s SDK and repeating typecheck passes.
These setup attempts are not test/typecheck passes.

- Focused Vitest run: 139 tests passed in eight files. This includes
source preservation, HTML escaping, large patches, tabs, Markdown
wrapping, line numbers, and transcript diff parsing. A 200-card check
confirms no patch fetch occurs before a preview request. CRLF
regressions cover no-newline markers and added lines that start with
`+++`.
- `pnpm --filter @paperclipai/ui typecheck`: passed after the plugin SDK
build prerequisite.
- `pnpm check:token-gates`: passed.
- Storybook catalog check and 30 map/catalog tests: passed.
- `STORYBOOK_TEST_BASE_URL=http://localhost:6006 pnpm exec playwright
test patch-preview.spec.ts
--config=tests/storybook-visual/playwright.config.ts --project=stories
--workers=2 --retries=0`: 20 passed on Linux Chromium. The checks
include accessibility, theme colors, overflow, opening a document,
wrapping source, exact clipboard contents, and the selected source line.
Screenshots were inspected and saved to the task.
- The first browser runs found contrast failures in diff text and source
gutters. Patch text tokens and patch gutter opacity now fix those
failures. The final run passes without retries.
- `pnpm verify`: exit 2. The new browser spec is a global CI input.
Required CI must supply broad coverage.
- The local full Storybook build was killed with exit 137. This is not a
build pass. External screenshot baselines are not published, so no
baseline comparison was run.
- Reviews found selected-row background precedence, file headers in
unified patches without git separators, eager patch loading across a
collection, and CRLF no-newline markers. Each finding has a focused fix
and regression checks. The Storybook hunk count now matches its source
lines.
- Prior feature commit `b3cb9d6d3c0423315540514b1ad68325a64c1cc2`: [CI
typecheck, build, tests, runner checks, and E2E
passed](https://github.com/paperclipai/paperclip/actions/runs/42247974154).
[Security scan
passed](https://github.com/paperclipai/paperclip/runs/127995160859).
[Greptile is
5/5](https://github.com/paperclipai/paperclip/pull/19478#issuecomment-6688482753),
and [Paperclip Review is
5/5](https://github.com/paperclipai/paperclip/runs/127998788846). All
six inline review threads are resolved.

## Risks

- Compact previews omit the end of long lines. The full document and
downloads keep the source.
- Patch colors apply to unified diff syntax. Other patch formats keep
literal text and do not have full format parsing.
- The shared classifier keeps the existing task chat behavior.
Regression tests cover that consumer.
- No database, API, or access rules change.

## Model Used

- OpenAI Codex, GPT-6, with reasoning, tool use, and code execution. The
runtime did not expose a more specific 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <[email protected]>
2026-11-07 00:06:50 +00:00
Nicky LeachandClaude Opus 5.5 a2aa69bc62 perf(ci): split the Runner package Vitest lane into three duration-balanced lanes (#19646)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Contributors wait for PR CI before merge, so CI wall-clock time sets
the pace of work
> - The Runner package Vitest lane is the PR CI critical path. It
finishes a median 15.3 minutes after the push
> - That lane runs all 311 package test files in one job with 2 workers:
1,381 seconds of serial work and 727 seconds of wall time
> - Ten files account for 61% of the time. Vitest's `--shard` splits by
file count, not by duration, so it cannot balance this work
> - This pull request splits the package files into three lanes balanced
by duration, and runs the native server group in its own lane
> - The benefit is about 232 seconds of tests per package lane instead
of 727 seconds

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The time that PR CI needs to run the Runner package Vitest tests.

**Subsystem affected**

CI: the `Verify Paperclip Runner` Vitest lanes in `pr-trusted.yml`, and
`packages/paperclip-runner/scripts/run-pr-vitest-lane.mjs`.

**Current behavior**

The `vitest package` lane takes a median of 13.6 minutes. It is the last
job to finish in most PR runs. The lane keeps a 2-worker limit so that
Runner and sidecar processes can start within their deadlines. Average
CPU use is 29%.

**Proposed behavior**

Three package lanes each run about one third of the files, balanced by
measured duration. Each lane keeps the 2-worker limit. The native server
group runs in its own lane, as before.

**Reason and benefit**

The Runner lane stops being the critical path. More workers per lane
would add CPU contention and timeouts, so this change adds lanes
instead. The cost is about 3 runner-minutes per run for the extra setup.

**Breaking changes**

None. The `master` ruleset requires only `ci / verify` and `ci / e2e`,
so the new job names do not change required checks.

## What Changed

- Add `packages/paperclip-runner/scripts/vitest-package-lanes.mjs`.
- It gets the file inventory from `vitest list --filesOnly --json`, so
it follows `vitest.config.ts`.
- It partitions the files with the `partitionGeneralServerSuites`
function from `scripts/general-server-shard.mjs`.
- Add `packages/paperclip-runner/scripts/vitest-lane-durations.json`.
- It uses the same schema as
`scripts/general-server-shard-durations.json`.
- It is seeded from CI run 42290920882. It measures 310 of the 311
files.
- `run-pr-vitest-lane.mjs` accepts two new arguments:
  - `--package-lane=K/N` runs one partition.
  - `--native-server` runs only the native server group.
- The `--shard=1/2` and `--shard=2/2` arguments keep their exact
behavior for the matrix that master still sends.
- `.github/ci-task-matrix.json` and the fallback matrix in
`pr-trusted.yml` have three `vitest package (K/3)` lanes and the `vitest
native server` lane. The labels keep the `vitest` prefix because
`scripts/ci-profile/trace.mjs` matches `Verify Paperclip Runner
(vitest`.
- Tests:
- the lanes cover the inventory exactly once, checked with real `vitest
list`;
  - the old `--shard` commands plan the same commands as before;
  - the manifest schema is correct;
  - bad lane arguments are refused.
- Update the Runner Vitest lane paragraph in `doc/DEVELOPING.md`.

## Verification

- `node --test scripts/__tests__/run-vitest-stable-shard.test.mjs`:
44/44 pass.
- `node --test scripts/__tests__/runner-release-crate-cache.test.mjs
.github/scripts/tests/ci-plan.test.mjs`, together with the file above:
71/71 pass.
- Related workflow, cache, routing and profile tests: 446/446, 153/153
and 14/14 pass.
- Local end-to-end run of `GITHUB_WORKFLOW=PR node
scripts/run-pr-vitest-lane.mjs --package-lane=3/3` on macOS:
- the prep steps ran, and the lane ran 104 of 311 files in 123 seconds;
- 2 tests failed: a macOS `kill EPERM` and a 5-second timeout under full
local load without the CI worker limit;
  - both files pass when run alone (35/35 tests).
- This PR's own CI runs the matrix from master, so it uses the old
`--shard` commands. The new lanes run in CI only after merge. Reviewers
can compare the job durations of the first PR runs after the merge.

| Lane | Files | Serial estimate | Wall estimate with 2 workers |
| --- | --- | --- | --- |
| 1/3 | 103 | 464.9 s | ~232 s |
| 2/3 | 104 | 465.0 s | ~232 s |
| 3/3 | 104 | 465.0 s | ~232 s |

## Risks

- The weights come from one CI log and do not include collection or
setup time. The balance can be uneven until a refresh measures real lane
runs.
- Each lane passes its file paths to Vitest as filters, and Vitest
matches a filter as a substring of the path. A path that is a substring
of another path would run a file in two lanes. No current path does
this. If one does, the script prints a warning, and no file is dropped.
- Each package lane runs `ensure:eval-build-deps` and `build:rust`
again, which adds about 3 runner-minutes per run.
- A comment in `scripts/run-vitest-stable.mjs` (about line 110) still
says that the last Runner shard runs the native group. A follow-up will
correct it.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use and
code execution. A Claude sub-agent wrote the change, and the parent
session reviewed it and ran the local lane check.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 15:12:05 -08:00
DottaandPaperclip 2e340d6235 fix(ui): show unread message counts in agent chat pickers (#19627)
## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - People switch between agent chats to read replies.
> - The agent picker does not show how many messages are unread.
> - People need that count before they select a chat.
> - The chat list already has the viewer's read state.
> - This pull request adds numeric badges from that shared list.
> - People can find unread replies without extra requests for each
agent.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The chat header agent picker and the secondary sidebar's Add chat
picker.

**Subsystem affected**

Agent Chat UI, shared task types, and the chat read-state service.

**Current behavior**

The pickers do not show the unread message count for each agent.

**Proposed behavior**

Show the unread count beside each agent. For example, show 3 for three
unread agent messages. Hide the badge when the count is zero. Keep
switching available while chat data loads.

**Reason and benefit**

People can see which chats need attention before they switch. The count
uses the existing shared request and cache.

**Breaking changes**

The chat response gains an optional `unreadMessageCount` field. Existing
fields and read receipts keep their behavior. There is no database
migration.

**Additional context**

Related: #19425 changes chat structure. This change adds counts to the
current agent pickers. The duplicate search found no PR for these count
badges.

## What Changed

- Count unread agent comments in the existing grouped read-state query.
Keep company and viewer scope.
- Exclude human messages, deleted comments, system notices, and
fast-response receipts.
- Pass memoized count maps to both agent pickers. Use a direct lookup
for each row.
- Show numeric badges and accessible singular or plural labels.
- Test count changes, account isolation, cached opens, search, and large
agent lists.
- Update the chat stories, fixture data, and development guide.
- Make the Slack owner retry-deadline test deterministic after
reproducing its CI failure.

## Verification

- On current head `05f5fa6201`, the [main CI
workflow](https://github.com/paperclipai/paperclip/actions/runs/42305845930)
passed, including build, typecheck, all applicable test shards, browser
shards, and final verification. The prior server shard 2, 6, and 7
failures do not recur.
- [Greptile's current-head
review](https://github.com/paperclipai/paperclip/pull/19627#issuecomment-6696161631)
is 5/5, its check succeeded, and both previous review threads are
resolved. Current head check inventory is complete: 55 successful
checks, three intentional skips, and no pending or failed checks. The
Paperclip application review summary reports paused; no new completed
Paperclip review is claimed. Its existing findings are resolved, and the
independent current-head Greptile review is 5/5.

- Current head `05f5fa6201` merges master `8621bcf53e` to resolve an
import conflict. Master now includes the same model-default test repair.
This branch keeps its upstream import, so that file no longer differs
from master.
- After this merge: all 21 server tests in the Slack owner and
model-default suites pass, all 22 tests in the three picker/sidebar
suites pass, and token gates pass. The standard preflight still cannot
build Rust because `cargo` is absent. Fresh CI must supply the broad
checks.

- Head `7d125fb8c7` replaces the Slack owner test's real timer wait with
the worker's existing clock input. It checks that the intent stays
pending one millisecond before the retry deadline and clears at the
deadline. All 15 Slack owner integration tests pass locally. Runtime
retry behavior is unchanged.
- On `4cfb1e9d55`, both review checks gave 5/5. CI [server shard
2](https://github.com/paperclipai/paperclip/actions/runs/42302458830/job/128183671349)
failed the Slack credential-lease retry test. The same assertion failed
in an unchanged local run. A diagnostic run with added logging passed;
the exact timer deviation was not captured. The new test removes the
real-time wait assumption and checks the clock boundary directly. The
earlier model-default and cleanup failures did not recur in that CI run.

- On `a3378dfc40`, both Greptile and Paperclip Review gave 5/5 with no
open findings. Typecheck, build, all eight browser shards, and most test
shards passed.
- That CI attempt failed two server shards. The model-default test still
expected `gpt-5.6-sol`, while the current default is `gpt-6.1-sol`. I
reproduced both failures locally. Commit `4cfb1e9d55` uses the exported
default-model constant; all six tests in that suite now pass. Production
model selection does not change.
- The retained-cleanup backlog test exceeded its 15-second limit in
[server shard
6](https://github.com/paperclipai/paperclip/actions/runs/42299858777/job/128175193320).
The unchanged test passes locally with the same limit. Its CI timeout
cause is not established. No assertion or timeout was changed. The fresh
CI run must recheck it.

- Review repair: commit `0db631b215` keeps the existing-chat and
unread-agent arrays stable when chat data and the active agent do not
change. A new 500-agent test fails before the repair and passes after
it. The test also checks count refreshes.
- The first CI typecheck failed on duplicate `IMAGE_ONLY` and
`TESTOPS_REQUESTED` keys in `tests/runner-e2e/workflow-target.test.ts`.
This was present in upstream code. Master commit `e482f0f632` removes
the duplicate keys. This branch now includes that commit. All nine
workflow-target tests pass locally.
- After the latest master merge: all 22 tests in the three affected
picker/sidebar suites pass. Before that merge, all 38 focused UI tests
passed.
- The current head is `05f5fa620134ad199a3d85b66fb2eb93d3bb6077`. All
current status checks are complete, the aggregate verify gate succeeds,
mergeability is CLEAN, and no unresolved review threads remain. The
maintainer has instructed normal merge after checks pass.

- After merging current master: 37 focused UI tests pass across
AgentChatPicker, CoordinatorAgentSwitcher, AgentConversationsSidebar,
useAgentChatRead, and SidebarAgentChats.
- After merging current master: all 38 proactive-chat integration tests
pass with a real disposable PostgreSQL database. They check counts of 2,
1, and 0 at exact read cutoffs.
- The prior revision passed UI typecheck and build, shared build,
isolated low-memory server typecheck, token gates, and 30 map/catalog
tests.
- The 500-agent test bounds index work and count lookups. Search does
not rebuild the existing-chat index.
- Browser checks used production components with simulated API data.
Dark, light, mobile, and Add chat cases passed. Counts of 3 and 12
stayed visible through search. Read agents had no badge. Cached opens
made no chat API requests.
- Twenty local cached opens took 34.5–57.6 ms, with a 38.4 ms median.
This is an unthrottled fixture measurement.
- Earlier full local checks could not complete because Rust cargo is
absent and the workspace has limited memory. The standard worktree
preflight also stops at missing cargo. Required CI must supply broad
checks for this PR head.
- The first server typecheck ran out of memory while Storybook was
active. Its isolated low-memory retry passed. No visual baseline
comparison or live provider delivery is claimed.

## Risks

- Low risk. The count replaces a boolean aggregate in the same grouped
query. Very long chat histories still need that existing scan.
- A count can be stale until the existing chat-list cache refreshes.
Read receipts still control the unread cutoff.
- The change adds no polling, per-agent history request, database
migration, or permission change.

## Model Used

OpenAI GPT-6 through Codex, with code editing, terminal tools, and
browser verification. The runtime did not expose a more specific served
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 <[email protected]>
2026-11-06 17:10:56 -06:00
Nicky LeachandClaude Opus 5.5 f2299cb00f feat(ci): re-run failed PR jobs once when every failure was a lost runner (#19640)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Contributors wait for PR CI before merge, so CI wall-clock time sets
the pace of work
> - PR CI runs on self-hosted AWS runners, and these runners are
sometimes lost mid-job
> - On 2026-11-06, 21 of 51 failed jobs on commits that later passed
were runner loss. The losses came in fleet-wide bursts, for example 10
jobs on 10 instances in 51 seconds
> - After each loss, a person must notice the failure and click "Re-run
failed jobs"
> - This pull request adds a workflow that re-runs the failed jobs once,
only when every failure was a lost runner
> - The benefit is fewer manual re-runs and a shorter wait for CI to
pass

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The recovery of PR CI runs that failed only because a self-hosted runner
was lost.

**Subsystem affected**

CI: the trusted PR workflow (`pr.yml` → `pr-trusted.yml`).

**Current behavior**

When an AWS runner is lost, the job fails with "The self-hosted runner
lost communication with the server" or "The runner has received a
shutdown signal". The PR stays red until a person re-runs the failed
jobs.

**Proposed behavior**

When the `PR` workflow completes with a failure on attempt 1, a new
workflow examines the failed jobs. If every failed job lost its runner,
it re-runs the failed jobs one time. If any job failed for another
reason, it does nothing.

**Reason and benefit**

A runner loss is not a code problem. The re-run starts without waiting
for a person to notice the failure.

**Breaking changes**

None.

## What Changed

- Add `.github/workflows/ci-runner-loss-rerun.yml`. It triggers on
`workflow_run` for `PR`, and it acts only on attempt 1 failures of
`pull_request` runs in this repository. It runs on `ubuntu-latest`, so
it does not depend on the AWS fleet.
- Add `.github/scripts/runner-loss-rerun.mjs`. The script:
- lists the attempt-1 jobs and ignores the aggregate jobs `ci / verify`
and `ci / e2e`;
- finds runner loss in the check-run annotations. If the only annotation
is "The operation was canceled.", it reads the `##[error]` lines in the
job log;
- calls `rerun-failed-jobs` only if every remaining failed job lost its
runner;
- does not re-run when the gate job failed, the PR head moved, the PR is
closed, or a newer run exists for the same commit;
- writes the decision and a job table to the step summary. A `--dry-run`
mode never posts.
- Add 16 tests, with trimmed fixtures from four real runs.
- Add a paragraph about the workflow to the "Trusted PR Workflow"
section of `doc/DEVELOPING.md`.

## Verification

- `node --test .github/scripts/tests/runner-loss-rerun.test.mjs`: 16/16
pass. The tests cover:
- re-run when all jobs were lost, including the case where the signal is
only in the log;
- no re-run for mixed failures, real failures, only aggregate failures,
attempt 2, a failed gate, or a stale run;
- pagination, incomplete listings, a cross-host link that is refused,
and the workflow guards.
- `node --test '.github/scripts/tests/*.test.mjs'`: 760/760 pass.
- A read-only dry run against the real GitHub API for six recent runs
classified all six correctly.
- The workflow can run on GitHub only after it is on master. Reviewers
can make sure that the first runner-loss failure after the merge
triggers one re-run.

## Risks

- **Trust:** a re-run of failed jobs uses the outputs of the gate job
that succeeded on attempt 1, including its AWS runner admission. A
person who clicks "Re-run failed jobs" gets the same result. The only
difference is that `github-actions[bot]` triggers attempt 2. The
workflow never checks out or runs PR code.
- **Failed gate:** if the gate job itself failed, the script does
nothing. A re-run by the bot would put all jobs on GitHub-hosted
runners.
- **Profiling:** `scripts/ci-profile/export.mjs` exports only attempts
triggered by authorized users. The bot-triggered attempt 2 is not
exported to Honeycomb. Attempt 1 is still exported.
- **Events:** GitHub may not create `workflow_run` events for attempts
that `GITHUB_TOKEN` started. Then other `workflow_run` consumers do not
react to attempt 2. Required checks still update.
- **Permissions:** `actions: write`, `checks: read`, `contents: read`
and `pull-requests: read`. The last one lets the script confirm that the
run still belongs to the open PR's current head.
- **Cancelled jobs:** a runner loss where the lost jobs end as
`cancelled` and only the aggregate jobs show `failure` is not re-run.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use and
code execution. A Claude sub-agent wrote the change, and the parent
session reviewed it.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 15:05:42 -08:00
Nicky LeachandClaude Opus 5.5 913102870d fix(environments): fill the managed instance default once, under a lock (#19632)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - On a managed instance under `enableManagedSandboxOnly`, boot points
the instance default at the managed sandbox when nothing deliberate is
set
> - That write read the default and wrote it later without a lock. Two
overlapping boots, or a boot and an admin write, could overwrite a value
the other had just committed
> - A revert path also reset that stamped default whenever the mode was
turned off, so a platform-filled default could change again later
> - A planned plugin-environment default should fill only an unset
default and never change a set one, which needs every instance's default
to stay set
> - This pull request makes the fill one locked transaction and removes
the revert, so a filled default is an ordinary set value
> - The benefit is a single, race-free rule: boot fills an unset default
once, and nothing changes a set default afterwards

## Linked Issues or Issue Description

Refs #19551 (the plugin-environment default that builds on this rule)

**Subsystem affected**
Server environments: the managed environment bootstrap and the instance
settings route.

**Problem or motivation**
`applyManagedEnvironments` read the instance default, decided, and wrote
it in separate steps with no lock, so a concurrent boot or admin write
could be overwritten. When `enableManagedSandboxOnly` was not declared,
a revert reset a default the platform had filled. That needed a
`managedDefaultStamped` marker, plus a marker clear on every admin
default write. A later boot could therefore change a default that had
already been set.

**Proposed solution**
Fill the default in one transaction that locks the instance default row
and rechecks it. Fill only an unset, local or deleted default. Remove
the revert and its marker, so a filled default behaves like any other
set value.

**Alternatives considered**
Keeping the revert and adding locks to it. A revert on a mode change
would still move an existing instance's default later, which a fill-only
plugin default cannot tolerate.

**Roadmap alignment**
This narrows existing managed-environment behaviour. It adds no product
surface.

## What Changed

- New `server/src/services/managed-instance-default.ts`:
`fillUnsetInstanceDefault(db, environmentId)`. In one transaction it
creates the settings row if needed, locks the instance default row (`FOR
UPDATE`), and rechecks the current value. It writes only when the
default is unset, names the local environment, or names a deleted
environment. Any other set default is kept.
- `server/src/services/managed-environments.ts`:
- Under `enableManagedSandboxOnly`, the managed sandbox fill now uses
that helper, through a new `fillInstanceDefault` test seam.
- The mode-off revert and the `managedDefaultStamped` marker are
removed.
- The unused `instanceSettings` seam option is removed, along with the
`getById`, `update` and `findManagedSandboxEnvironment` methods of the
environments seam.
- `server/src/routes/instance-settings.ts`: an explicit default write is
a plain `update` again. The marker clear and its transaction are gone.
- `doc/DEVELOPING.md`: the "Managed environments" section states the
fill rule.

Existing rows may still carry `managedDefaultStamped` in their metadata.
Nothing reads it any more, and it is not part of the managed stock hash.

## Verification

- `cd server && npx vitest run
src/__tests__/managed-instance-default.test.ts` (embedded Postgres):
- The fill writes an unset default, including on a fresh instance with
no settings row. It also writes a default that names the local
environment, or one whose environment was deleted.
- It never changes a default set to an ssh, plugin or other sandbox
environment, and leaves a default that already names the target alone.
- Controlled overlap: an uncommitted admin default write makes the fill
wait, and the fill then keeps the admin's value. This test fails without
the row lock.
  - Two overlapping fills: exactly one writes.
- Real boot pass: the default is filled under managed-sandbox-only and
stays the same after a boot with the mode off. A set default stays on
every boot.
- `npx vitest run src/services/managed-environments.test.ts
src/services/managed-environments-plugin-entries.test.ts`: the fill runs
only under managed-sandbox-only, with the managed sandbox row. It never
runs for a plugin environment. Nothing is written when the mode is off.
A failed fill does not fail the ensure.
- `npx vitest run src/__tests__/instance-settings-routes.test.ts`: an
explicit default write is a plain update that touches no environment
row.
- `npx vitest run src/__tests__/managed-plugin-environments.test.ts
src/__tests__/environment-routes.test.ts
src/__tests__/server-startup-feedback-export.test.ts
src/__tests__/instance-settings-service.test.ts`: pass.
- `pnpm --filter @paperclipai/server typecheck` passes.
- I did not run the full `pnpm test:run`, `pnpm -r typecheck` or `pnpm
build`. CI covers them.

## Risks

- Behaviour change: when `enableManagedSandboxOnly` is turned off for an
instance, a default the platform filled with the managed sandbox now
stays. Local execution is still allowed then, but it is no longer the
default unless an admin picks it.
- No schema change. Stale `managedDefaultStamped` keys stay in some row
metadata and are ignored.
- The fill holds the instance default row lock for one short transaction
at boot.

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

## Model Used

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

## Checklist

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

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

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 15:01:58 -08:00
DottaandPaperclip fdda02a26e fix(ui): remove stale plan approvals from the composer (#19528)
## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - A task can ask a person to approve a saved plan.
> - The server refuses approval after a newer plan replaces that plan.
> - The document and request queries can finish at different times.
> - The composer can then keep controls for a plan that is already out
of date.
> - This change removes those controls when a newer saved revision is
known and refreshes the request state.
> - People can review the current plan without an obsolete approval
above the composer.

## Linked Issues or Issue Description

**What happened?**

An old plan approval can stay above the task composer after the plan
changes. Selecting Approve returns a superseded error. The failed action
does not refresh the request list.

**Expected behavior**

Remove the old approval and its pending count as soon as the UI knows
that the plan has changed. Keep a newer approval visible if its document
query still has an older revision. Preserve accepted decisions and
unrelated requests.

**Steps to reproduce**

1. Create a confirmation for a saved plan revision.
2. Save a newer plan revision while the old request remains in the
browser cache.
3. Read the composer before the request query finishes.
4. Try an old approval after the server has expired it.

**Paperclip version or commit**

Source checkout based on `5d25117ffa`.

**Deployment mode**

Source checkout. The regressions use the production React components and
query cache in jsdom.

Related work: Refs #14907 (replaced request receipts) and Refs #15025
(server replacement for requests from board users). This change fixes
the interval between document and request reads, plus the refresh after
a failed resolution. It does not change those server or history rules.

## What Changed

- Remove pending approvals for older saved plan revisions from the task
display and composer.
- Compare revision numbers, or document and request times for targets
that have only a revision ID. Keep newer requests when the document
cache is behind.
- Refresh request queries after document activity and settled approval
errors.
- Use the same plan freshness check for recovery retry. An old hidden
approval no longer blocks Retry agent.
- Test the recovery controls and the guards for missing plans, accepted
decisions, questions, other tasks, other companies, and older document
reads.
- Add regressions for both workspace presentations, task and
conversation modes, revision formats, unrelated requests, and accepted
history.
- Document the display behavior in the development guide.

## Verification

- TDD: five assertions failed before the main fix. Old controls stayed
in the composer, document activity did not refresh requests, and
superseded accept or reject responses did not read the new state.
- The previous session recorded 815 passing focused UI tests after the
recovery retry fix at `d95cf3235ed83dc1f6c7f51372dff408814eb15c`. UI
typecheck, the production UI build, and token gates also passed.
- The main fix at `5147fd29a03413596d5b2445ae3b480352cbff1a` passed all
CI gates. This includes typecheck, build, general tests, and eight E2E
shards. [CI
run](https://github.com/paperclipai/paperclip/actions/runs/42266101662).
- The prior affected local run passed 7,656 UI tests, with one skipped,
and 38 shared tests. Its CLI phase had two file-mode failures under the
agent's `0077` umask and a child-server timeout during first-use builds.
All three CLI files passed after a new check with `umask 022` and warm
build dependencies: 25 tests passed. No CLI source changed. The initial
full command did not pass or store a reusable pass.
- On this continuation, the local test command could not start because
the restored workspace has no installed Vitest dependency. No test
verdict came from this attempt.
- Final
[CI](https://github.com/paperclipai/paperclip/actions/runs/42302544154)
passes all applicable required gates for
`d95cf3235ed83dc1f6c7f51372dff408814eb15c`, including typecheck, build,
general tests, and eight E2E shards. Greptile and Paperclip reviews both
report 5/5 at that exact revision, with no unresolved findings. The
separate advisory review workflow remains queued.
- No live browser acceptance or deployment is claimed.

## Risks

- The UI must distinguish an older approval from an older document
cache. Tests cover both query orders and preserve newer approvals.
- This is a display and cache refresh change. Stored decisions, server
authorization, approval gates, and database schemas are unchanged.

## Model Used

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

## 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 described the issue in-PR following the bug template
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My remote branch name describes the change and contains no
internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass for the final revision
(scope and initial environment failures are stated above)
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <[email protected]>
2026-11-06 16:25:51 -06:00
DottaandPaperclip 44c60c7221 Verify default skill sync through two real cold sleep cycles (#19191)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Automatic skill sources check upstream every five minutes.
> - The domain deadline contract can hand future work to a durable wake
alarm.
> - A saved deadline or a worker unit test does not prove that a stopped
tenant resumes the real producer.
> - This pull request checks the default cadence through the actual app
and a matching service checkout.
> - It checks two cold starts and three distinct persisted skill
revisions with real clocks.
> - The benefit is repeatable local evidence without changing production
timing or authority.

## Linked Issues or Issue Description

Refs: #19001, #17008, #17923.

This is a test and documentation follow-up to #19001. #19001 is now
merged; this PR targets master. Feature code remains in that merged
foundation. #17008 covers the earlier source worker change. #17923
covers separate skill-cache retention; this PR does not change that
behavior.

A scoped search of public PRs and issues found no duplicate
default-cadence cold-cycle acceptance test. No matching public issue was
found. The enhancement fields follow.

**Pre-submission checklist**

This improves checks for an existing feature. It adds no product
endpoint or execution authority. The public related-work search is
complete.

**What existing behavior does this improve?**

Verification of automatic skill-source updates across a durable idle
handoff and cold restart.

**Subsystem affected**

`server/` test fixtures and development documentation.

**Current behavior**

Domain tests cover claims, access and future deadlines. They do not
prove the full default-300-second path from ordinary source attachment
through signed tenant reports, durable alarms, stopped compute and a
changed persisted skill.

**Proposed behavior**

An opt-in launcher checks bootstrap first, then two full real-clock
cycles. Ordinary CI can run the transport controls without the host
fixture. The launcher-specific worker assertion runs only when prewire
is requested, and fails closed in that lane.

**Reason and benefit**

The test can detect an early refresh, a lost deadline, a stale proof, an
ACK after physical stop, a failed cold restart or a producer that does
not publish changed contents.

**Breaking changes**

None to product APIs, schema, cadence, authorization, claims or retry
policy. Existing maintenance-helper defaults stay unchanged.

**Additional context**

The opt-in host flow needs an authorized matching service checkout, its
normal compiled acceptance bridge, Node 22 and two owned PostgreSQL test
databases. The standalone controls do not need that service or
PostgreSQL. Roadmap review places this under existing Skills Manager and
Cloud deployment verification, not a new core feature.

## What Changed

- Add an opt-in default-cadence acceptance test and checked-in launcher.
- Reuse real server startup, managed tenant authority and the ordinary
project attachment route. Control only the remote Git snapshot boundary;
keep actual authorization, scanning, claims and publication.
- Use actual signed tenant routes and the matching service's gate, sleep
controller and PostgreSQL wake-alarm store. Stop and restart real app
children through a controlled local provider.
- Preload the Node transport guard before coordinator, worker and child
module work. Check worker-local and child PID, thread and source-hash
receipts. Keep caught unexpected transport attempts fatal.
- Require both persisted refresh times and actual upstream handler
timestamps to be at or after each original deadline.
- Join physical callbacks during bridge close, stop a late wake-created
child, and preserve the original cleanup budget. Add a real
IPC/HTTP-child counterfactual.
- Accept valid owned HTTP options paths without allowing Unix sockets or
unknown origins. Remove the unused App helper export.
- Add finite transport and launcher controls. Defer only the
launcher-specific parent assertion in vanilla CI. Keep all subprocess
controls default-runnable.
- Add optional child entry, preload and bounded pool settings to the
existing test helper. Preserve its defaults and the real observer test
body.
- Remove added explanatory comments under the contribution policy.
Preserve executable syntax, directives and literals.
- Document pinned setup, fresh owned databases, bootstrap and full
two-cycle commands in `doc/DEVELOPING.md`.

The diff against the intended base contains nine test/helper/launcher
files and this one document. It adds no production feature code or Cloud
source to the App repository.

## Verification

The latest two-file CI repair preserves both original failed jobs. A
newer master workflow introduced two required step inputs absent from
its extracted-step test environment; the faithful false/false inputs now
pass all five existing target-authorization cases, after an actual
missing-input counterfactual failed. A controlled socket case also
reproduces legal reuse of the released runner port; the fixture now
holds that reservation until the preview binds, releases it in finally,
and asserts distinct ports. All original retry/result/accounting
assertions and timeouts stay unchanged. The original Linux listener
owner remains unknown; the control proves this fixture collision
mechanism, not that original attribution. Fresh current-head full CI and
complete reviews are pending.


Current merge-integration candidate:
`f6f686103b7bfd0a4ab83085634c622af3896ecb`, a normal merge after #19001
landed, including actual master
`1c9767d323cf872a266962b9e6b4bd5cbcb57f1c`. All nine acceptance code
files are byte-identical to148a82e5; both parents' documentation
additions survive. Every other path equals actual master. The only
conflict was the old foundation's startup block; current master was
retained in full. Fresh exact-head CI and complete reviews are pending.
The local measurements below belong to their explicitly named revisions;
no fresh full614-second run is claimed.

Current candidate: `148a82e54c502759785407357001c6b50c1439dd`, normally
carrying the current base `8c912e1ba4fc66397a7341ade59373af8075068f`.
All nine acceptance code blobs remain identical to qualified repair
`e092746f4da181c8414c63be71474619a84b97e7`; documentation retains both
parents. The matching built service for the fresh bootstrap is
`389878364b42dd129835d263f91882c5774d5123`, using Node 22.23.4. The App
uses Node 24.22.2 and pnpm 9.15.4.

Use the exact controlled setup and commands in
[doc/DEVELOPING.md](doc/DEVELOPING.md#controlled-default-cadence-sleep-acceptance).
Fresh owned databases and the matching compiled service are
prerequisites; the launcher does not create a cluster or build
dependencies.

- **Current affected semantic check passed**, using actual native
TypeScript 7 and the current generated CI options/references (0.640
seconds). Normal declaration references passed in 11.726 seconds, with
source-matched SDK/shared/kernel/Runner prerequisites and the frozen
current lockfile.
- **Current guarded controls: 44 passed**, plus the same single explicit
real-PG observer deferral, in 4.790 seconds. The executing worker
checked its own global PID/thread/preload source hash and matching
receipt. Guard hash:
`7daf9ae7d2f26e8fcd389b1d560fac1d4e218a8304a2c58e49f646af108165bc`. No
unexpected attempts or PG sockets occurred. Vanilla controls on the
identical repair files passed 22 cases with one explicit launcher-parent
assertion deferral (2.044 seconds).
- **Real cleanup counterfactual:** unchanged old helper failed the
actual endpoint assertion in 1.568 seconds: its late HTTP child remained
reachable after close returned. The same fixed-helper case passed in
1.485 seconds: close waited, the child exited, the port was unreachable
and no IPC error occurred. The isolated driver records only the known
legacy closed-IPC error long enough to inspect cleanup; current helper
requires zero such errors. All recorded processes and ports were
independently checked empty.
- **Fresh current bootstrap passed once**, 16.159 seconds wall / 14.12
seconds Vitest / 12.237 seconds body. The ordinary enabled producer
persisted useful revision 1, cleared its lease and retained exactly
300,000 ms between success and original next deadline. A signed
current-generation report produced the matching
generation/operation-lock SQL ACK before physical child stop (ACK
06:54:02.020 UTC; stop signal 06:54:02.025 UTC). The alarm retained the
original D and D-120 eligibility. Both fixture databases ended with zero
sessions; the coordinator independently audited and normally removed
them. This bootstrap does not execute a due wake or the new v2/v3 timing
assertions.
- **Historical full two-cycle result remains 1/1 passed in 614.213
seconds wall**, at measured App
`74f7c012ca17ad0d7302d76403f5033affb1430b` and service
`2cb54f904031dcf69a32902cd853b43f74d6f301`. Two new processes published
distinct immutable v2/v3 at original D+115 ms and D+98 ms. Exact signed
SQL ACKs preceded both physical stops; real SQL D-120 claims caused both
cold starts. Features, claims and original business deadlines stayed
unchanged.
- Both historical 10.002-second quiet windows kept app SQL, upstream and
outgoing HTTP counts unchanged. Two awake PG sockets remained. Quiet
counts do not mean all connections closed.
- The stronger timing checks are additional assertions, not
retroactively executed results. Offline inspection of the original
receipts confirms persisted successes at D+115/+98 ms and earliest
upstream handler entries at D+44/+30 ms; all five requests in each
refresh group started after D. The exact new pre-sleep request counter
and dispatch-before-handler time were not independently recorded
historically.
- Source review found 19 critical automatic Git producer/runtime files
identical to the measured App. The current active cache helper change
does not run on automatic Git publication and adds no pool. The matching
service carries unchanged measured helper/sleep/controller/alarm seams
through its normal master merge. The current bootstrap and focused
controls qualify the changed cleanup/guard and parent integration. No
fresh current-head 614-second pass or whole-tree identity is claimed.
- Runtime is serialized: worker 1, per-pool limits at most 4, configured
App potential 10 + shared service pool 2 + one observer against server
max 40. Current bootstrap point samples observed at most 3 owned clients
plus the observer; those samples are not a continuous aggregate peak.
App/service guard peaks were 2 each, with zero remaining sockets at
cleanup.

Failures remain accounted for. The first historical bootstrap cleanup
masked an unknown primary error. Later attempts exposed missing
fixture-owned domain metadata and lost origin snapshots; test-only
ownership/cleanup corrections preserved the original assertions and
timeouts. The new causal driver first lacked a final observation, then
encountered sandbox bind EPERM, then exposed the old closed-IPC error;
only the final real endpoint failure above is labeled the cleanup
counterfactual. An offline dependency miss and a generator-cleared
ignored compiler config were retained and corrected without weakening
compiler options. The former inherited policy failure (397 findings) is
resolved by the genuine current-master base/scanner, with zero findings
and seven scanner tests; no unrelated UI site was edited and the gate
was not relaxed.

Historical real TCP observer coverage passed 1/1 in 3.688 seconds.
Comment-only and opt-in-selection carries remain documented in prior
evidence; self-hash changes are metadata exceptions with fresh
executing-worker checks.

The full workspace typecheck, test, build and browser commands were not
run locally for this test-only slice. The current base passes its full
exact-head CI and Greptile 5/5; this leaf now has all 56 exact-head
check runs terminal (54 success, one neutral, one skip), the additional
legacy security status passing, complete current Greptile 5/5 and
Ponytail 5/5 reviews, and all four review threads resolved across all
pages. No live provider or deployed host/database suspension was
exercised.
## Risks

- The host test requires a matching service checkout and compiled
bridge. An unrelated service revision is not a valid substitute.
- Controlled localhost Git, S3 and physical process operations do not
qualify deployed Railway or Neon suspension, external permissions or
provider uncertainty.
- The Node guard does not prove native subprocess transport isolation.
Only finite local Git metadata and Node version commands were needed.
- Two short SQL/HTTP quiet windows do not establish strict 90-second
host acceptance. Separate plugin-host LISTEN, MCP wake and other domain
gaps remain open.
- Explicit test URLs are destructive fixture inputs. Use fresh, owned
`_test` databases. The launcher does not start a cluster or choose a
database implicitly.
- Passing CI and review do not qualify deployed suspension; the current
proof boundaries above still apply.

> For core feature work, check `ROADMAP.md` first and discuss it in
`#dev` before opening the PR. This PR adds verification to the existing
feature in its base.

## Model Used

OpenAI `gpt-6.1-sol` assisted with implementation, repository review and
local code execution. OpenAI Codex, based on GPT-6, performed managing
review and integration. The context-window sizes and exact served
backend snapshots were not exposed.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [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 — fresh merge-head gates pending
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
fresh merge-head review pending
- [x] I will address all Greptile and reviewer comments before
requesting merge


### Current master reconciliation (2026-11-06)

Merged actual master `e28158b353fd274f0322be76d62cd223e0df599f`
normally. All nine qualified acceptance code files and the held-port
repair remain byte-identical. Upstream independently landed the workflow
input fix in #19602; the fixture now uses that exact upstream version
once, avoiding duplicate object keys in a squash merge. All other master
changes are preserved. The earlier two-cycle local evidence remains
source-specific; no fresh 614-second run or deployed suspension is
claimed. This new head requires fresh CI and bot reviews.

### Actual combined-tree typecheck failure and repair

CI job `128156887548` failed with TS1117 at workflow-target.test.ts:22:
two independently landed master fixes declared the same `IMAGE_ONLY` and
`TESTOPS_REQUESTED` keys. The complete original log is retained. A
further normal master merge now removes the duplicate declarations,
retaining exactly one false/false pair and every original assertion. All
nine qualified acceptance code paths and the held-port repair remain
byte-identical; unrelated master changes are preserved. This is a
concrete compiler failure and source repair, with fresh exact-head
CI/reviews required.

---------

Co-authored-by: Paperclip <[email protected]>
2026-11-06 15:55:05 -06:00
DottaandClaude Opus 5.5 02a2299173 feat(codex): default Codex to gpt-6.1-sol, half the gpt-5.6-sol price (#19623)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Codex agents run through Paperclip Runner or the legacy
`codex_local` harness, and the runner E2E suites run paid Codex cells
> - The shared Codex default and many selections preferred `gpt-5.6-sol`
> - The reviewed OpenAI rate card in
`server/src/services/codex-pricing.ts` (`openai-standard-2026-09-30`)
prices `gpt-6.1-sol` at $2 input / $0.10 cached / $10 output per 1M
tokens, and `gpt-5.6-sol` at $4 / $0.40 / $20
> - So every default run and every paid Codex eval cell cost twice as
much as necessary
> - This pull request moves every place that prefers or selects
`gpt-5.6-sol` to `gpt-6.1-sol`, and keeps historical facts, rate cards
and compatibility fallbacks on `gpt-5.6-sol`
> - The benefit is half the token price for new default Codex agents and
for the Codex E2E and TestOps cells

## Linked Issues or Issue Description

**What existing behavior does this improve?**
The default Codex model for new agents and the Codex model that the
runner E2E suites and TestOps plans select.

**Subsystem affected**
Adapters (`adapter-utils`, `codex-local`), server native runtime
fallback, UI AI connection pools, Paperclip Runner devtools and live
evals, runner E2E catalog and TestOps mappings.

**Current behavior**
New Codex agents without an explicit model store `gpt-5.6-sol`. The
runner E2E `runner-codex` profile, the Reliability `queue-steer` plan
cell, the Product regression TestOps mapping and the ACPX Codex
qualification selection all use `gpt-5.6-sol`.

**Proposed behavior**
They use `gpt-6.1-sol`. Existing agents that store a model keep it.
`gpt-5.6-sol` stays selectable.

**Reason and benefit**
`gpt-6.1-sol` costs half as much per token on the in-repo reviewed rate
card. The current OpenAI pricing page (retrieved 2026-11-06) shows the
same short-context rates and also lists long-context rates of $4 / $0.20
/ $15 for `gpt-6.1-sol` against $8 / $0.80 / $30 for `gpt-5.6-sol`.

**Breaking changes**
None for stored configurations. See Risks.

## What Changed

Changed defaults and selections (all now `gpt-6.1-sol`):

- `PAPERCLIP_RUNNER_DEFAULT_MODELS.codex` in
`packages/adapter-utils/src/paperclip-runner-permissions.ts`. This also
changes `DEFAULT_CODEX_LOCAL_MODEL`,
`resolvePaperclipRunnerModel("codex", …)`, runner config normalization,
`codex_local` → Paperclip Runner conversion, the UI new-agent and
agent-config defaults, the composer default model and the public MCP
effort lookup.
- `codex-local` `models`: the 5.6 entry is now the literal
`gpt-5.6-sol`, because it used to be `DEFAULT_CODEX_LOCAL_MODEL`. The
list order and contents do not change.
- AI connection pool: a new OpenAI member profile uses
`PAPERCLIP_RUNNER_DEFAULT_MODELS.codex` instead of a hard-coded
`gpt-5.6-sol`.
- Paperclip Runner devtools ACPX Codex default and preset, the
`runner-codex-sol` local provider smoke, and the `acpx-codex-sol` live
workflow candidate.
- `tests/acpx-qualification-models.ts` Codex selection (runner E2E and
runner acceptance ACPX Codex profiles).
- Runner E2E: the `runner-codex` profile follows the default, so all
native Codex cells move. This includes the `runner-codex` cells among
the 52 standard `everyday-workflows` cells and the seven Codex
reliability cells. `runner-codex-mini` stays on `gpt-5.4-mini`. The
Reliability plan `queue-steer` `steeringProfile` is now
`['runner-codex', 'gpt-6.1-sol']`. Its executionId
(`everyday-workflows.runner-codex.daytona.queue-steer`), its fullName
and its TestOps case identity do not change. No separate profile is
necessary.
- `product-regression-testops.mjs`: the declared model for every
`runner-codex` case is now `gpt-6.1-sol` (it was already that for
`configured-repository`).
- `tests/runner-e2e/installed-cli.ts`: the credential-free installed
startup proof opens a session on the default model.
- `tests/runner-e2e/scheduled-prices.json`: add a `gpt-6.1-sol` price
(`openai-standard-2026-11-06`, including 30m/12h cache writes and
long-context rates from the OpenAI pricing page). The weekly scheduled
Codex cells need an exact price for their model, or admission fails
closed.
- Storybook: the composer model picker fixtures, the connection pool
stories and host, and the agent settings preview show `gpt-6.1-sol` as
the default.
- Docs: `doc/DEVELOPING.md` (the Codex conversion default) and
`tests/runner-e2e/EVERYDAY-WORKFLOWS.md` (local matrix, reliability
extras, `queue-steer` row).
- Tests that assert the default now expect `gpt-6.1-sol`. New
assertions: the default has the `0.159.0` CLI floor, the compatibility
fallback is floor-free, the `queue-steer` plan cell runs `runner-codex`
on `gpt-6.1-sol`, and all native Codex everyday cells (standard and
reliability) use `gpt-6.1-sol`.

Deliberately kept on `gpt-5.6-sol`:

- Rate cards: `server/src/services/codex-pricing.ts` and
`packages/paperclip-runner/src/evals/model-pricing.ts`. Old receipts
need them. (The runner eval rate card has no `gpt-6.1-sol` entry. An
unpriced candidate is reported as unpriced, not as zero. I did not add a
rate there because that card has its own version and source.)
- The remote Codex CLI compatibility fallback. A new
`CODEX_LOCAL_COMPATIBILITY_FALLBACK_MODEL = "gpt-5.6-sol"` replaces the
default in `codexModelFallbackCandidates`. `gpt-6.1-sol` needs Codex
0.159.0 or newer, so it cannot be the floor-free last resort. Fallback
behavior is unchanged.
- `tests/runner-e2e/provider-usage.ts`: the implicit request
cache-policy attribution stays qualified only for `gpt-5.6-sol`. I did
not verify that policy for `gpt-6.1-sol`. A `gpt-6.1-sol` response that
omits the write-TTL split keeps its cache-write cost unknown, which is
the existing behavior for unqualified models.
- `tests/runner-e2e/scheduled-measurements.json`: historical
`gpt-5.6-sol` measurements. They no longer match the current suite
definitions, so the forecast reports them as historical only. The
forecast tests now build their synthetic cohorts on the current
definition hash.
- Model catalogs that list `gpt-5.6-sol` as available (`codex-local`,
`cursor-local`, `opencode-local`, Speko voice diagnostics, Pi picker
fixture).
- The `gpt-5.6` → `gpt-5.6-sol` alias for old stored configs.
- Dated or evidence docs: `doc/plans/*`, `doc/adapter-model-audit-*`,
`QUALIFICATION-*`, `doc/connections/MEMORY.md`,
`doc/connections/TASK-REVIEWS-VERIFICATION.md`,
`tests/runner-e2e/CHAT-FEATURES.md`, the Slack eval evidence, and the
S49 soak signature text (the soak failure happened on `gpt-5.6-sol`).
- Test fixtures that parse, price, migrate or round-trip an existing
`gpt-5.6-sol` value (Rust runner tests, driver tests, pricing tests,
stored-config tests, stories that show an existing agent).

## Verification

- `npx vitest run` on every server, UI, adapter-utils and codex-local
test file that mentions `gpt-5.6-sol` or the default, plus all of
`packages/adapter-utils` and `packages/adapters/codex-local`: 5,483
passed. Three timeouts under load (`sandbox-managed-runtime`,
`acp.quota`) passed when rerun.
- `packages/paperclip-runner`: `vitest run` for `src/eval`, `src/evals`,
`src/drivers/acpx`, `src/drivers/codex`, `src/cli`, `src/contracts`,
`src/backends`: 2,641 passed.
- `node --test tests/runner-e2e/reliability-testops.test.mjs
tests/runner-e2e/product-regression-testops.test.mjs`: 28 passed.
- `node tests/runner-e2e/run-unit.mjs`: all model, catalog, reliability,
scheduled and TestOps tests pass. Failures remain only in
`copilot-protection-flow`, `denied-target-isolation-flow`,
`pi-controls-flow`, `workflow-target` and `unit-launcher`. These five
also fail on unmodified master in this local environment (filesystem
watch and git environment), and they do not touch models.
- `vitest run --config tests/runner-acceptance/vitest.config.ts`: 19
passed.
- `pnpm test:e2e:runner -- --list --suite everyday-workflows --group
reliability`: the seven `everyday-workflows.runner-codex.daytona.<case>`
cells show `gpt-6.1-sol` with `OPENAI_API_KEY,DAYTONA_API_KEY`.
`--matrix-json` gives 16 reliability cells with the same credentials as
before. `--list --all` shows no `gpt-5.6-sol` cell.
- Typecheck: `adapter-utils`, `adapter-codex-local`, `server`, `ui`,
`paperclip-runner` (TypeScript), and `pnpm test:e2e:runner:typecheck`.
- `pnpm check:token-gates`: clean.
- Not run: paid cells, Rust tests (no Rust change), the browser suites,
and the full `pnpm -r typecheck` / `pnpm build` (CI runs them).

## Risks

- Agents without an explicit model change model on their next save or
normalization, and Paperclip Runner rows with a blank model use
`gpt-6.1-sol` at execution. Stored models, including `gpt-5.6-sol`, are
never rewritten. No migration is necessary.
- `gpt-6.1-sol` needs Codex CLI 0.159.0 or newer with ChatGPT sign-in.
The runner pins `@openai/[email protected].0`. On a remote target with an
older image CLI, the existing compatibility check falls back to
`gpt-6-sol` and then `gpt-5.6-sol`, and that probe now runs for
default-model agents too. A legacy `codex_local` agent created with the
new default on a host with an older Codex CLI gets the existing
actionable "upgrade Codex" Test result.
- Qualification: the native Codex profile uses `adapter_constant` model
qualification, so it follows the default. No qualification gate is
dropped. The ACPX Codex qualification selection moves to `gpt-6.1-sol`.
Its next qualification run uses the new model. The provider-usage
cache-policy attribution stays limited to `gpt-5.6-sol`.
- Runner E2E history: the Codex cells change their suite definition
hashes, so earlier `gpt-5.6-sol` results are a separate cohort and do
not count as current.
- The `gpt-6.1-sol` scheduled price uses the file's existing `expiresAt`
(2026-11-19). It needs the same review as the other entries before then.
- No `.github/` change. The Reliability queue-steer cell keeps the
`runner-codex` profile id, so the existing workflow credential and setup
steps still match.

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

## Model Used

- Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, running in
Claude Code with tool use (shell, file edits, web fetch of the OpenAI
pricing page).

## 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: #19604 (commits the Reliability TestOps identities; this PR
changes only `steeringProfile` in the same file).

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

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 15:35:34 -06:00
DottaandPaperclip 6148fbd31f feat(ui): distinguish GitHub resources in inline Markdown links (#19476)
## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - Task conversations and documents use the shared Markdown renderer.
> - GitHub repository, pull request, and issue links currently use the
same icon.
> - Long pasted URLs make these references hard to scan in a
conversation.
> - This change identifies each resource from its URL and shows a
compact label with a distinct icon.
> - The exact destination and custom link text stay intact. Connected
status data keeps its existing behavior.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Inline GitHub links in rendered Markdown.

**Current behavior**

Repositories, pull requests, and issues all use the GitHub mark. Pasted
resource links show the full URL.

**Proposed behavior**

Pull requests use the pull request icon. Issues use the issue icon.
Repositories and other GitHub pages keep the brand mark. Pasted resource
URLs show `owner/repository#number` or `owner/repository`. Custom labels
keep their text and formatting.

**Reason and benefit**

Readers can identify GitHub resource types and scan references in a
narrow conversation. Full URLs, query strings, and comment anchors stay
available through the link destination. Link rendering makes no remote
metadata requests.

**Breaking changes**

The visible text of a bare GitHub resource URL becomes compact. The
destination does not change. No API, database, or connection changes are
required.

Searches found no duplicate work for GitHub inline resource
presentation. Related work: [same-origin Markdown
routing](https://github.com/paperclipai/paperclip/pull/17864). This
change preserves that route handling.

## What Changed

- Add a pure GitHub URL classifier with exact host checks and resource
labels.
- Move ordinary URL presentation into `MarkdownUrlLink`. Keep internal
references, workspace links, custom resolvers, and connected metadata in
their existing order.
- Add separate icons and accessible resource names. Preserve authored
Markdown titles and formatted labels.
- Add Playground, Narrow, and Streaming Storybook examples that mount
`MarkdownBody`.
- Add parser and renderer tests, eight Chromium browser cases, and the
browser command in the development guide.
- Consider Open Graph loading. Use the existing connected metadata path
for remote status and titles. Add no arbitrary page fetches.

## Verification

- Focused Vitest coverage: 129 passing tests across nine files for
GitHub parsing, Markdown rendering, connected metadata, document
rendering, and workspace-file rendering.
- `pnpm check:token-gates`.
- `pnpm --filter @paperclipai/ui typecheck`.
- `pnpm --filter @paperclipai/ui build`.
- `pnpm build-storybook`.
- Storybook catalog generation/check and 30 catalog/map tests.
- Site-route regression: Stars, Education, and enterprise pages keep
general GitHub branding and their full URL text. Three parser cases
failed before the fix; parser and renderer cases now pass.
- Streaming regression: three resource cases failed before the fix and
pass after it. The fix preserves the generated word span and custom
formatted labels.
- Chromium: `STORYBOOK_TEST_BASE_URL=http://localhost:6106 pnpm exec
playwright test inline-links.spec.ts
--config=tests/storybook-visual/playwright.config.ts --project=stories`.
- Browser scope: eight passing cases for ordinary/streaming modes,
dark/light at desktop and 390px mobile, completed play assertions, exact
comment destinations, safe external attributes, keyboard focus,
wrapping, and no GitHub requests. Screenshots are saved as task
artifacts; no review images are committed.
- The first browser attempt in the resumed environment could not launch
Chromium because its executable was missing. The browser was installed,
and all eight cases then passed.
- The browser cases use synthetic text and one connected metadata
fixture. They do not prove provider authentication or server
persistence. No visual baseline comparison is claimed; the repository
baseline manifest is unpublished.
- Initial typecheck found two unsupported story assertion options and an
unbuilt SDK dependency in the fresh checkout. The options were removed
and the SDK was built before the successful retry.
- `pnpm verify` returned 2 because the new browser spec requires broad
CI coverage. Focused tests and the browser suite supply local evidence.
Required CI supplies broad coverage.

## Risks

- Compact labels hide URL detail from the visible line. The exact href
and default tooltip preserve that detail.
- GitHub site routes can change. Unknown paths retain the general GitHub
treatment. Lookalike hosts and credential-bearing URLs do not get GitHub
resource treatment.
- Connected external-object status and title rendering keeps priority
over URL-derived presentation.

## Model Used

OpenAI Codex, GPT-6 (`gpt-6` family), with reasoning, tool use, browser
checks, and code execution. The exact deployment ID and context window
are not exposed in this run.

## Checklist

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

Agent worktrees each built the Runner from nothing: a cold cargo debug and
release build, the Runner TypeScript build and its checks, about 8 minutes
per fresh worktree on a loaded machine.

`pnpm agent:preflight` replaces the manual install + Runner build sequence.
It skips steps whose inputs are unchanged and reuses other worktrees'
results from ~/.cache/paperclip/agent-build:

- Runner and eval-kernel dist, keyed by the Runner build-input digest and
  the toolchain
- Runner debug binaries, keyed by every file the Rust build reads
- a cargo seed with third-party crates only; the Runner crate is scrubbed
  because cargo records its out-of-package includes by absolute path

Entries are written once with an atomic rename and restored as APFS clones.

Also exclude provider-assets from the Runner build inputs. The build writes
that directory itself, so the first build in every fresh checkout saw its
inputs change and never recorded a build stamp.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 14:01:54 -06:00
Nicky LeachandClaude Opus 5.5 7dfdc97b9f fix(deps): let Dependabot update the sandbox providers (#19489)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandbox providers (`packages/plugins/sandbox-providers/*`) are
standalone published packages. `pnpm-workspace.yaml` keeps them out of
the root workspace on purpose.
> - `.github/dependabot.yml` watches only `/`, so Dependabot never saw
the providers.
> - Because of this, the Kubernetes provider stayed on zod 3
(GHSA-mrhx-c7xc-m3w5) after the rest of the repo moved to zod `^4.6.6`.
#18839 fixed it by hand.
> - We do not want to keep these versions current by hand in many
places.
> - This pull request lets Dependabot update the providers, and keeps
each provider standalone.
> - The benefit is that a provider dependency that falls behind gets a
pull request that can pass CI.

## Linked Issues or Issue Description

Refs #18839 (adds `scripts/typecheck-sandbox-providers` and fixes
Kubernetes zod by hand).
Refs #18998 (a Dependabot pull request for the Pi distribution that
fails CI by design).

**What existing behavior does this improve?**
Dependabot version updates. They skip every package that the root pnpm
workspace excludes.

**Subsystem affected**
CI and dependency automation: `.github/dependabot.yml`,
`scripts/typecheck-sandbox-providers`.

**Current behavior**
Dependabot does not update the sandbox providers. A provider can stay on
an old or unsafe version, and nobody gets a pull request.

**Proposed behavior**
Dependabot updates the providers each week. Each pull request changes
the provider manifests and the lockfiles that CI installs frozen, so the
pull request can pass CI.

**Reason and benefit**
We stop having to find and fix old provider dependencies by hand.

## What Changed

- `.github/dependabot.yml`: add an npm entry for
`/scripts/typecheck-sandbox-providers`. Dependabot treats this workspace
as one unit. One pull request per dependency changes every provider that
uses the dependency and the shared `pnpm-lock.yaml` together. So the
entry needs no `groups`. One entry per provider would open a separate
pull request for each provider, and each one would fail the frozen
install in CI.
- `.github/dependabot.yml`: add an npm entry for
`/packages/plugins/sandbox-providers/daytona`. Daytona is the only
provider with its own lockfile, and the release build and the Daytona
end-to-end job install it frozen. Each lockfile has exactly one
Dependabot entry, so every pull request changes a manifest and its
lockfile together.
- Take Daytona out of the shared typecheck workspace. This pull request
does not commit `scripts/typecheck-sandbox-providers/pnpm-lock.yaml`:
since #19522, only the lockfile refresh workflow writes it. After merge,
the bot removes Daytona's packages from it (no other version changes;
checked with pnpm 9.15.4). Until then CI resolves it in place.
`scripts/typecheck-test-programs.mjs` now installs Daytona from its own
lockfile (`LOCKED_PROVIDERS`, with `--ignore-workspace`). A stale
lockfile is resolved in place for the run, for both installs.
- `.github/dependabot.yml`: add a comment that tells why the Pi and Kimi
runtime distributions have no entry (see Risks).
- `scripts/typecheck-sandbox-providers/pnpm-workspace.yaml`: list each
provider by its full path. Dependabot does not expand a workspace glob
that starts with `../`, so with the glob it saw no providers.
- `scripts/typecheck-test-programs.test.mjs`: add tests. They check that
the workspace list has no glob and matches `SANDBOX_PROVIDERS` without
`LOCKED_PROVIDERS`, that each locked provider has its lockfile, that a
locked provider installs frozen with `--ignore-workspace`, and that a
stale locked-provider lockfile is resolved in place.
- Add `.npmrc` (`auto-install-peers=true`) in
`scripts/typecheck-sandbox-providers` and in the Daytona provider.
Without it, Dependabot reads the root `.npmrc` (`false`). That rewrites
the lockfile settings, and the frozen install then fails. npm and pnpm
never publish `.npmrc`.
- `scripts/typecheck-sandbox-providers/package.json`: add
`"packageManager": "[email protected].4"`, the version CI uses. Without it,
Dependabot guesses pnpm 10.
- `doc/DEVELOPING.md`: say that contributors do not refresh or commit
Daytona's lockfile either, and that a new provider goes in both lists.

No provider moves into the root workspace. There is no pnpm catalog. The
root `pnpm-lock.yaml` does not change.

## Verification

I ran the real Dependabot updater on this branch with the Dependabot CLI
(`dependabot update ... --local`, updater image
`dependabot-updater-npm@sha256:c5a9451c…`):

- Before the change, the workspace entry loaded only
`cloudflare/bridge-template` (the one path without a glob). It skipped
all the other providers.
- After the change, `--dep typescript` gave one pull request. It changed
the 9 non-Daytona provider `package.json` files and the shared
`pnpm-lock.yaml`, and did not touch Daytona.
- `--dep wrangler` changed only the wrangler closure in the shared
lockfile. Before the `.npmrc` fix, the same run changed
`autoInstallPeers` to `false` and 757 lockfile lines.
- The Daytona entry (`--dep @daytonaio/sdk`) changed Daytona's
`package.json` and its own lockfile in one pull request, and kept
`autoInstallPeers: true`. It keeps the exact pin exact: `0.203.0`
becomes `0.227.3`.
- Both frozen installs (shared workspace, and Daytona with
`--ignore-workspace`) pass with pnpm 9.15.4.
- Dependabot installed `[email protected].4` from the new `packageManager`
field.

Other checks:

- `.github/dependabot.yml` passes the SchemaStore `dependabot-2.0`
schema (Ajv). A wrong key fails the same check.
- With pnpm 9.15.4, `install --resolution-only --ignore-scripts
--no-frozen-lockfile` in `scripts/typecheck-sandbox-providers` (the
lockfile bot's command) gives exactly the lockfile that passed the
frozen installs earlier in this pull request.
- The lockfile rule from #19522 allows this pull request's diff: it
changes no lock content.
- `node --test scripts/typecheck-test-programs.test.mjs`: the new test
and the provider tests pass. Two other tests need a root `node_modules`
(`tsc`), which my worktree did not have. CI runs them.

These checks cannot run before merge. GitHub reads `dependabot.yml` only
from the default branch. After merge, open Insights → Dependency graph →
Dependabot and make sure that the two new entries show no errors and run
on Monday.

## Risks

- A dependency in both a non-Daytona provider and Daytona (for example
`typescript`) gets two pull requests: one from each entry. Each one
passes alone, and they can merge in any order.
- The typecheck job now typechecks Daytona's tests against Daytona's own
lockfile instead of the shared one. Its versions are a little older (for
example vitest 4.1.11), so a type difference can show up there.
- Majors of shared dev tools (for example TypeScript 7, Vitest 5) come
as one pull request across all providers. It can need code changes
before it passes. That is the same as the root entry.
- Pi and Kimi get no version updates. Every direct dependency there is a
pinned provider release, and the pins feed the provider's ACPX
`commandDigest`. The Runner checks compare the Pi lock with
`PI_DISTRIBUTION_PINS`, and the Kimi build compares the lock SHA-256
with a pin. So any Dependabot change there fails CI (see #18998), and a
real upgrade starts saved sessions fresh. GitHub security alerts still
cover both lockfiles.
- The Pi zod `3.25.76` pin is still affected by GHSA-mrhx-c7xc-m3w5
(open alert). It is deliberate: `[email protected]` has `zod ^3.25.0` as a
direct dependency, not a peer. A fix needs a `pi-acp` release that
allows zod 4. This pull request does not change it.
- `packages/plugins/examples/plugin-orchestration-smoke-example` is also
outside the root workspace. It uses `workspace:*` dependencies, so it
cannot install alone, and this pull request does not cover it.

## Model Used

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

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 11:08:39 -08:00
DottaandClaude Opus 5.5 7afb57c022 test(runner-e2e): repair restart-cell harness drift (#19538)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Runner Product E2E suite proves that agent work survives a
control-plane restart
> - A restart chaos campaign found three restart cells that fail in the
test harness, not in the product
> - A harness failure hides the real restart result, so the campaign
cannot classify these cells
> - This pull request repairs the harness drift in those cells and names
the one failure the harness cannot prevent
> - The benefit is that these cells now test restart behavior, or fail
with a clear cause

## Linked Issues or Issue Description

Refs #14811 (connector setup became one screen). Refs #19475
(completion-update restart fixture).

**What happened?**
1. `lifecycle-baseline.runner-codex.local.tool-review-restart` clicked a
"Save and continue" button. #14811 removed that button. The cell failed
during setup. After that fix, the cell also failed after the restart:
the expanded review card does not survive the post-restart reload, so
"Approve & run" was not visible.
2.
`local-session-integrity.runner-acpx-claude.local.structured-question-restart-resume`
failed with `page.goto: net::ERR_ABORTED` after the controller restart.
The old document's Vite client reloads when the replacement server
starts, and this aborts the test navigation. The code tolerated only
`TimeoutError`.
3.
`completion-updates.runner-acpx-claude.local.handoff-completion-restart`
waited for the source agent to run an instructed wait command. Claude
replied without the command (2/2 attempts), so the restart never
occurred. The cell then failed with a generic timeout.

**Expected behavior**
Each cell reaches its restart boundary and tests restart behavior. If
the boundary cannot be reached, the cell names the cause.

**Steps to reproduce**
Run `pnpm test:e2e:runner -- --max-parallel 1 --max-automatic-retries 0
--id <cell>` for each cell above on `master`.

## What Changed

- `tests/runner-e2e/connection-reviews.ts`: accept the one-screen "Check
link" step and the older "Save and continue" step. This uses the same
regex approach as `connection-flow.ts`.
- `tests/runner-e2e/runner.spec.ts` (tool review): click "Review
request" after the restart reload, on the document that acts on the
card.
- `tests/runner-e2e/chat-restart.ts`: add `gotoAfterServerRestart`. It
retries only `net::ERR_ABORTED`, within the caller's deadline and at
most 5 attempts. It still accepts `TimeoutError`. It rethrows all other
errors. The structured-question restart uses it. The existing sentinel
and durable-state assertions still prove the new document.
- `tests/runner-e2e/completion-updates.ts` and
`completion-update-flow.ts`: add `completionRestartGateSkipped` and
`completionRestartGateTimeoutDetail`. The gate poll stops at once with
`restart boundary not reached (agent skipped gate)` when no completion
turn is active and the turn that carries this task's Done facts
(`chatCompletionUpdates`, as in `completionDelivery`) succeeded or
published its reply before the gate reports `waiting`. A failed or
cancelled turn is not blamed on the agent, because delivery can retry
it; the timeout detail names it instead. The post-restart chat
navigation now uses `gotoAfterServerRestart`.
- A fixture-level gate is not possible without a product hook. The
native run finalizer publishes the completion reply inside its own
transaction, through the issue service closure. The isolated-server
entrypoint can patch only class prototypes and HTTP responses, and the
reply uses no tool call or HTTP request.
- `tests/runner-e2e/README.md`: document the skipped-gate failure.
- Docs: use `pnpm --filter @paperclipai/paperclip-runner run
build:runner-binaries` in `tests/runner-e2e/README.md`,
`tests/restart-chaos/README.md`, `tests/restart-chaos/main.ts` and
`doc/DEVELOPING.md`.
- No product code changes.

## Verification

- Unit: `pnpm exec vitest run --config tests/runner-e2e/vitest.config.ts
tests/runner-e2e/chat-restart.test.ts
tests/runner-e2e/completion-updates.test.ts
tests/runner-e2e/connection-reviews.test.ts` (36 passed).
- Typecheck: `node scripts/typecheck-test-projects.mjs tests/runner-e2e`
passed.
- Live cells, local, `--max-parallel 1 --max-automatic-retries 0`,
macOS, load average 35–65:
- `lifecycle-baseline.runner-codex.local.tool-review-restart`: PASS
(first run found the review-card reload drift, fixed in the second
commit).
-
`local-session-integrity.runner-acpx-claude.local.structured-question-restart-resume`:
PASS. A first attempt failed before the restart with `PRP command
session.open timed out` at load ~36 (environment).
-
`completion-updates.runner-acpx-claude.local.handoff-completion-restart`:
FAIL as designed, twice (before and after the review fix), with `restart
boundary not reached (agent skipped gate): completion turn … ended
succeeded without the reference gate reporting waiting`. Claude skipped
the wait both times, so this cell still does not exercise a restart.

## Risks

- Low risk. Test harness and docs only.
- The navigation retry could hide a real abort. It is limited to
`net::ERR_ABORTED`, 5 attempts and the deadline, and the test still
asserts that a new document loaded.
- The ACPX Claude completion restart cell still does not exercise a
restart when the agent skips the gate. It now reports this cause by
name.

## Model Used

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

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 12:59:54 -06:00
Devin FoleyandPaperclip fd4b197f83 fix(ui): hide runner selection during agent setup (#19517)
## Thinking Path

> - Paperclip lets people manage AI agents for work.
> - Agent setup connects a harness, model, account, and computer.
> - The system already selects a qualified runner for a new agent.
> - Setup still asks users to select that internal runner.
> - The runner dropdown can be the only field in Advanced.
> - This PR removes the dropdown and hides an empty Advanced section.
> - Users keep their saved execution settings and see fewer setup controls.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Agent creation, onboarding, built-in agent setup, company import, and agent configuration.

**Subsystem affected**

The UI, setup error messages, tests, and Storybook fixtures.

**Current behavior**

Users can select a runner from a dropdown. Some setup screens place only that dropdown in Advanced. Errors can direct users to that control.

**Proposed behavior**

Remove runner dropdowns from setup and configuration. Use the existing automatic selection for new agents. Retain saved runner settings. Show Advanced only when other fields remain.

**Reason and benefit**

Users can configure agents without an extra internal execution choice. Setup also avoids an empty Advanced section.

**Breaking changes**

Users can no longer select a runner through these UI dropdowns. The API and CLI retain their runner options. Existing agents keep their saved execution settings.

**Additional context**

Related: [Default new agents to the qualified runner for their harness](https://github.com/paperclipai/paperclip/pull/15422). This PR changes the existing setup UI. It adds no harness or runtime default. The master merge retains the custom gateway placement from [#19510](https://github.com/paperclipai/paperclip/pull/19510).

## What Changed

- Remove the shared runner dropdown and its setup/configuration callers.
- Hide Advanced when it has no remaining fields. Keep command and argument settings where supported. Preserve custom gateway setup under Advanced on the Claude and OpenAI API key paths.
- Keep setup failures visible and keep the retry action outside the removed control.
- Update errors and documentation that referred to the removed dropdown.
- Update component tests and Storybook fixtures for the new UI states.
- Make Legacy first-task evals resume an API-configured setup record. Record that path separately from fresh automatic onboarding.

## Verification

- Latest master integration (`187c0f6d6e5`): 276 onboarding/provider-connection tests passed across six files. UI typecheck and build passed. Token gates, query-error rendering checks, and the Storybook catalog check passed. The conflict resolution retains master's bounded connection tests and cancellation while removing the obsolete runner instruction. The first test command used the wrong working directory and failed before collecting tests; the corrected invocation from `ui/` passed.
- Earlier branch verification passed: full workspace build and typecheck, 1,037 focused UI/service tests, 49 heartbeat provider contract tests, eval typecheck, and 30 map/catalog tests.
- The full local `pnpm test:run` run passed all six chat integration shards (1,931 tests). It was stopped for the master merge before the complete suite finished. The [complete CI run](https://github.com/paperclipai/paperclip/actions/runs/42264662642) passed on the previous head `72e1699`.
- Current head `a097c8369d079c839e78661a2206cfa62c5210e0`: [full CI](https://github.com/paperclipai/paperclip/actions/runs/42273168268) passed. PR checks total 55 passed and 3 skipped, with none failed or pending. Greptile reviewed this head at 5/5 with no actionable findings or unresolved review threads.
- Live browser/provider evals have not run. The Legacy eval change has no live qualification claim.

## Risks

- Removing the dropdown changes the setup UI. Existing API and CLI runner choices remain available.
- Conditional Advanced sections must retain valid command and argument fields.
- Legacy first-task evals now prove setup resumption. They do not prove the fresh-agent automatic default.
- No schema change or data migration is included.

## Model Used

OpenAI `gpt-6.1-sol`, through Codex in T3 Code. Reasoning effort: `xhigh`. The model used file editing, shell commands, and test tools. The harness 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 <[email protected]>
2026-11-06 10:58:42 -08:00
Nicky LeachandClaude Opus 5.5 3b3037aab5 fix(ci): run the sandbox provider tests in required CI (#19533)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandbox provider plugins (Daytona, E2B, Cloudflare, Kubernetes,
Modal, and others) run agents in remote sandboxes
> - The providers are outside the root pnpm workspace, so `pnpm
test:run` and the CI test lanes never run their Vitest suites
> - paperclipai/paperclip#18839 typechecks those tests, but a test that
fails at runtime still fails nothing
> - The providers have about 1,040 tests, and they finish in about 15
seconds on Linux
> - This pull request adds a required `Sandbox provider tests` job that
runs them when a change can affect them
> - The benefit is that a broken provider, plugin SDK, or shared
contract fails CI on the pull request instead of in a release or a paid
end-to-end run

## Linked Issues or Issue Description

Refs #18839

**What happened?**

No CI job runs the sandbox provider tests. `runner-full-stack-e2e.yml`
builds the Daytona plugin for a paid nightly run, but it does not run
any provider's `pnpm test`.

**Expected behavior**

A pull request that can break a provider runs that provider's tests, and
a failure blocks `ci / verify`.

**Steps to reproduce**

Add `expect(1).toBe(2)` to a test in
`packages/plugins/sandbox-providers/e2b/src`. Open a pull request. CI
stays green.

## What Changed

- Added `scripts/test-sandbox-providers.mjs` and its tests. The script
installs the providers through `typecheckTestPrograms` with no programs,
because that function already owns the provider install and the plugin
SDK link. Then it runs each provider's `pnpm test`, four at a time, and
prints each provider's output as one block. It fails if a suite fails or
if no provider has a test script.
- The script skips `cloudflare/bridge-template`, because Cloudflare's
Vitest config already runs those tests.
- Added the `sandbox_provider_tests` job to
`.github/workflows/pr-trusted.yml`, and made `ci / verify` require its
result.
- Added the `//#ci:sandbox-providers` task to `turbo.json` and its lane
to `.github/ci-task-matrix.json`. The CI planner runs the job only when
a change touches `packages/plugins/sandbox-providers/**`,
`packages/plugins/sdk/**`, or `packages/shared/**`. `packages/shared` is
the plugin SDK's only workspace dependency. Global CI inputs, such as
`scripts/**`, `.github/**`, `pnpm-lock.yaml`, or any `package.json`,
still select every job.
- The job turns off core dumps. A Daytona capacity test kills a process
at a file-size limit inside the exported directory. If core dumps are
on, the core file joins the export, and the test sees 12 files instead
of 11.
- Updated the job lists in `scripts/__tests__/e2e-shard.test.mjs` and
the job count in `.github/scripts/tests/ci-profile.test.mjs`. Added
planner tests to `.github/scripts/tests/ci-plan.test.mjs`. One of these
tests fails if the plugin SDK gets a new workspace dependency that is
not in the job's inputs.
- Added the new test file to `test:release-registry`, and documented the
script in `doc/DEVELOPING.md`.

## Verification

- `node scripts/test-sandbox-providers.mjs` on macOS: 8 of 9 suites pass
in 31.6 s, including the install. Kubernetes fails 12 `file-sync` tests,
because macOS has no `/proc/self/fd`. Some Daytona tests skip, because
they need GNU `tar` and coreutils.
- The same script in a Linux container (`node:24`, a non-root user, core
dumps off): all 9 suites pass, with 1,012 tests passed and 26 skipped.
The script took 15 s after the install.
  - Kubernetes: 205 passed.
  - Daytona: 590 passed and 16 skipped.
- With core dumps on, Daytona's `preserve literal capacity repair` test
fails. The export includes a `core` file. With `--ulimit core=0`, it
passes.
- To look for timing failures under load, I ran the whole script 12 more
times in the container, limited to 2 CPUs (8 runs) and 1 CPU (4 runs).
All 12 runs passed. They took 45 to 76 s on 2 CPUs and 73 to 102 s on 1
CPU.
- `ubuntu-latest` has `zstd`, and my container did not, so some Daytona
tests skipped in those runs. With `zstd` installed, Daytona passed with
598 tests and 8 skipped, and only the live tests skipped. One earlier
`zstd` run failed while the stress runs used the same CPUs, and I did
not capture its output. The next run passed.
- `node --test` on every test that reads `pr-trusted.yml`, the CI
planner, or the task matrix: 816 passed. These include
`.github/scripts/tests/*.test.mjs`,
`scripts/__tests__/e2e-shard.test.mjs`,
`scripts/ci/test-selection-workflow.test.mjs`, and the new tests.
- `pnpm run test:release-registry` on macOS: 251 of 255 pass, including
the new tests. The 4 failures are `wait_for_npm_package_versions` tests
in `scripts/release-lib.test.mjs`. They fail the same way on master on
the same machine, and CI runs this suite on Linux.
- `actionlint` 1.7.7 reports no errors on the changed workflow.
- I called `planTasks` from `.github/scripts/ci-plan.mjs` with one
changed file at a time. `server/src/index.ts` and `ui/src/App.tsx` leave
`sandbox_provider_tests` off.
`packages/plugins/sandbox-providers/e2b/src/plugin.ts` and
`packages/shared/src/index.ts` turn it on.

This pull request cannot run its own new job. `pr.yml` calls
`pr-trusted.yml@master`, so the job first runs on pull requests after
this one merges. After merge, check the first pull request that changes
a provider or `packages/shared`.

## Risks

- **First real run is after merge.** I measured the time on Linux in
Docker, not on a CI runner. CI runners are about 2 times slower than my
machine, so I expect about 2 to 3 minutes for the job, including the
dependency restore. The job runs in parallel with the other jobs, and
the slowest required job takes about 13 minutes. So the job should not
make CI slower.
- **Open provider pull requests.** After this merges, open pull requests
that change a provider run its tests. If those tests fail, `ci / verify`
blocks them until they are fixed.
- **`packages/shared` changes often.** Any change there runs this job. A
Linux-only provider failure would block those pull requests. All suites
pass on Linux today.
- **One unexplained Daytona failure.** One Daytona run with `zstd`
failed under CPU contention, and I did not capture its output. Some
Daytona tests have short deadlines. If the job is flaky after merge,
quarantine the failing test or revert the job, and do not leave `ci /
verify` red.
- **Path rules can be too narrow.** If a provider starts to import
another workspace package, a change to that package does not run this
job. The plugin SDK dependency test covers the SDK. It does not cover a
provider that imports a workspace package directly. Today no provider
does.
- **Overlap with paperclipai/paperclip#19489 and
paperclipai/paperclip#19522.** Both pull requests change the provider
install in `scripts/typecheck-test-programs.mjs`. This pull request does
not edit that file. It calls the same install function, so it uses their
install changes when they merge. All three pull requests edit the same
section of `doc/DEVELOPING.md`, so a small text conflict is possible.

> 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, GitHub CLI, Docker). Extended reasoning was
on.

## Checklist

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

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

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 10:34:03 -08:00
Nicky LeachandClaude Opus 5.5 e3041f5481 fix(ci): let the lockfile bot own the sandbox provider lockfiles (#19522)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The repository has a supply-chain rule: contributors do not commit
lockfiles. A bot and verified Dependabot commits write them.
> - The rule checks only the root `pnpm-lock.yaml`.
> - The sandbox providers are outside the root workspace, so they have
two more pnpm lockfiles. Pull requests edited them by hand, for example
#12791, #18839 and #19489.
> - This pull request makes the same rule and the same bot cover those
two lockfiles.
> - The benefit is one lockfile rule for every managed pnpm lockfile,
and no lockfile edits in contributor pull requests.

## Linked Issues or Issue Description

Refs #19489

**What existing behavior does this improve?**

The lockfile policy and the lockfile refresh bot.

**Subsystem affected**

CI: `.github/scripts/lockfile-trust.mjs`,
`.github/workflows/refresh-lockfile.yml`, and the sandbox provider
installs.

**Current behavior**

`lockfile-trust.mjs` blocks contributor edits to `pnpm-lock.yaml` only.
A contributor can edit
`scripts/typecheck-sandbox-providers/pnpm-lock.yaml` or
`packages/plugins/sandbox-providers/daytona/pnpm-lock.yaml`, and CI
passes. The refresh bot does not update these files.
`typecheck:test-programs` fails on a stale provider lockfile and tells
the contributor to commit a new one.

**Proposed behavior**

The policy blocks contributor edits to all three lockfiles. The refresh
bot regenerates all three after merge. CI resolves a stale provider
lockfile in place for the run, as it does for the root lockfile.

**Reason and benefit**

Lockfile content comes only from the bot or from verified Dependabot
commits. Contributors do not need to run pnpm in special folders.

**Breaking changes**

None. A pull request that edits a provider lockfile now fails the policy
check. Remove the lockfile change from the pull request. The bot commits
it after merge.

## What Changed

- `lockfile-trust.mjs` now exports `LOCKFILES`: the root lockfile, the
shared sandbox provider lockfile, and the Daytona lockfile. The policy
blocks contributor edits to each of them. A Dependabot pull request must
match a verified Dependabot commit for each lockfile that it changes.
The `merged` command and the reporting-lock proof still use only the
root lockfile.
- `check-pr-lockfile.mjs` (advisory check) uses the same list.
- `refresh-lockfile.yml`:
- A job-level `LOCKFILES` env lists the same three files. A test checks
that it equals the policy list.
- A new step regenerates the two provider lockfiles with
`--resolution-only --ignore-scripts --no-frozen-lockfile`.
- The unexpected-changes check, the change check, and `git add` cover
all three lockfiles.
  - The release-age gate checks the added versions in every lockfile.
- A held refresh saves all three lockfiles at their repository paths,
with a `source-locks.sha256` file. The next run reuses them only if
every master lockfile is unchanged. An artifact in the old format is not
reused.
  - The PR title is now `chore(lockfile): refresh pnpm lockfiles`.
- `check-lockfile-release-age.mjs` accepts more than one
`--base`/`--head` pair.
- `scripts/typecheck-test-programs.mjs`: if the frozen install of the
provider workspace fails, the script resolves the lockfile in place and
installs frozen again.
- `scripts/build-standalone-public-packages.mjs`: the same fallback
applies to a package with a committed lockfile (Daytona). `release.sh`
already restores tracked files when it exits.
- `doc/DEVELOPING.md` and `doc/RELEASE-AUTOMATION-SETUP.md` describe the
managed lockfiles. They also say that the Pi and Kimi distribution
lockfiles stay hand-managed, because they hold attested provider pins.

## Verification

- `node --test .github/scripts/tests/*.test.mjs
scripts/__tests__/build-standalone-concurrency.test.mjs`: 731 tests
pass. New tests cover:
- the policy for provider lockfiles, including a real git history with a
Dependabot commit and a later hand edit
  - the workflow and policy lists match
  - the unexpected-changes check, run with bash against a fixture
- a held refresh round trip, and its invalidation when a provider
lockfile changes on master
  - the release-age gate across lockfile pairs
  - the inline fallback in both install scripts
- `node --test scripts/typecheck-test-programs.test.mjs`: the install
tests pass. Two tests fail locally only because this worktree has no
root `node_modules` (no `tsc`). They also fail without this change.
- I ran the new bot commands on master with pnpm 9.15.4. No package
version changed. pnpm 9.15.4 removes `libc:` fields and adds
`deprecated:` notes, so the first refresh is a metadata-only change. The
release-age gate passes it because it adds no versions.
- I ran the release-age and held-lock steps locally with bash against
this commit. The gate reported `eligible=true`, and `sha256sum --check`
accepted the saved hashes.
- The workflow YAML parses, and `bash -n` accepts every `run` block.
- Not testable before merge: the policy job loads `lockfile-trust.mjs`
from the base branch, so the new rule applies only to pull requests
opened after this merges. The refresh workflow runs only on master.

## Risks

- The release build now resolves a stale Daytona lockfile in place, from
the registry. The root release install already resolves without a frozen
lockfile, so this is the same level of trust.
- The paid runner e2e job still installs Daytona with
`--frozen-lockfile`. After a Daytona manifest change merges, its Daytona
cases fail until the bot's refresh merges. This matches the rule that
paid jobs refuse a new root lockfile until its refresh merges.
- Dependabot pull requests for the provider entries in #19489 change a
provider lockfile. They pass only if each lockfile matches a verified
Dependabot commit, the same as for the root lockfile.
- Low risk otherwise: the bot keeps its existing token, environment, and
merge rules.

## Model Used

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

## Checklist

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

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

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 10:07:57 -08:00
Nicky LeachandClaude Opus 5.5 98ebeefbc9 feat(environments): let managed config declare plugin environments (#19508)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A managed instance gets its instance-level environments from the
`PAPERCLIP_MANAGED_CONFIG` document
> - #19490 added the plugin environment rows, their marker index and
write floor, but nothing creates them yet
> - The managed config still accepts only one sandbox entry, so a
managed instance cannot get a plugin-provided environment without manual
setup
> - This pull request lets the managed config declare plugin entries
beside the sandbox entry, and provisions one locked row for each at boot
> - The benefit is that a managed instance gets its plugin environments
with no manual setup, and the rows recover by themselves when their
plugin restarts

## Linked Issues or Issue Description

Refs #19490 (the plugin environment rows this pull request provisions)

**Subsystem affected**
Server environments: the managed-config parser, the managed environment
bootstrap, and the environment routes' write floor.

**Problem or motivation**
A managed instance cannot get a plugin environment from its managed
config. The `environments` section accepts one entry, and that entry
always provisions the managed sandbox. Company settings cannot create a
plugin environment either. So each managed instance that needs one
requires manual setup.

**Proposed solution**
Let an `environments` entry declare `driver: "plugin"`. Boot provisions
one locked row for each plugin entry through the row layer from #19490.
A later pull request adds a one-time instance default for one plugin
entry, and advertises the new managed-config capability.

**Alternatives considered**
Manual setup on each instance, and a new control-plane endpoint. Both
need a step outside the deploy. The managed config already delivers the
sandbox entry.

**Roadmap alignment**
This extends the existing managed-config `environments` section, which
already provisions the managed sandbox. It adds no new product surface.

## What Changed

- `server/src/services/managed-config.ts`: an `environments` entry may
set `driver` to `"sandbox"` or `"plugin"`. An entry without `driver` is
still the sandbox entry, so today's documents parse unchanged.
- A plugin entry has `name`, optional `description`, `pluginKey` (the
plugin's manifest id), `driverKey` and optional `driverConfig`.
- At most one sandbox entry. Names are unique across the section, and
`pluginKey`–`driverKey` pairs are unique. Secret-like keys in
`driverConfig` refuse startup, as for sandbox `config`. An unknown key
or driver still refuses startup, and that includes `instanceDefault`
until the follow-up pull request.
- Plugin entries parse into a new `pluginEnvironments` list. The
`environments` list keeps only the sandbox entry, so its consumers do
not change.
  - `MANAGED_ENVIRONMENT_DRIVERS` lists the accepted drivers.
- `server/src/services/managed-environments.ts`
(`applyManagedEnvironments`):
- Boot refuses to start unless an auto-installed catalog entry installs
each plugin entry's `pluginKey`. `plugins.autoInstall` holds catalog
keys, not manifest ids, so the parser cannot check this. Boot uses the
manifest ids `createApp` already resolves from the bundled and
distribution catalogs (`app.locals.managedBundledPluginKeys`, passed in
by `server/src/index.ts`). Without that list it resolves the catalog
itself.
- Plugin entries run after the sandbox entry and reuse its readiness
handling. Boot awaits `pluginsReady`, then requires the plugin `ready`,
declaring the entry's driver, and running a worker. Otherwise boot
archives the row and re-ensures it on the worker's `ready`.
- Recovery re-ensures on every worker `ready` until one ensure succeeds
or the plugin rejects the config (HTTP 422).
- If the ensure fails because the worker died, boot archives the row and
waits for `ready`. If the worker is still running (for example the call
timed out), boot keeps the row as it is, retries once at once and on the
next `ready`, and otherwise at the next boot. No timer is added.
  - Removing an entry never archives its row.
- A plugin-only document does not conflict with
`PAPERCLIP_EXECUTION_MODE`, because plugin rows do not use the sandbox
slot.
- Plugin entries never change the instance default. The sandbox stamp
still fills only an unset or local default, so it never replaces a
plugin default.
- `server/src/routes/environments.ts`: `isPlatformSlotEnvironment`
treats a marked plugin row as a live slot while the document declares
its entry. So the marker-clear escape hatch is refused then, and allowed
again after the entry leaves the document.
- `doc/DEVELOPING.md`: new "Managed environments" section.

## Verification

- `cd server && npx vitest run src/__tests__/managed-config.test.ts`:
old documents parse unchanged. Sandbox plus plugin parses. Each of these
refuses startup: two sandbox entries, `driver: null`, duplicate names, a
duplicate pair, a secret-like key, an unknown key (including
`instanceDefault`), an unknown driver, a malformed `driverKey` or
`driverConfig`.
- `npx vitest run
src/services/managed-environments-plugin-entries.test.ts` (the
sandbox-entry tests stay in `managed-environments.test.ts`; shared
fixtures are in `managed-environments.test-support.ts`):
- A plugin entry is ensured with the validator's worker manager, after
the sandbox entry.
- Boot refuses a plugin entry whose `pluginKey` no auto-installed
catalog entry installs, both from the passed list and from its own
catalog lookup.
- A plugin that is down archives the row, and worker recovery re-ensures
it. A missing plugin, a plugin without the driver, or a worker manager
without `call` counts as unavailable.
- Recovery stays armed after a failed attempt and stops after one
success or a permanent config rejection (422). A `ready` during an
attempt queues one more.
- A worker that dies during the boot ensure archives the row and arms
recovery. A failure while the worker keeps running leaves the row as it
is and retries.
- Removing an entry does not archive. Plugin entries and the sandbox
stamp never change a plugin default.
  - An ensure failure is counted, not thrown.
- `npx vitest run src/__tests__/managed-plugin-environments.test.ts`
(embedded Postgres, real `applyManagedEnvironments`): the first boot
creates the row and the second changes nothing. A plugin that is down
archives the row, and recovery brings it back. Removing the entry does
not archive the row. The instance default stays unset. The row tests
from #19490 still pass.
- `npx vitest run src/__tests__/environment-routes.test.ts`: the marker
clear is refused while the document declares the entry, and allowed
after it leaves.
- `npx vitest run src/__tests__/instance-settings-service.test.ts
src/__tests__/instance-settings-cloud-defaults.test.ts`: pass with the
new `pluginEnvironments` field.
- `pnpm --filter @paperclipai/server typecheck` passes.
- I did not run the full `pnpm test:run`, `pnpm -r typecheck` or `pnpm
build`. CI covers them.

## Risks

- No schema change.
- A managed-config document with a plugin entry now provisions a row at
boot. A document without one behaves as before.
- Adoption renames an unmarked row for the same plugin and driver to the
declared name and resets its driver config to the declared one. The row
keeps its id, agents and other metadata, and it is then locked like the
managed sandbox.
- Boot now refuses to start when a plugin entry names a plugin that no
auto-installed catalog entry provides. This is the same fail-closed
posture as an unknown sandbox provider.

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

## Model Used

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

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 17:35:13 +00:00
Nicky LeachandClaude Opus 5.5 83c6832ccb fix(build): typecheck package, Playwright, and e2e test files in CI (#18839)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Runner, plugin, and Playwright tests verify how those agents run,
connect, and appear in the board
> - Most package tsconfigs exclude tests, and Vitest strips types, so a
type error in those tests fails nothing
> - The tests drifted to 803 real type errors, and some of those errors
hid assertions that checked nothing
> - paperclipai/paperclip#18739 and paperclipai/paperclip#18738 check
the server test tree and four test projects, but not these files
> - This pull request adds a required check for these test files, fixes
every error, and fails CI if a test file is in no program
> - The benefit is that a type error in any test file outside
`server/src` now fails CI

## Linked Issues or Issue Description

Refs #18739
Refs #18738

**What happened?**

517 test files were in no program that required CI checks. A type error
in those files did not fail CI.

**Expected behavior**

CI fails on a type error in any test file.

**Steps to reproduce**

Add `export const value: number = "text";` to a test file under
`packages/paperclip-runner/src` on master. CI stays green, because
`tsconfig.json` excludes test files and Vitest strips types.

**Measured gap**

Each row is a program with its tests included, on one thread. File and
error counts are for head `7d4b5e907a`. Memory peaks were measured
before the two latest merges of master.

| Group | Test files | Real errors | Peak |
| --- | ---: | ---: | ---: |
| `paperclip-runner` Node source | 290 | 644 | 1.09 GiB |
| `paperclip-runner` surfaces | 9 | 12 | 0.42 GiB |
| `paperclip-runner` devtools and examples | 14 | 0 | 0.68 GiB |
| `sandbox-providers/kubernetes` | 21 | 65 | 0.44 GiB |
| 8 other sandbox providers and `bridge-template` | 32 | 4 | 0.34 GiB |
| `plugins/sdk` | 22 | 15 | 0.41 GiB |
| `plugin-workspace-diff` | 5 | 0 | 0.37 GiB |
| `plugin-orchestration-smoke-example` | 1 | 1 | 0.15 GiB |
| `kv-demo-mcp-server` | 3 | 0 | 0.13 GiB |
| `packages/db` scripts | 1 | 0 | 1.01 GiB |
| 8 Playwright suites under `tests/` | 52 | 25 | 1.72 GiB |
| `tests/runner-acceptance` | 3 | 0 | 0.58 GiB |
| `tests/e2e`, through layer declarations | 63 | 37 | at most 1.59 GiB |
| `ui` `AttentionInteractionResolver.test.tsx` | 1 | 0 | n/a |
| **Total** | **517** | **803** | |

27 of the 803 errors came from master while this pull request was open.
Each merge of master brought new test code that no check had read.

Errors that were config errors, not real errors:

- One TS2688 for each provider, because the root install does not
install provider dependencies.
- 9 in the Runner Node program (TS7016 and TS6059).
- 2 in `tsconfig.browser.json`. TypeScript 7 removed `baseUrl`, so that
config was invalid.
- 213 in the Playwright suites (`@/*` aliases and Vite types).
- About 60 in `tests/e2e` (import extensions and aliases).

`packages/plugins/create-paperclip-plugin` has no gap. Its tsconfig
excludes its test, but `cli/tsconfig.json` reaches that test through an
import. `server/test-baselines` also has no gap, because
`tests/lifecycle-baseline` reads it.

## What Changed

- Added `scripts/typecheck-test-programs.mjs` and its tests. The
`Typecheck + Release Registry` job runs it. It checks each program in
`PROGRAMS` on one thread.
- The script fails if a tracked `*.test.ts(x)` or `*.spec.ts(x)` file
outside `server/src` is in no program. The compiler file list decides
this, because `include` drops `foo.test.tsx` when `foo.test.ts` exists.
- Added a `tsconfig.test.json` to each uncovered package, and
`tests/tsconfig.playwright.json`.
- Added `tests/e2e` to `PROJECTS` in
`scripts/typecheck-test-projects.mjs`, with a `tsconfig.json` and a
`tsconfig.layers.json`.
- Added `scripts/typecheck-sandbox-providers`. It is a small workspace
that installs provider SDKs from one lockfile. I did not add a lockfile
inside each provider, because
`scripts/build-standalone-public-packages.mjs` uses a frozen install
when one exists, and that changes what a release resolves.
- Corrected `packages/paperclip-runner/tsconfig.browser.json` for
TypeScript 7.
- Set `rootDir` to `..` in
`packages/paperclip-runner/tsconfig.test.json`, because one Runner test
imports from `packages/adapter-utils/src`. The program does not emit
files, so this changes no build output.
- Moved the Kubernetes provider from `vitest ^3.2.4` to `^4.1.8`, the
version the other providers use. Vitest 3 requires `tinypool ^1.1.1`,
which has two critical advisories (GHSA-85c8-ppgw-ccpr and
GHSA-5gmw-xhrv-c9v3). The first patched version is 2.1.2. Its tests have
the same result on both versions: 189 passed, and the same 12
`file-sync` tests failed.
- Moved the Kubernetes provider from `zod ^3.24.2` to `^4.6.6`, and gave
its two `z.record` calls a key schema. This is the only production
change, and it stays in this pull request. See "Production change for
review".
- Renamed `AttentionInteractionResolver.test.ts` to
`AttentionInteractionResolver.replace.test.ts`, so TypeScript reads the
`.test.tsx` beside it.
- Fixed all 803 errors, with changes to test files and test support
only. The fix for `contracts/native-execution.test.ts` is the same as
the fix in paperclipai/paperclip#18739, so those two changes merge
cleanly.
- Updated `doc/DEVELOPING.md`.

This pull request does not edit `server/scripts/typecheck-layers.mjs`.
paperclipai/paperclip#18739 and paperclipai/paperclip#18738 have both
merged, and this branch includes them.

### Production change for review

The new lockfile made dependency review flag `[email protected]` as a
high-severity advisory (GHSA-mrhx-c7xc-m3w5). The first patched version
is 4.6.6, so 3.x has no fix. The published Kubernetes plugin uses zod to
validate operator config. The zod 3 source does not compile against zod
4, so a typecheck-only override cannot fix this.

The existing tests do not prove this change, because they also passed
with the invalid one-argument `z.record`. So I parsed 45 inputs with the
same schema on zod 3.25.76 and zod 4.6.7:

| Result | Inputs | Detail |
| --- | ---: | --- |
| Identical | 23 | Includes defaults, valid config, and a `__proto__`
key |
| Same outcome, new error wording | 21 | For example, `Invalid url` is
now `Invalid URL`. `validateConfig` shows this text to operators. |
| Different outcome | 1 | `jobTtlSecondsAfterFinished: 2 ** 60` was
valid and is now rejected. The new limit is `Number.MAX_SAFE_INTEGER`. |

I did not test this against a real cluster.

The decision is to keep this change in this pull request. Since then,
paperclipai/paperclip#17364 has moved the rest of the repository to zod
4.6.x, so the Kubernetes plugin now matches it.

### Behavior change for review

`reducedMotion` under `use` is not a Playwright option. It had no effect
in 11 configs and 2 `test.use` calls, so those suites ran with
`no-preference`. The options are now under `contextOptions`, so reduced
motion now takes effect. `app-shell-design.spec.ts` asserts a real
transition, so it now asks for `no-preference` itself.

The baseline manifest has `snapshotCount: 0`, so this invalidates no
published baseline. A frozen trial reference PNG can differ.

### Findings

These are product questions. I did not change them, and I did not hide
them with casts:

1. **`PrpEvent` and its schema disagree.** The TypeScript type in
`src/protocol/replay-contract.ts` requires `normalizedSessionId`. The
`required` list in `protocol/schemas/event.schema.json` does not include
it. `validatePrpEvent` accepts events that the type forbids.
2. **`requestCheckboxConfirmation` is stricter than its runtime.** It
uses the output type, so plugin authors must pass `continuationPolicy`
and `allowDeclineReason`, although the schema gives them defaults.
`z.input` would fix this.
3. **`RetainedRunnerdMaintenanceInput.backend.kind` is a free string.**
The quarantine domain comes from `descriptor.kind`, but the release uses
`proof.backend.kind`. If they differ,
`completeRetainedNativeSessionCleanup` returns 0 and the quarantine
stays. The production caller passes `"runner"` today, so this is not a
live bug.
4. **`CodexHarnessSession.close(input?)` is optional, but the contract
requires `{ reason }`.**

These are tests that verified nothing. I corrected them, and they pass:

- `backends/native-instruction-measurement.test.ts` filtered on
`placement === "core"`, which is not a valid placement. It measured an
empty tool list. It now measures 14 tools.
- `drivers/acpx/codex-acpx-driver.test.ts` checked for `usage.updated`,
which is not an event type.
- The `hostFixture()` helper in `drivers/acpx/runtime-host.test.ts`
ignored two overrides, so
`expect(prepareSandbox).not.toHaveBeenCalled()` could not fail.
- `live/live-session.test.ts` set `agent: "pi"` before a spread that
overwrote it.
- `native-distribution-integrity.test.ts` passed an AbortSignal where
`openCommand` expects `{ signal }`.

These are gaps that this pull request does not close:

- **The root lockfile still has some old zod versions.**
paperclipai/paperclip#17364 moved the direct zod dependencies to 4.6.x.
`pnpm-lock.yaml` still resolves `[email protected]`, `4.1.11`, and `4.4.3`,
which are below the patched 4.6.6.
`packages/paperclip-runner/scripts/pi-distribution/package.json` pins
`zod` to `3.25.76`, and some agent SDKs resolve `4.4.3`. Dependency
review does not flag them, because this pull request does not add them.
- **Dead GitHub coverage.** `chat-adapters-ui-providers.spec.ts`
returned early for GitHub, so four later `provider === "github"`
branches never ran. I removed them. They included the private key file
and paste match, and the reconnect flow. Nothing asserts those behaviors
in that spec now.
- **Provider tests do not run in CI.** The root `vitest.config.ts` lists
only Daytona. The other 8 providers, `bridge-template` included, are
typechecked now, but CI does not run their tests.
- **One `packages/db` test never runs.**
`scripts/clean-poisoned-claude-sessions.test.ts` is outside the
`include` of `packages/db/vitest.config.ts`.
- **`plugin-orchestration-smoke-example` does not collect.**
`@paperclipai/shared` is not linked in its `node_modules`. It passes 3
of 3 with an alias.

## Verification

Run these commands:

```sh
node scripts/typecheck-test-programs.mjs --low-memory
pnpm --filter @paperclipai/server typecheck
node scripts/typecheck-test-projects.mjs --low-memory --reuse-declarations
node --test scripts/typecheck-test-programs.test.mjs
```

Results on macOS (arm64), Node 26.12. CI uses Node 24, and its
`Typecheck + Release Registry` job also passes. At head `7d4b5e907a`,
the test programs, `tests/e2e`, and the script tests pass again with 0
errors. The times and peaks below come from the run before the two
latest merges of master:

| Check | Result |
| --- | --- |
| Test programs, `--low-memory` | Exit 0, 0 errors, 42 seconds, peak
1.78 GiB |
| Server layers, low memory | Exit 0, 0 errors, 38 seconds, peak 3.41
GiB |
| Test projects, including `tests/e2e` | Exit 0, 0 errors, 13 seconds,
peak 1.59 GiB |
| Script tests | 12 passed |
| Injected type error in each of 23 places | Each one failed its program
with TS2322 |
| Coverage check | 0 unchecked files, 10 seconds |

I did not run this in a 4 GiB container. The peaks above are `maximum
resident set size` for the largest single process.

**Runtime check of the fixes.** Types do not prove that a test still
works, so I ran the suites before the two latest merges of master:

| Suite | Result |
| --- | --- |
| Runner, full suite | 4,885 passed, 3 failed, 13 skipped |
| Sandbox providers, 10 packages | 939 passed, 12 failed, 36 skipped |
| Plugin SDK | 200 passed |
| `kv-demo-mcp-server` | 12 passed |
| Renamed UI test and its sibling | 4 passed |

All 15 failures exist without these fixes or come from the local
machine:

- **3 Cursor Cloud timeouts.** `artifact-delivery.test.ts` has 1 and
`cursor-cloud-driver.test.ts` has 2. They time out in a full local run,
and all 133 tests in those files with `codex-security-config.test.ts`
pass when run alone. They fail in the same way with those two files at
their unedited versions.
- **12 Kubernetes `file-sync.test.ts` failures.** They fail in the same
way before and after the fixes. The first failure is that
`/proc/self/fd` does not exist on macOS.

After each of the two latest merges of master, I ran only the Runner
test files that I changed:

| Merge | Result |
| --- | --- |
| `885c249cb9`, 8 files | 534 passed |
| `7d4b5e907a`, 7 files | 672 passed, 16 failed |

The 16 failures are in the two `source-reconciliation` test files. They
fail in the same way with master's unedited versions of those files. 15
of them fail with `ACPX sidecar exited at initialize`, so I think my
local build of the Rust and sidecar binaries is out of date. I did not
rebuild them to check. CI builds them from scratch, and all Runner jobs
pass in CI at `7d4b5e907a`.

**Not run:**

- `tests/e2e` and the 8 Playwright suites need a server, so most of them
have only compile checks. `playwright test --list` loads 308 tests from
the main e2e config.
- `packages/plugins/sandbox-providers/kubernetes/test/integration` needs
a cluster, so it has only compile checks.
- I did not run the full `pnpm test:run` or root `pnpm build`. Required
CI runs them.

**Reduced motion.** I ran 6 motion-sensitive Storybook specs in system
Chrome with a temporary config, once with `reduce` and once with
`no-preference`. Both runs had 45 passed and the same 5 failed. So
reduced motion did not change those results. The 5 failures do not
depend on motion. At least one comes from the browser, because system
Chrome prints `oklch(55.6% 0 0)` where the test expects `oklch(0.556 0
0)`. I did not run the other Storybook specs. The `storybook-visual`
label runs that workflow.

## Risks

- **A new lockfile to maintain.** A change to a provider `package.json`
now fails the typecheck job until someone refreshes
`scripts/typecheck-sandbox-providers/pnpm-lock.yaml`. The failure
message gives the command.
- **The job has a new install.** The step downloads provider SDKs, so it
depends on the npm registry. It uses `--frozen-lockfile` and
`--ignore-scripts`.
- **The local install replaces provider `node_modules`.** A contributor
with a standalone provider install must reinstall that provider after
running the script.
- **Reduced motion can change Storybook results.** See "Behavior change
for review". That workflow is not part of required CI.
- **Kubernetes plugin validation changed.** A release of this plugin
ships zod 4. Error wording changes, and very large integers are now
rejected. See "Production change for review".
- **Master moves fast.** Each merge of master brought new test code with
type errors, 27 so far. After this merges, the check reports those
errors in the pull request that adds them.

## Model Used

- Provider: Anthropic Claude, through Claude Code
- Model: Claude Opus 5.5 (`claude-opus-5-5`)
- Capabilities: tool use, code execution, and subagents for the type
fixes in separate files

## 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)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived
details. The app generated the branch name.
- [ ] I have run tests locally and they pass. See the failures explained
in 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 07:33:59 -08:00
DottaandPaperclip 6cafcaf89b fix(workspaces): recover unpublished archived Git uploads (#19459)
## Thinking Path

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

## Linked Issues or Issue Description

Refs #19396. Refs #19409.

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

## What Changed

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

## Verification

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

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

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

## Risks

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

## Model Used

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

## Checklist

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

Co-authored-by: Paperclip <[email protected]>
2026-11-06 08:04:35 -06:00
DottaandPaperclip 2f35eb1080 feat(dev): add incremental local verification and pass reuse (#19345)
## Thinking Path

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

## Linked Issues or Issue Description

**Subsystem affected**

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

**Problem or motivation**

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

**Proposed solution**

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

**Alternatives considered**

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

**Roadmap alignment**

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

## What Changed

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

## Verification

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

## Risks

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

## Model Used

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

## Checklist

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

---------

Co-authored-by: Paperclip <[email protected]>
2026-11-06 07:29:10 -06:00
DottaandPaperclip 70d23612dd fix: offer Interrupt when queued-message steering is unavailable (#19450)
## Thinking Path

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

## Linked Issues or Issue Description

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

**What happened?**

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

**Expected behavior**

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

**Steps to reproduce**

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

**Paperclip version or commit**

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

## What Changed

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

## Verification

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

## Risks

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

## Model Used

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

## Checklist

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

---------

Co-authored-by: Paperclip <[email protected]>
2026-11-06 07:11:38 -06:00
DottaandPaperclip 275b334fc5 fix(workspaces): skip redundant repository admission snapshots (#19444)
## Thinking Path

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

## Linked Issues or Issue Description

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

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

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

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

## What Changed

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

## Verification

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

## Risks

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

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

## Model Used

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

## Checklist

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

---------

Co-authored-by: Paperclip <[email protected]>
2026-11-06 07:08:59 -06:00
DottaandPaperclip c1d567ff6a fix(workspaces): isolate archived nested Git reconstruction (#19409)
## Thinking Path

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

## Linked Issues or Issue Description

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

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

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

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

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

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

## What Changed

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

## Verification

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

## Risks

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

## Model Used

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

## Checklist

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

---------

Co-authored-by: Paperclip <[email protected]>
2026-11-06 00:56:06 -06:00
DottaandPaperclip 66d3fa97a9 fix(workspaces): preserve ignored files in archived Git imports (#19402)
## Thinking Path

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

## Linked Issues or Issue Description

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

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

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

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

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

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

## What Changed

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

## Verification

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

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

## Risks

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

## Model Used

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

## Checklist

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

Co-authored-by: Paperclip <[email protected]>
2026-11-05 23:11:28 -06:00
DottaandPaperclip da34bcd48d fix(workspaces): resume archived imports from their saved upload (#19396)
## Thinking Path

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

## Linked Issues or Issue Description

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

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

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

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

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

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

## What Changed

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

## Verification

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

## Risks

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

## Model Used

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

## Checklist

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

---------

Co-authored-by: Paperclip <[email protected]>
2026-11-05 22:19:20 -06:00
f2593b7905 feat: make Coordinator and Agent Chat standard and speed up navigation (#19360)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - People enter that work through the workspace, agent conversations,
and tasks.
> - Coordinator and Agent Chat still require experimental enrollment.
> - Task link prefetches do not warm the compact queries used by the
default task view. Chat also waits for a request when its navigation
list already holds the conversation.
> - This pull request makes Coordinator and Agent Chat standard and
reuses the reads needed for navigation.
> - People can choose Classic workspace in Experimental settings. Cached
work can appear while fresh data loads.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Workspace selection, Agent Chat availability, and navigation between
Chat, Tasks, and task details.

**Subsystem affected**

Cross-cutting: UI, workspace preferences, instance settings, and the
database migration.

**Current behavior**

People must enable Coordinator and Agent Chat in Experimental settings.
Opening a task can repeat requests because prefetch warms the full
detail and comment queries, while the default view reads compact task
data and a recent comment window. Opening Chat waits for a detail
request even when the current person's conversation list contains it.

**Proposed behavior**

Coordinator is the standard workspace for everyone. Experimental
settings offer Classic workspace, off by default. Old Coordinator
experiment values do not select Classic. Agent Chat is available to
everyone, including instances with an old saved or managed off value.
Task link intent warms the queries used by the mounted view. Chat can
use the current person's company-scoped conversation list during
refresh.

**Reason and benefit**

People can use the standard workspace without setup. Repeat navigation
can show cached content while the server refreshes it.

**Breaking changes**

Existing Coordinator experiment and Agent Chat off values no longer
disable these surfaces. Classic requires an explicit new opt-in. The
workspace API keeps its existing response shape and audit checks. The
migration retains legacy values and adds a separate Classic preference.
Execution permissions, company access, and chat privacy checks still
apply.

**Additional context**

Roadmap check: this graduates existing surfaces and improves their
navigation. It does not add a new agent execution capability. Duplicate
searches found no open Coordinator workspace PR. Related server cache
work, Refs: #17828, addresses compact issue-list invalidation; this PR
addresses client navigation queries.

## What Changed

- Add a default-off Classic workspace preference. Derive the existing
workspace API response from that choice. Keep personal persistence,
account isolation, and audited writes in the workspace preference
service.
- Replace the Coordinator experiment control with Classic workspace. Use
Coordinator while preferences load. Update the existing settings
examples and navigation recipe.
- Retire the Agent Chat experiment control. Ignore old stored and
managed off values. Retain the compatibility key and the existing
execution and access checks.
- Reuse account- and company-scoped chat list data with its original
freshness timestamp.
- Share compact task and recent-comment query options between prefetch
and the task view. Keep full-history prefetch for Classic tasks and
expanded history.
- Add upgrade, replay, cache, account isolation, and browser switching
regressions. Update product, database, development, and roadmap
documentation.

## Verification

- Targeted preference, settings, migration, cache, and UI regression
tests passed during implementation. The cache tests hold refresh
requests pending and check that content still renders. They also check
account isolation and stale refresh behavior.
- A real browser test verified fresh Coordinator defaults, Classic
opt-in persistence, and return to Coordinator. It used an isolated
source checkout with fresh data on macOS.
- Token, migration numbering and safety, map, and Storybook catalog
checks pass after the rebase.
- Repository typecheck and build pass. Typecheck and token gates also
pass after review repairs. The broad local run passed all six chat
integration shards (1,931 tests). It then found the host-CLI-dependent
recovery fixture described below. Its remaining duplicate work was
stopped after the full current-head CI matrix passed; this is not a
claim that the broad local command completed green.
- Review repairs passed 419 UI/migration fixture tests, 19 Coordinator
navigation tests, and all 214 tests in the five affected server suites.
The 24 Chat/workspace browser tests pass, including standard Coordinator
switching and explicit Classic persistence.
- The broader browser repair run passed 85 tests. It retained two failed
assertions and skipped five later serial cases. The affected files then
passed all 11 tests after the fixes: reopen the desktop panel after
reload and open More for Audit and Connectors. This includes the five
serial cases that had not run.
- The broad local suite exposed a recovery fixture that executed the
host Codex CLI during a simulated remote probe. On a host with Codex
0.154.0, model fallback broke its fixed-model assertion. The fake
sandbox now reports Codex 0.160.0. All 20 selected recovery/provider
compatibility cases pass after the repair.
- A CLI company import/export suite timed out during server startup in
the second CI run. The port fallback is intentional in that fixture. No
root cause was established. That suite passes on the final commit in CI.
This records recovery, not a claimed product fix for the earlier
timeout.
- The first CI run exposed remaining assumptions about the retired flags
and Classic UI. The repair commit updates those fixtures and navigation
steps. It also restores the missing enabled Cases destination in
Coordinator. Earlier local recovery attempts used the wrong fixture
notification and advanced fake time during live execution; the corrected
tests use the subscribed recovery queue and wall time during execution.
- Greptile reviewed commit `98972b030d4505d838427a2fca6b196b8cad6d47` at
**5/5** with no new comments. The original finding is resolved.
- Current-head CI is green: **56 successful checks**, with three checks
skipped by workflow filters. This includes typecheck, build, the
complete Vitest matrix, all eight browser shards, Runner verification,
release checks, and security checks. [Final CI
run](https://github.com/paperclipai/paperclip/actions/runs/42132651414).
- Typecheck caught missing `pid` and `startedAt` fields in the simulated
command result. The fixture now returns the complete process contract,
and the server test-layer typecheck passes.
- Storybook catalog alignment was checked. Storybook play tests were not
run. Hosted latency and cold bundle loading were not measured.

## Risks

- The new default changes the workspace for people who previously left
the Coordinator experiment off. They can opt into Classic in
Experimental settings.
- The additive migration must run before the new server code. It is safe
to replay and preserves explicit Classic choices.
- Cached content can be stale during background refresh. Existing query
invalidation and source timestamps still control freshness.
- These changes reduce avoidable loading waits. They do not establish a
measured improvement in hosted request latency or initial bundle
download time.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools, code
execution, and browser automation. The exact serving revision 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: Scott Tong <[email protected]>
Co-authored-by: Paperclip <[email protected]>
2026-11-05 16:28:25 -08:00
DottaandPaperclip 62a5aa7705 fix(runner): forward configured bindings into OpenCode (#19319)
Forward controller-selected task bindings into OpenCode shell commands. Keep OpenCode runtime configuration isolated. Redact raw and JSON-escaped binding values while preserving the non-secret environment manifest.

Fixes #19257

Co-Authored-By: Paperclip <[email protected]>
2026-11-05 14:01:12 -06:00
Nicky Leach 53937be283 fix(agents): select environment plugins only for agents on that environment (#19173) 2026-11-04 20:59:02 -08:00
DottaandPaperclip 9e5039f6c8 fix(daytona): retain duplex close ownership after lease cleanup (#19150)
## Thinking Path

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

## Linked Issues or Issue Description

**What happened?**

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

**Expected behavior**

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

**Steps to reproduce**

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

**Paperclip version or commit**

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

**Deployment mode**

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

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

## What Changed

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

## Verification

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

## Risks

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

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, source editing and tool
execution. The exact model ID and context window are not exposed in this
session.

## Checklist

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

---------

Co-authored-by: Paperclip <[email protected]>
2026-11-04 19:15:50 -06:00
fb96537e28 test(ui): verify disconnect recovery through browser outages (#18832)
## Thinking Path

> - Paperclip lets people manage agent work through the board.
> - Deploys can interrupt reads and writes while people use the board.
> - Component tests cannot prove the browser keeps task content and
saved drafts through a reload.
> - The read and cache changes need a regression suite against the built
app.
> - This pull request drives those journeys through temporary API
failures.
> - CI discovers the new suite through the existing e2e shard plan.

## Linked Issues or Issue Description

**What happened?**
Outage recovery lacked one browser suite covering retained content,
write recovery, cached reloads, access errors and sign-out clearing
together.

**Expected behavior**
Keep loaded task content visible through a 20-second API outage. Show
one connection banner and its recovery state. Save comments once and
retain document edits. Restore approved reads after a reload. Keep real
error responses readable and clear query records on sign-out.

**Steps to reproduce**
Open a task or settings. Return 503 with `tenant_app_unavailable` from
browser API requests for 20 seconds. Type a comment, edit a document, or
reload. Restore the API and compare the visible result with the saved
server state. Repeat with real 403/404 statuses and sign-out.

Stacked on Refs #18825, which depends on Refs #18822. Related
durable-write foundation: Refs #17960.

## What Changed

- Add `disconnect-resilience.spec.ts` with ten browser cases for
comments, a queued comment across reload, document autosave, cached
settings/sidebar/task reads, legacy Inbox reloads, 403 and 404
responses, sign-out clearing, restored-draft conflict detection, and
first-socket outage control.
- Assert one connection banner, readable recovery, no transient error
boxes or raw codes, retained task text, one saved comment, and the saved
document after reload.
- Use the real local server and isolated embedded PostgreSQL. Intercept
browser API traffic for outages. Control the logout/session response for
the sign-out test. Create no agents or model runs.
- Register realtime interception before navigation. Track both ends of
current sockets, close them at outage start, and keep reconnects
unavailable through the HTTP outage deadline. Preserve normal close
forwarding and close tracked pairs during teardown. The regression
captures the first browser socket without waiting for history or open,
then checks that the outage closes it.
- Prepare complete server-derived experimental settings and workspace
preferences before the legacy Inbox navigation. Its route callbacks
serve per-test snapshots across reloads, avoiding reads from disposed
route.fetch responses.
- Document the direct test command. The existing CI e2e shards already
discover this file exactly once; no workflow or package script is added.

## Verification

- Current head `228c1b68d30` is merge-ready: all 54 check runs completed
(52 successful, one skipped, one neutral), the legacy Snyk status is
successful, and both required gates pass. The single failed-jobs debug
rerun in run `41752978283` passed on attempt 2, including Runner package
job `126328280650` and `verify` job `126332119116`. Greptile Apex
reviewed this exact head at 5/5 with zero unresolved threads; the branch
has no merge conflicts. The three-PR stack is awaiting operator merge
approval.
- Original attempt 1 of current-head CI run `41752978283` had 46
successful jobs, one skipped and two failed: Runner package tests and
the downstream `verify` gate. GitHub reports lost communication with the
Runner host; the test step was cancelled, its log returns HTTP 404, and
no Runner diagnostic artifact is available. The underlying test outcome
and disconnection cause are unknown. One failed-jobs retry with debug
logging was accepted after verifying the unchanged current head and
passed on attempt 2. No source change was made; earlier passing CI and
local tests do not certify this incomplete Runner result. Current-head
CI now passes; the earlier disconnection cause remains unknown.
- Preceding head `6531caccf62` reached merge-ready: all 54 check runs
completed (52 successful, one skipped, one neutral), the legacy Snyk
status was successful, and both required gates passed in run
`41749512329`. Greptile reviewed that exact head at 5/5 with zero
unresolved threads. Current head `228c1b68d30` carries later
account-preference and bulk-selection fixes and now has its own passing
current-head CI.
- All 23 built-UI outage/write/loading cases pass in 4.5 minutes on
exact current head `228c1b68d30`: ten outage, six durable-write and
seven loading cases. The run used bundled Chromium, a fresh
local-trusted server and isolated embedded PostgreSQL on macOS
arm64/Node 25.6.1. Owned temporary server and database stopped.
- Current integrated head `228c1b68d30` contains cache revision
`fe4f682c879` and read head `09d1db29dea`. The exact application source
passes all 1,198 focused tests across twenty-eight files, UI typecheck
and both guards. Seventeen new cases cover restored-account settings,
owned crown cleanup and pending bulk selections; sixteen fail the
previous source and one preserves denial compatibility. All three
browser specs pass standalone strict checking. All 23 current-head
browser cases pass in 4.5 minutes.
- Application revision `9c1a10f25d9` passed all 1,137 focused tests
across twenty-one files, UI typecheck and both guards. Exact preceding
application/spec head `4fec693ec94` passed all 23 built-UI
outage/write/loading cases in 4.6 minutes and standalone strict checking
of all three specs. It used bundled Chromium, fresh isolated PostgreSQL
and local-trusted auth on macOS arm64/Node 25.6.1. Owned test processes
stopped.
- Preceding integrated head `870aa709657`, containing cache
`cbb891dd37d`, passes all 23 built-UI outage/write/loading cases in 4.6
minutes: ten outage cases, six durable-write cases and seven loading
cases. It used bundled Chromium, the built UI, a fresh local-trusted
server and isolated embedded PostgreSQL on macOS arm64/Node 25.6.1.
Owned test processes stopped.
- The earlier head `763fe431b74` passes the affected legacy Inbox
outage/reload case (34.3 seconds; 50.8 seconds including startup) and
strict three-spec checking after the response-snapshot fix. The
preceding head `973a12d77f4` passed all 23 built-UI outage/write/loading
cases in 4.6 minutes: ten outage cases, six durable-write cases and
seven loading cases. Its first-socket regression fails the previous
helper because that socket stays open. The run used bundled Chromium,
the built UI, fresh isolated PostgreSQL and local-trusted auth on macOS
arm64/Node 25.6.1. That earlier isolated callback revision was verified
with its affected case; the later application/spec head `4fec693ec94`
also passes the full suite. Owned test servers and databases stopped.
- The current base gives known skills/app errors priority over sibling
loading. All 60 affected tests and both application UI typechecks
passed. Its twelve new regressions failed the preceding source. All 47
recovery-worker tests and seven related Runner fixture variants also
passed during the historical CI audit; those focused checks do not claim
a full current server/Runner suite.
- All 22 outage/write/loading cases passed in 4.7 minutes on preceding
integrated head `b7deaaee5da`, including direct cache revision
`97c0bc3966f`. The run used the built UI, bundled Chromium and a fresh
local-trusted server with isolated embedded PostgreSQL on macOS
arm64/Node 25.6.1. It covered all nine outage, six durable-write and
seven loading cases. The owned temporary server and database stopped.
- All 1,038 focused tests passed on that direct cache revision, with UI
typecheck and guards. Six regressions failed the previous source: online
retries could trigger a hidden-tab worker reload, and sign-out failed
without crypto.randomUUID. Both modes and stamp states now pass.
Standalone strict checking passes for all three browser specs.
- The restored cached-A/draft-B/server-C test asserts zero replay
PATCHes and retains the conflict. The queued-comment case holds fresh
session verification after general API recovery and asserts zero POSTs
until release. Denial, logout and cached-navigation cases require saved
records. Sign-out seeds several company records to exercise native
Storage key reordering. Healthy navigation waits for
conversation-history readiness.
- Earlier runs exposed real cached-header, storage cleanup and replay
defects in the base. Those failures and corrected fixture assumptions
remain in the discussion. A prior run passed 21/22 while real sockets
remained available during simulated HTTP failures. A diagnostic trace
established the contradiction. The first repair gated new sockets, but
review identified the first pre-existing socket still escaping. This
revision intercepts before navigation and closes existing pairs too. The
new regression fails the preceding helper, and all 23 current cases pass
without weaker assertions. Shared-memory exhaustion prevented two
earlier diagnostic startups; only unused segments from completed owned
test databases were removed.
- All six separate cross-organization link/upload variants passed on an
earlier integrated base. Standalone strict checking of that unrelated
spec reports five existing errors in its shared fixture; this PR does
not claim that check passes.
- CI on older head `53d979e384c` failed the legacy Inbox case with
`apiResponse.json: Response has been disposed` inside a route.fetch
callback. Its other 25 cases passed and one skipped. The seven other
browser shards succeeded; the aggregate only reports that child failure.
The new snapshot callbacks remove those fetched-response lifetimes.
Original CI evidence remains recorded in the discussion.
- Greptile Apex reviewed exact current head `228c1b68d30` at 5/5 with no
actionable findings and zero unresolved threads (summary updated
2026-11-04 at 01:26:14 UTC). The branch has no merge conflicts.
Current-head CI now passes; the earlier disconnection cause remains
unknown. Controlled local outages do not prove hosted authentication or
a real Cloud restart/deploy.

Commands run:

```sh
pnpm exec playwright test --config tests/e2e/playwright.config.ts tests/e2e/disconnect-resilience.spec.ts --grep 'a legacy Inbox reload retains' --reporter=list
pnpm exec playwright test --config tests/e2e/playwright.config.ts tests/e2e/disconnect-resilience.spec.ts tests/e2e/disconnect-write-recovery.spec.ts tests/e2e/task-loading-frame.spec.ts --reporter=list
pnpm exec tsc --ignoreConfig --noEmit --strict --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck tests/e2e/disconnect-resilience.spec.ts tests/e2e/disconnect-write-recovery.spec.ts tests/e2e/task-loading-frame.spec.ts
```

## Risks

The suite adds about three minutes to one e2e shard and depends on
current accessible UI controls. It uses controlled outages and logout
responses. A passing local run must not be treated as real deployment
acceptance. There are no production behavior, schema, dependency, or API
changes in this PR.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with xhigh reasoning effort, tool
use, and code execution. The context window size is not exposed.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for related PRs and linked them above
- [x] I have described the issue following the bug report template
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change and contains no internal
ticket id
- [x] All 23 built-UI cases pass on current head `228c1b68d30`
- [x] All three browser specs pass standalone strict typechecking on the
current head
- [x] The current application source passes all 1,198 focused tests, UI
typecheck and both guards
- [x] I have added tests and updated relevant documentation
- [x] I have considered and documented risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: devinfoley <[email protected]>
Co-authored-by: Paperclip <[email protected]>
Co-authored-by: Bender (Fable) <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
2026-11-04 16:11:02 -08:00
2ccc7d37ef feat(ui): retain approved reads across outage reloads (#18825)
## Thinking Path

> - Paperclip lets people manage agent work through the board.
> - The board already retains loaded reads during a temporary server
failure.
> - Reloading the page removes that in-memory data.
> - A deploy can then leave a returning user without the shell or recent
tasks.
> - This pull request saves an explicit, limited set of reads in the
browser.
> - Cached identity supports display while fresh session checks protect
writes.

## Linked Issues or Issue Description

**What happened?**
A reload during an API outage loses the board's loaded reads. An
automatic worker update can also reload a hidden tab while a write is
pending.

**Expected behavior**
Show the cached shell and recent tasks during a temporary outage. Limit
saved content by user, company, age and build. Clear it on sign-out.
Wait for a fresh session before sending account-bound writes. Defer
automatic reloads while work is pending.

**Steps to reproduce**
Open a task and settings while online. Return 503 from the API and
reload. Restore the API. Sign out and confirm that saved query records
are gone. Discover a worker update while offline or during an autosave
and confirm that it waits.

Stacked on Refs #18822. Related durable-write foundation: Refs #17960.
The outage browser suite follows in a separate test PR.

## What Changed

- Persist an exact read allowlist through TanStack Query dehydration and
hydration. Save shell preferences, company/sidebar data, agent/project
lists, plugin UI metadata, the first inbox page, and 20 recent tasks.
Task UUID and identifier reads share one recent-task slot.
- Namespace records by user and company. Retain eligible reads across
normal memory garbage collection, expire each read after 24 hours, and
discard them after a build change. Unstamped builds disable hydration
and persistence while retaining sign-out cleanup. Use the existing
instance-wide plugin contribution query in the user's root record.
- Exclude session credentials and bootstrap data, embedded plan
documents, comments, preview work products, provider credentials and
environment values, browser capabilities, pairing data, other queries,
and mutations. Restore only display identity from session data. Preserve
safe agent provider/model display fields and configuration object
shapes. Apply the projection on save and restore.
- Clear saved reads on sign-out, cross-tab account changes, and
definitive session denial. Keep revocation working on plain HTTP without
crypto.randomUUID. Use per-user revocation revisions to reject delayed
writes and stale records. Preserve a newer account. Remove every
UUID/identifier projection and nested inbox copy of a denied task.
- Detach shared GETs when the account changes or signs out. Load
complete, freshly authorized agents before opening bulk settings from a
display-only roster. Retire a pending bulk-editor result or error when
row, select-all or clear changes the selection, including a
clear/reselect of the same agent.
- Require a fresh server lookup before account-bound writes, pending
task creation, or mounted autosaves resume. This includes automatic task
read receipts, inbox archives, workspace preferences and primary-agent
choices, with optimistic archive changes gated too. Cancel the old write
scope when the verified user changes. Release completed archives through
account-independent cleanup without restoring old cached rows; an old
operation cannot clear a newer hide flag. Retry unconfirmed read
receipts after a fresh session succeeds for the original account, and
remember confirmed receipts across refreshes. Track pending reads per
task so opening another task sends its receipt immediately; revisiting
reuses an outstanding receipt and its original account scope.
- Defer a hidden-tab worker reload until connectivity, mutations,
durable sends, and autosaves are safe. Synchronously count each
unresolved operation across online rate-limit retries. Release a
cancelled target’s unused dirty token when an editor changes tasks; keep
stored drafts, admitted saves and queued retries. Cover autosave
debounce, newer edits, navigation and undo.

## Verification

- Current head `fe4f682c879` is merge-ready: all 54 check runs completed
(52 successful, one skipped, one neutral), the legacy Snyk status is
successful, and both `ci / verify` and `ci / e2e` pass in run
`41752927822`. Greptile Apex reviewed this exact head at 5/5 with zero
unresolved threads; the branch has no merge conflicts. All three PRs now
meet the merge-ready bar; the stack remains unmerged pending operator
approval.
- Greptile Apex reviewed exact current head `fe4f682c879` at 5/5 with no
actionable findings and zero unresolved threads (summary updated
2026-11-04 at 01:24:40 UTC). Both settings-account and pending-selection
findings are fixed, replied to and resolved. The branch has no merge
conflicts. Current-head CI now passes in run `41752927822`.
- Seventeen new cases cover settings restored from actual saved query
records, matching/changed/signed-out identities, denied verification,
late crown responses without overwriting newer reads, and bulk-selection
changes during successful/failed reads. Sixteen fail the preceding
source and one preserves denial compatibility. Both settings paths reuse
the existing account-bound mutation guard; its optional explicit
QueryClient supports the workspace hook’s existing detached-host mode.
Primary-agent optimistic updates begin only after verification and
cleanup checks ownership and the cache revision.
- Earlier CI run `41749456480` on prior head `2cf7516f18c` has one real
server test failure: the owned-runtime case in
`idle-completed-history.test.ts` could not reach its HTTP child on port
34757 within 10 seconds (99 refused probes). Its other 1,829 cases pass
and three skip; the only other unsuccessful job is the downstream
`verify` gate. The exact selected case passes locally, and the entire
75-case file passes in 72.19 seconds on the exact `2cf7516f18c` source
tree with isolated PostgreSQL and a real child process on macOS
arm64/Node 25.6.1. The Linux CI startup cause remains unknown; these
local results are not a fix or CI acceptance. One failed-jobs retry with
debug logging was accepted on that unchanged head, then superseded by
the later settings fixes. Current-head CI remains required; no source
change was made for this failure.
- Current cache head `fe4f682c879`, based on read head `09d1db29dea` and
master `683fa4725e9`, passes all 1,198 focused tests across twenty-eight
files, UI typecheck, token gates and the enforcing query-error guard.
Dependent browser head `228c1b68d30` contains this exact application
source and passes standalone strict checking of all three browser specs.
Current-head CI now passes in run `41752927822`.
- Three new navigation regressions fail the preceding application source
and pass with the fix. They open three cached tasks before any receipt
resolves, leave the page before success or failure, revisit an
outstanding read without duplicating it, and recover an older failed
receipt after a fresh session succeeds while a later task remains
pending.
- The fixture repair passes all seven deadline integration cases and the
complete server typecheck, including the server test layer. The old
assertion deterministically fails at 23:59:06.888 UTC; its
one-second-old run also fails the first-second-of-day probe. The
corrected fixture passes noon and both midnight probes. The permanent
three-case coverage uses seeded 2099 clocks.
- Nine new cases cover archive success/failure after sign-out and
another account takes over, both mounted and unmounted; cleanup
ownership when a newer archive uses the same task; session-check 429
exhaustion followed by matching, changed or signed-out identity; and a
failed read receipt followed by a new session read. They verify no
old-cache restoration, toast or navigation, retained newer hide flags,
no failed-check loop and no duplicate confirmed receipt. Seven fail the
previous implementation; two compatibility cases retain account-change
suppression. Initial test error-constructor and query-notification
timing assumptions were corrected before the passing checks.
- The inherited skills/app error-priority fix passes all 60 affected
tests and UI typecheck on both application branches. Its twelve new
regression cases fail the preceding read implementation. The
persisted-cache suite includes 48 cases.
- Six new regressions fail against the preceding application source and
pass with the fix. Actual worker-update and mutation-cache listeners
cannot reload a hidden tab between online 429 retries; the original
create key survives, and the update reloads once after a receipt. Real
self-hosted and Cloud sign-out work without crypto.randomUUID, with
stamped and unstamped builds. Saved reads are removed and account write
scopes are invalidated. An initial test-fixture type annotation error
was corrected before the passing typecheck.
- The 48 cache tests cover actual five-minute memory GC, the 20-task
bound across saved and live reads, original 24-hour expiry, denial
before session verification, unstamped cleanup, cross-tab revocation and
delayed flushes. Private GETs cannot cross account transitions. Bulk
edits require complete current agent data. Undo releases the update
blocker while preserving the Saved indicator.
- All 23 built-UI outage/write/loading cases pass in 4.5 minutes on
exact integrated head `228c1b68d30`, containing current cache revision
`fe4f682c879`: ten outage, six durable-write and seven loading cases.
The run uses bundled Chromium, the built UI, a fresh local-trusted
server and isolated embedded PostgreSQL on macOS arm64/Node 25.6.1. All
three specs pass standalone strict TypeScript checking. Owned temporary
server and database stopped.
- Earlier browser runs exposed and fixed cached-header, native Storage
cleanup and replay/session defects. A prior run passed 21/22 because
real WebSockets contradicted the simulated HTTP outage; the fixture now
closes existing socket pairs and holds new sockets through the same
outage deadline. An older CI run exposed a disposed response in the
Inbox route callback; pre-navigation snapshots remove that lifetime, and
the current full run includes that repair. Earlier failures and their
diagnosis remain in the discussion. The current full run passes. Broader
historical workspace failures remain documented in Refs #18822; this
does not claim a clean full workspace suite.
- Current-head CI passes on `fe4f682c879`. Controlled local outages do
not complete hosted authentication or a real Cloud restart/deploy
acceptance.

Commands for the current cache change and inherited read fix (the
current focused run also includes src/lib/inboxArchiveCache.test.ts and
src/pages/Inbox.test.ts):

```sh
pnpm --filter @paperclipai/ui exec vitest run src/pages/CompanySkills.test.tsx src/pages/apps/AppNotConnected.test.tsx
pnpm --filter @paperclipai/ui exec vitest run src/hooks/useDurableSubmit.test.tsx src/hooks/useDurableAutosave.test.tsx src/hooks/useSignOut.test.tsx src/lib/persisted-query-cache.test.ts src/lib/service-worker-updates.test.ts src/lib/service-worker-cache-policy.test.ts src/lib/pending-send.test.ts src/lib/issue-execution-policy.test.ts src/components/InlineEditor.test.tsx src/components/MarkdownEditor.test.tsx src/components/NewIssueDialog.test.tsx
pnpm --filter @paperclipai/ui exec vitest run src/components/agents/AgentBulkRoster.test.tsx src/components/agents/AgentBulkSettingsSheet.test.tsx src/hooks/useAccountBoundMutation.test.tsx src/pages/IssueDetail.test.tsx src/components/TaskChatThread.test.tsx src/components/TaskChatThread.task-creation-order.test.tsx src/api/client.test.ts src/api/agents.test.ts
pnpm --filter @paperclipai/ui exec vitest run src/hooks/useCoordinatorWorkspaceEnabled.test.tsx src/context/PrimaryAgentProvider.test.tsx src/components/CoordinatorWorkspaceSetting.test.tsx src/components/primary-agent/PrimaryAgentPresentation.test.tsx src/components/MobileBottomNav.coordinator.test.tsx src/components/CoordinatorSidebar.test.tsx src/pages/Issues.coordinator-routing.test.tsx
pnpm --filter @paperclipai/ui typecheck
pnpm check:token-gates
node scripts/check-query-error-rendering.mjs --enforce --summary
```

## Risks

This deliberately saves task content on the local browser for up to 24
hours. The allowlist excludes credentials and ephemeral capabilities.
Local storage can be disabled or full; persistence then fails without
blocking the app. Restored data can be stale and never grants server
access. A user or access change clears cached account reads and
invalidates pending writes. The browser outage suite and real hosted
deploy acceptance remain release requirements. There are no migrations
or new dependencies.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with xhigh reasoning effort, tool
use, and code execution. The context window size is not exposed.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have described the issue in-PR following the bug report template
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change and contains no internal
ticket id
- [x] The focused local tests for this change pass
- [x] I have added tests and updated relevant documentation
- [x] I have considered and documented risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: devinfoley <[email protected]>
Co-authored-by: Paperclip <[email protected]>
Co-authored-by: Bender (Fable) <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
2026-11-04 15:40:30 -08:00
a3313f6944 fix(ui): complete read recovery and enforce query error rendering (#18822)
## Thinking Path

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

## Linked Issues or Issue Description

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

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

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

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

## What Changed

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

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

## Verification

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

Commands for the latest fix:

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

## Risks

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

Recovered commits retain their original authors. If squash-merging,
preserve `Co-Authored-By: Bender (Fable) <[email protected]>`
and the Paperclip trailer.

## Model Used

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

## Checklist

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

---------

Co-authored-by: devinfoley <[email protected]>
Co-authored-by: Paperclip <[email protected]>
Co-authored-by: Bender (Fable) <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
2026-11-04 14:07:18 -08:00
DottaandPaperclip ecc253b73d fix(workspaces): retain redacted archive capture causes (#18935)
## Thinking Path

> - Paperclip helps people manage AI agents at work.
> - Remote workspaces need a verified saved archive.
> - A failed capture must retain a safe cause for diagnosis.
> - The codec and capture wrapper erased known validation and filesystem
failures.
> - The one-shot output fix in #18888 preserves only the existing disk
and Git messages.
> - This PR retains a closed set of capture failures without exposing
workspace data.

## Linked Issues or Issue Description

Refs #18888.

**What happened?**

Remote archive capture can still report only
`remote_workspace_operation_failed`. This was observed after the public
revision `f211d44c00fbc31d22e7ff687e22dfa05643da2a` included #18888.
That observation identifies the capture stage. It does not identify the
underlying failure.

The real local codec gives a specific validation error for an escaping
symlink. Node also gives a specific errno when an archive is missing.
The capture wrapper erased both causes. The codec sent raw filesystem
paths to its parent process before that wrapper discarded them.

**Expected behavior**

Retain exact, fixed capture validation and filesystem classes. Keep
arbitrary messages, paths, filenames, file contents, and command output
private.

**Steps to reproduce**

Call the real capture transport with an escaping symlink. Separately,
create a canonical capture receipt whose archive is missing. Both fail
with the generic transport message on unchanged master. The new tests
use real Node processes and files.

## What Changed

- Add a self-contained classifier for fixed capture validation messages
and a finite Node errno set.
- Redact unknown codec capture errors. Preserve recognized classes
through helper installation, capture, and split or combined command
output.
- Keep the existing disk and unfinished-Git messages first. Keep other
command behavior and all save, retry, ownership, Stop, and permission
policy unchanged.
- Document the diagnostic contract.

## Verification

Node 24.22.2, pnpm 9.15.4, macOS arm64, isolated source checkout.

- Six new real-process cases failed before production edits. The same
six passed after the repair.
- The complete archive and transport family passed **93/93**, with no
skips. A new contract test covers every capture-side reason in the
actual serialized codec; an unlisted-reason mutation fails that test.
- These tests cover real symlink rejection, missing canonical archive,
serialized-helper ENOENT/EEXIST redaction, unchanged source bytes and
canonical receipt, unknown errno, malformed output, other command
stages, and the successful capture/restore path.
- A safety test caught a regression in the first patch: early redaction
hid a Git child's existing disk-full signal. Unchanged master passed
both split/combined cases; the first patch failed both. The final family
passes both after the same closed disk recognition was restored. The
earlier failure remains recorded.
- Full recursive typecheck and `pnpm build` passed on the current head
with Node 24.22.2.
- A fresh probe of the current head's compiled `server/dist` artifact
passed. It ran nine real Node commands with combined one-shot output. It
checked both fixed failure classes, unchanged source/receipt bytes, and
a successful capture, hash check, cold restore, and scoped cleanup.
- The first head's full local `pnpm test:run` failed one unchanged
GitHub fast-response streaming-quarantine acknowledgement assertion: an
acknowledgement appeared where the test expected none. The first chat
shard passed 319 cases; the second passed 323 and failed one. One
bounded focused comparison passed on both that head and untouched
master. It did not explain the original failure. No chat source,
assertion, or timeout changed. No full local-suite pass is claimed.
- All **57 current-head contexts are terminal and clean**: 53 success,
two skipped, two neutral. Required
[CI](https://github.com/paperclipai/paperclip/actions/runs/41781977986)
gates passed. Fresh Greptile and Paperclip assessments both report
**5/5** on `d7b2fcc22c75e3157e0c53bf4725e665810f2d19`, with no open
review threads. The optional security scan is neutral, not an
independent security assessment pass.

```sh
pnpm exec vitest run server/src/services/remote-workspace-transport.test.ts server/src/services/remote-workspace-snapshot-archive.test.ts
```

This is a **diagnostic repair**. It does not establish the cause of the
observed hosted failures, qualify a live provider capture, or recover
the original files. No hosted resources were executed, started, changed,
or retried for this PR.

## Risks

The classifier can expose only a fixed message or a fixed class from its
finite errno set. Unknown and altered messages keep the generic failure.
Existing disk/Git priority and command-stage restrictions have explicit
tests. The new classes may improve later diagnosis; they do not
authorize another operation.

## Model Used

OpenAI Codex, GPT-6 family. The exact serving model build and context
window are not exposed in this agent session. The agent used reasoning,
terminal tools, code edits, and real Node/filesystem tests. A separate
Codex agent reviewed the production diff.

## 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 <[email protected]>
2026-11-04 07:18:55 -06:00
DottaandPaperclip a194166017 perf(ci): repair dependency caches and parallelize transport fixtures (#18883)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Required CI checks protect changes to its native Runner and control
plane.
> - Recent Runner jobs restored a Rust cache but still rebuilt
dependencies for 74–92 seconds.
> - New workspace install hooks disabled the materialized JavaScript
dependency cache.
> - A single transport file also kept 326 test cases on one worker.
> - This pull request corrects the cache inputs, replays known install
hooks, and separates independent transport test groups.
> - The same checks can finish sooner without removing test cases or
widening runner access.

## Linked Issues or Issue Description

Refs #18794 and #18544. Related dependency-action version update:
#17698; this change fixes the cache input contract used by the currently
pinned action.

**What happened?**

Recent PR jobs such as #18783 and #18825 restored an exact Rust
dependency cache from October 16. The virtual workspace's development
profile changed on October 30. Rust Cache 2.9.2 hashes member manifests
but omits that virtual manifest. Rebuilding cannot replace an immutable
exact cache entry. The JavaScript cache also rejected the new server and
Runner Copilot postinstall hooks.

**Expected behavior**

Changing a Cargo profile selects a new dependency cache. An exact
JavaScript dependency restore still performs required workspace install
hooks. Independent transport fixtures use the existing file workers
while retaining all assertions and real deadlines.

**Steps to reproduce**

Inspect the Rust dependency cache hit and following `build:rust` in the
recent Runner package jobs. Run `node scripts/ci-dependency-cache.mjs`
on the previous revision to see `cacheable: false`. Run the original
transport file to measure the serial test work.

## What Changed

- Add the virtual workspace manifest and ancestor Cargo configuration to
all shared Rust dependency keys. Keep reader/writer identities aligned
and preserve master-only cache writes and CODEOWNERS-only AWS routing.
- Recognize only the exact reviewed root, server, and Runner postinstall
commands. Include the server/Runner installer scripts in the dependency
key. Replay workspace postinstall hooks after a materialized restore.
Unknown lifecycle hooks still disable reuse.
- Move attachment recovery, warm attachment, live adoption, and
suspension fixtures into separate files. Keep the large event-backlog
cases in the original file to avoid starting another expensive fixture
group at the same time. Share five stateless test helpers outside
production source.
- Extend the opt-in `profile-ci` benchmark with a PR-scoped Rust cache
producer and a fresh-host consumer. Require exact dependency/workspace
hits and reject any Cargo unit that is rebuilt. Retain the JSON build
evidence. Existing setup qualification checks real JavaScript cache
restores.
- Schedule the ten measured files over 50 seconds first, in approximate
duration order. Package CI uses up to four workers, bounded by available
CPUs; local runs retain Vitest defaults. The AWS runner has four CPUs
and 16 GiB RAM; the three-worker run averaged 44% CPU and peaked at 15%
memory. Preserve Vitest selection and shard membership and default
ordering for other files. Job count and AWS authorization are unchanged.
- Scope the optional paired server benchmark to changes in its measured
inputs. Cache-only changes qualify exact restores and run the shared
package tests using restored dependencies.
- Update the cache and test documentation and the existing native cache
contract guard.

## Verification

- All 586 `.github/scripts/tests/*.test.mjs` policy tests passed,
including cache trust and runner routing guards. Actionlint passed for
all three changed workflows.
- All 169 original top-level transport test definitions remain
byte-identical. The original file passed all 326 cases in 317.99 seconds
on this macOS host. A clean preliminary split passed the exact same
named cases in 183.01 seconds with the same four-worker cap.
- Preserve the first failed experiment: its parallel run overlapped the
policy suite and timed out in the 1,024-event settled backlog case at 60
seconds; 325 cases passed. A clean repeat passed, but that does not
establish the earlier failure's sole cause. The final layout keeps the
backlog cases in the original file. The final layout passed the same 326
cases in 186.35 seconds (41.4% faster than the 317.99-second baseline).
The original test bodies and all timeouts remain unchanged.
- Fresh-host Rust qualification passed: all 308 compilation units were
fresh, and Cargo completed in 1.59 seconds on a new runner ([final-head
run](https://github.com/paperclipai/paperclip/actions/runs/41745082837/job/126296908816)).
JavaScript setup dropped from 46 seconds cold to 23 seconds with exact
dependency/build restores; all 1,052 shared-package cases passed against
the restored tree ([final-head
job](https://github.com/paperclipai/paperclip/actions/runs/41745082837/job/126295937797)).
The previous qualification also passed (43 to 19 seconds) and confirmed
all three postinstall hooks replayed. These are qualification results,
not an end-to-end PR timing claim.
- Initial full AWS package run passed 4,907 cases with 10 existing
skips. The original-file remainder fell to 118.7 seconds, but the
100-second deadline file started last, leaving the full package at
417.43 seconds. Starting only three long files first passed 4,911 cases
(including four new sequencer guards) in 422.54 seconds, so reordering
alone did not improve full-package time. Summed file execution was 1,202
seconds across three busy workers. The final four-worker configuration
passed **4,912 cases, with the same 10 existing skips**, in **324.99
seconds**, a **23.1% reduction** from 422.54 seconds. The five added
cases guard ordering, selection, sharding, and path handling. Peak
memory was 17.95% of the 16 GiB runner; average CPU was 49.14%
([final-head package
job](https://github.com/paperclipai/paperclip/actions/runs/41745082974/job/126296339480)).
- Local workspace typecheck and build passed. The complete local `pnpm
test:run` passed all six chat partitions but reported failures in one
existing OAuth redirect case and one gateway authority case in the
general-server group. Both focused repetitions passed (three OAuth cases
and one gateway case), and a complete repeat of both files passed all
593 cases in 42.89 seconds; that does not explain the original failures.
The broad local run was stopped after 61 minutes once full final-head CI
had passed; its remaining general/workspace/serialized work was not
completed locally, and it is not reported as a pass. Original failure
logs were retained. Full final-head CI provides the complete suite
validation. Sequencer selection/shard guards and the 11-case transport
suspension smoke passed. The corrected release workflow guard passed all
71 tests.
- The first CI run failed only the policy guard that still expected the
old native cache key; downstream aggregate checks also failed. That
guard now checks the new profile/config prefix and existing
host/target/protocol suffix. A second policy run found that the new
sequencer test imported Vitest in the dependency-free policy lane; it
now runs in the Runner package lane, where all four cases pass. Greptile
reviewed final head `8011f84189` at 5/5 with no actionable findings and
confirmed this issue fixed. Final-head policy, typecheck, build, and
review passed. At 23:52:46 UTC on November 3, several AWS jobs were
terminated together: the Runner package process received SIGTERM and
browser logs recorded a runner shutdown signal. The failed/cancelled
jobs passed on the same-head retry, including required `ci / verify`; no
assertion or timeout was changed in response. The final concurrency
adjustment at `fe5a69bb0b` also received Greptile 5/5 with no actionable
findings. Final-head required CI, including `ci / verify`, passed
([aggregate](https://github.com/paperclipai/paperclip/actions/runs/41745082974/job/126300554711)).
Local timings exclude install, build, cache transfer, and CI queue time.

## Risks

A new Rust key needs one successful canonical master build before
ordinary PRs can hit it. The fresh-host qualification uses separate
PR-scoped entries. Parallel test files can increase I/O contention; case
execution inside each file, timeouts, and assertions are unchanged. Two
extracted suites exceed 1,000 lines because they retain existing
multi-stage recovery fixtures unchanged; one parameterized fixture alone
is 1,059 lines. No production execution logic, lockfile, credentials,
runner trust gates, or required check is removed.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository editing, command
execution, and GitHub inspection. The host does not expose a more
precise model snapshot or context-window size.

## Checklist

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

---------

Co-authored-by: Paperclip <[email protected]>
2026-11-03 20:17:08 -06:00
Nicky LeachandClaude Opus 5.5 11ac90e31f fix(build): typecheck server/src/__tests__ and fix every type error (#18739)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The server has about 1,100 test and helper files in
`server/src/__tests__`
> - `server/tsconfig.json` excludes that directory, Vitest does not
check types, and no script runs the tsconfig in that directory
> - So a test can call a function with the wrong arguments and still
pass, and no tool reports it
> - This gap hid 2,168 real type errors, and some of them hid tests that
did not test what they claim
> - This pull request fixes all of these errors and adds the directory
to the server layers from paperclipai/paperclip#18737
> - The benefit is that a type error in a server test now fails the
typecheck, with no baseline and no backlog

## Linked Issues or Issue Description

Refs paperclipai/paperclip#18737, which is merged and added the server
layers. Refs paperclipai/paperclip#18738, which is merged and checks the
test projects outside `pnpm -r`, so it is not a duplicate. Refs
paperclipai/paperclip#18137, which records that this directory has no
check.

**What happened?**

No tool checked the types of 1,074 of the 1,101 files in
`server/src/__tests__`. I searched `package.json`, the workflows, and
all tsconfig files. 27 files are in the program of another tsconfig, and
these 27 have 0 errors. 11 of the 27 are in
`helpers/maintenance/tsconfig.json`. No script ran that project when I
measured this. paperclipai/paperclip#18738 now runs it.

**Expected behavior**

The server typecheck checks these files, and a type error fails the
check.

**Steps to reproduce**

1. On the base commit, run `tsc -p server/src/__tests__/tsconfig.json
--noEmit`. It reports 6,536 errors and needs 12.3 GiB.
2. Add a type error to any file in `server/src/__tests__`.
3. Run `pnpm --filter @paperclipai/server typecheck`. It passes.

**Paperclip version or commit**

I measured the error breakdown below on commit `8dfd632452`, with
TypeScript 7.0.2. The branch now includes master up to
paperclipai/paperclip#18865.

**Deployment mode**

Not applicable. This issue applies to development and CI. No production
source file in the server changes.

### Error breakdown

Each row adds one change to the row above.

| Step | Total errors | Change | Cause |
| --- | --- | --- | --- |
| Config as committed | 6,536 | | |
| Add `src/types/*.d.ts` | 4,447 | -2,089 | Config. The Express request
types were missing. |
| Set `rootDir` to the repository root | 2,436 | -2,011 | Config. |
| Set `allowImportingTsExtensions` | 2,181 | -255 | Config. Vitest
accepts `.ts` imports. |
| Add `ES2024.Promise` to `lib` | 2,138 | -43 | Config. Node 24 has
`Promise.withResolvers`. |
| Set `allowJs` for `.mjs` imports | 2,148 | +10 | Config. It removes 8
`TS7016` errors and shows new real errors. |
| Set `declaration` to false | 2,142 | -6 | Config. Tests emit nothing.
|
| Count only files in `server/src` | 2,058 | -84 | Other packages: 53
Daytona, 29 Runner test, 2 UI. |

So 4,478 of the 6,536 errors (69%) came from config or scope, and 2,058
are real type errors in 314 files. The 29 Runner test errors are also
real, which gives 2,087 errors. Tests that master added after this
measurement had 81 more errors, so this pull request fixes 2,168 errors.

This table classifies the first 2,058 errors.

| Kind of real error | Errors |
| --- | --- |
| Wrong argument or assignment type | 1,023 |
| Stale or partial fixture objects | 525 |
| Property that the type does not have | 317 |
| Missing narrowing of null or unknown | 101 |
| Implicit any | 60 |
| Missing name or module | 16 |
| Other | 16 |

### What the type errors hid

Most errors were stale fixtures and loose mocks. These errors hid real
test problems, and this pull request fixes them:

- **A test that never ran.** `instrumentation.test.ts` required
`sdk-trace-base`, which is not installed. So 3 span tests were skipped.
They now run and pass.
- **Assertions that could not fail.**
- `issue-thread-interactions-service` used a company ID as an issue ID.
- `status-card-evaluation-work` compared `[]` with `[]` in a rollback
check.
- `slack-conversation-lifecycle` read `item.issueId`, which does not
exist.
  - `email-channels` used a permission key that does not exist.
- `heartbeat-project-env` imported `LOW_TRUST_REVIEW_PRESET`, which is
not exported. The value was undefined.
- **Postgres processes that did not stop.** 11 test files called `stop`
on the test database helper. That method does not exist, and the helper
method is `cleanup`.
- **Arguments that the service ignored.**
- `methodKey` is now `connectionMethodKey` in the Daytona, Unikraft,
1Password, and Railway tests.
- 108 calls passed `"owner-user"` to a parameter that expects an options
object.
- 9 chat wizard fixtures passed `managementContext` as `source`, so they
ran `existing_app` and not `manifest`.
  - 2 tests passed a `{ userId }` actor that the function ignores.
- Inserts into `environments` passed a `companyId` column that the table
does not have.
- **Problems in tests that master added during this work.**
- `sandbox-creation-operations` read `database.connectionString` when an
external test database was set. In that mode `database` is undefined, so
the fixture would stop with a TypeError. It now uses the external URL.
- `idle-completed-history` set a Dot operation to `dispatched`. That is
a Muse status, and the Dot type does not have it. The fixture now uses
`admitted`, which is a real in-flight Dot status.

### Possible product problems, not changed

These may be real problems in production code or types. I did not change
them, because this pull request does not change production behavior:

- `decisions-service` `service.create` does not validate the lenient
`cancel_issue_tree` input.
- `createConnection` ignores `input.connectionPurpose`.
- The `createConnection` route schema default stores `{}` for
`transportConfig`.
- `prepareManagedAiRuntime` accepted the invalid value `mode:
"connection"`.
- `createBinding` accepts invalid `projectionClass` strings outside
class 3.
- `ensureManagedProjectWorkspace` forwards an untyped `repoRef` by
spread.
- `routineVariableSchema.label` is optional, but `RoutineVariable.label`
is required.
- `ToolRunContext.projectId` may be too strict.
- `withdrawInteraction` reads `issue.status`, which its type omits.
- `issueService.list` and `reconcileNativeFinalizations` omit fields
that they return at runtime.
- `createGitHubExternalObjectProvider` handles `tokenProvider: null`,
but its type forbids this value.

## What Changed

- Fixed the 2,168 type errors. The diff changes 322 test and helper
files.
- Corrected `server/src/__tests__/tsconfig.json`. Editors and the
typecheck now read the same options and report the same errors.
- Added three `test-tree` layers to
`server/scripts/typecheck-layers.mjs`. They read the server layers as
declarations, and any error fails them. A hash of the path selects the
shard, so a new file does not move other files.
- Added `server/scripts/typecheck-stubs/daytona-sdk.d.ts`. Server tests
reach Daytona plugin source, and the root install does not have
`@daytonaio/sdk`.
- Changed two imports in `ui/src/lib/issue-execution-policy.ts` to use
`.js` extensions, because server tests import this file.
- Made `declarationsConfig` and `--declarations-only` skip the
`test-tree` layers, because these layers emit nothing. Other projects
read only the source layers. Added a test for this.
- Updated `doc/DEVELOPING.md`.

## Verification

Run these commands:

```sh
pnpm --filter @paperclipai/server typecheck
pnpm exec vitest run server/scripts/typecheck-layers.test.mjs
```

Results on macOS (arm64), Node 24, 16 cores. Each check ran after the
merge of paperclipai/paperclip#18738 unless the row says otherwise:

| Check | Commit | Result |
| --- | --- | --- |
| Server typecheck, `PAPERCLIP_TYPECHECK_LOW_MEMORY=1`, all 7 layers |
`6e45a08573` | Exit 0, 0 errors, 62 seconds, peak 3.52 GiB |
| `node scripts/typecheck-test-projects.mjs --low-memory` | `a3306bd13e`
| Exit 0, 0 errors, 42 seconds, peak 3.25 GiB |
| The same script with `--reuse-declarations` | `1b878aabc0` | Exit 0, 0
errors |
| `typecheck-layers.test.mjs` and `live-recovery-work.test.ts` |
`01db5634e4` | 90 passed. These include real compiler runs for a pass, a
type error, and an invalid tsconfig. |
| `tsconfig.declarations.json` after a full server typecheck |
`0fb1e4fb0a` | It references only `below`, `cycle`, and `above`. No
generated config names a `tsconfig.layers.json` file. |
| Owned runtime test in `idle-completed-history` | `e8f2f448ac` |
Passed, with the fix that master later replaced |
| `idle-incoming-wake`, `plugin-idle-manager`, and the changed
`chat-channels` test | `6e45a08573` | 30 passed |
| Runner `native-execution.test.ts` | `eab4dcc1f4` | 114 passed |
| The 19 server test files that changed since `eab4dcc1f4`, and
`typecheck-layers.test.mjs` | `259737b700` | 4,275 of 4,277 passed. The
2 failures are in `chat-channels` and are in the list of 7 below. |

The peak is the largest single process, from `/usr/bin/time -l`. Peaks
for the server typecheck were between 3.42 GiB and 3.56 GiB across 8
runs. Peaks for the test projects were 3.25 GiB and 3.37 GiB across 2
runs. I did not measure a cgroup.

**Runtime check of the fixes.** Types do not prove that a test still
works. So I ran all 309 changed server test files, 11,929 tests, before
the rebase onto paperclipai/paperclip#18737:

| Result | Tests | Detail |
| --- | --- | --- |
| Passed | 11,913 | Includes the 2 tests and the 54 `dot-runner` tests
that passed in a later run, as described below. |
| Failed | 7 | The same 7 tests also fail on the commit before the
fixes. |
| Skipped | 9 | These tests have their own skip conditions: `sentry` 5,
and 1 each in `bundled-plugin-registration`, `customer-success-cloud`,
`onepassword-connection`, and `workspace-runtime-https-live-exercise`. |

The 7 tests that fail with and without this change, on my machine:

- 3 tests in `chat-channels` and `email-channels` stop with `ENOENT` on
a `SKILL.md` file. The path is in the parent checkout, above the git
worktree that I used, so I expect that the worktree is the cause. I did
not prove this.
- `workspace-instance-cleanup` compares `/private/var/...` with
`/var/...`. This is a macOS symlink difference.
- 2 tests in `git-reproducible-managed-checkouts` get
`unknown_base_git_files` and expect `unpushed_managed_checkout`. I did
not find the cause.
- 1 more test in `chat-channels`, `materializes direct external-chat
finals once`, gets `undefined`. I did not find the cause.

CI is the check for these 7 tests. `ci / verify` passed on `eab4dcc1f4`.

Failures during this work:

- My fix to `agent-runtime-connector-access` changed an invalid
permission key in the setup, but not in the assertion. The runtime check
found this. I corrected the assertion, and the file passes.
- `heartbeat-plugin-environment` failed one wait in the full run. It
passed three times alone, so I think that load caused the failure. It
can fail again under load.
- `dot-runner` skipped all 54 tests in the full run because `cargo` was
not on my `PATH`. It then passed 54 of 54 with the toolchain on the
`PATH`.
- My first `lib` value removed the default DOM types, and the error
count went up by 375. I added the DOM libraries.
- My Daytona stub with bare `any` left 48 errors, because generic
helpers then inferred `unknown`. I added shapes that return promises.
- One fix to `agent-secrets-routes` used `class_3_static_lease`, and the
test failed. That class allows only specific paths. I reverted to
`unclassified`.
- `ci / General tests (server (8/12))` failed on `259737b700` because of
the flaky port in `idle-completed-history`. paperclipai/paperclip#18853
fixed this test on master, so this pull request keeps that fix.

**Audit of the diff.** I counted these patterns in added and removed
lines, to find fixes that only hide an error:

| Pattern | Added | Removed |
| --- | --- | --- |
| `@ts-ignore` | 0 | 0 |
| `@ts-expect-error` | 0 | 1 |
| `.skip` | 0 | 0 |
| `as any` | 9 | 9 |
| `: any` | 11 | 2 |
| `as never` | 8 | 2 |
| `as unknown as` | 35 | 4 |
| `Promise<unknown>` | 16 | 5 |
| `expect(` | 128 | 113 |

No hunk removes more `expect(` calls than it adds. Three removals of
test content were intentional, and I checked each one:

- `muse-control` removed `signalToken`. Line 1012 already asserts that
it is absent.
- `merged-pr-confirmation-sweep` removed legacy aliases. Other tests
cover them.
- `heartbeat-workspace-branch-containment` removed a `finalize` branch
that no test row can reach.

Some partial fixtures stay unchecked through `as unknown as` and
`Promise<unknown>`, mostly in `issue-agent-mutation-ownership-routes`
and `issue-comment-reopen-routes`.

**Accuracy of the check.** One program over the tree and the three
shards reported the same errors at the same positions. Memory, as the
peak of the largest process on one thread, was 3.93 GiB for one program
and 2.43 GiB for the largest shard. I measured these two values before
the fixes.

Not run or not proven:

- I did not run the full `pnpm test:run`, or a root `pnpm build`. I
expect CI to run the full suites.
- I did not run the 4 GiB container check, or the full
`scripts/typecheck-low-memory.mjs`. The peaks above come from macOS, so
they are not a Linux cgroup measurement.
- I did not run the UI typecheck after the merges.
- `workspace-runtime-https-live-exercise` is skipped, so its `cleanup`
fix did not run.

## Risks

- **A large diff in test files.** 328 files change. A pull request that
edits the same tests can get merge conflicts. New tests on master can
also add type errors before this merges. Any such error fails this check
and needs a fix here.
- **A higher cost for changes to source types.** A change to a type can
add errors in test files that the author did not touch. The author must
then fix them. This is the purpose of the check, but it is new work.
- **Changed test behavior.** Some tests now run paths that they did not
run before, such as the 9 chat wizard fixtures and the 3 span tests.
They pass here, but they can be more sensitive to timing or to the
environment.
- **The Daytona stub can go stale.** The stub is written by hand. If the
plugin uses a new SDK member, the typecheck reports an error until
someone updates the stub.
- **The release path.** `release-verify.yml` and `scripts/release.sh`
run `pnpm -r typecheck`, so they now run this check.
- **The tsconfig change.** The test transform reads
`server/src/__tests__/tsconfig.json`. The options that I changed are for
type checks, and the runs above passed.
- **A gap that remains.** Tsconfigs in `packages/paperclip-runner` and
in most sandbox provider plugins exclude `*.test.ts`. This check does
not read most of those test files, so they can still drift. That work is
separate from this pull request.

> 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`) through Claude Code, with
reasoning, tool use, code execution, and subagents. The runtime does not
show 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
- [ ] I have run tests locally and they pass (7 tests fail locally, with
and without this change. 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: Claude Opus 5.5 <[email protected]>
2026-11-03 15:27:38 -08:00
DottaandPaperclip 61e8d5c7eb fix(ci): keep trusted full-ci label runs on AWS (#18855)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Public pull requests need CI without access to private runner
capacity.
> - Trusted CI CODEOWNERS use the AWS runner fleet after identity and
revision checks.
> - The PR caller accepts both `profile-ci` and `full-ci` label events.
> - The runner gate accepted only `profile-ci`, so `full-ci` sent
trusted work to GitHub.
> - This change accepts both labels and keeps the existing authorization
checks.

## Linked Issues or Issue Description

Refs #18821. Observed on #18848 in [run
41711436968](https://github.com/paperclipai/paperclip/actions/runs/41711436968).

**What happened?**

A CODEOWNER added `full-ci` to a trusted PR. The metadata gate ran on
AWS, then sent the test matrix to GitHub with the notice `only the
profile-ci label can request a profiling run`.

**Expected behavior**

Both supported labels keep authorized CI on AWS. A label must not grant
an outside contributor access to AWS.

**Steps to reproduce**

Add `full-ci` to an open PR whose author and event sender satisfy the CI
owner and numeric allowlist checks. Before this change, the runner
decision returns `ubuntu-latest` before it checks those identities.

## What Changed

- Accept `full-ci` and `profile-ci` label events in the trusted runner
gate.
- Test both labels with trusted users, unlisted users, and
numeric-allowlisted users who are not CI owners.
- Document that label selection changes CI scope and preserves runner
authorization.

## Verification

- The new trusted `full-ci` regression failed before the fix with
`ubuntu-latest` and passes after the fix.
- `node --test .github/scripts/tests/ci-routing.test.mjs
.github/scripts/tests/ci-plan.test.mjs
.github/scripts/tests/ci-profile.test.mjs
scripts/__tests__/e2e-shard.test.mjs`: 147 passed.
- `actionlint -shellcheck='' .github/workflows/ci-routing.yml`: passed.
- `git diff --check`: passed.
- This isolated checkout has no installed workspace dependencies.
Repository-wide typecheck, tests, and build are delegated to this PR's
CI; local verification covers the changed routing and its callers.
- A read-only replay against this PR's real GitHub identities and
commits returned GitHub with the old `full-ci` guard and AWS with the
corrected guard. No PR code was executed by the replay.
- Greptile reviewed current head
`47f7159f67c8633830d7732cb68c6d43d8243fe5`: 5/5, successful check, no
unresolved review threads. Both incremental-CI qualification jobs and
commitperclip review passed.
- Full CI is not green. Attempt 1 lost workers to verified Spot
reclamations in `m5.xlarge/us-east-1c`. After excluding that type from
new placements, one failed-job retry retained 19 successful checks and
reran 27 interrupted checks on AWS. The policy check passed, including
these regressions. AWS then confirmed seven more reclaimed
`m8a.xlarge/us-east-1c` workers and two reclaimed
`m8i-flex.xlarge/us-east-1b` workers. The affected checks show runner
shutdowns; unaffected jobs remain running. [Recovery
attempt](https://github.com/paperclipai/paperclip/actions/runs/41714303495/attempts/2).

## Risks

This changes the event allowlist before authorization. The author, event
sender, rerun actor, repository, CODEOWNERS, numeric allowlist, and
immutable revision checks still apply. Unrelated labels remain denied.
Separate helper workflows that explicitly choose GitHub runners are
outside this change.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, shell, Git, and GitHub API
tools. The runtime does not expose a more specific model build 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
- [ ] 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 <[email protected]>
2026-11-03 15:09:36 -06:00
DottaandPaperclip 2a16dcf629 fix(workspaces): preserve shared directory trees during enrollment (#18835)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A volume-free web server needs existing task files in durable object
storage.
> - Historical workspace records can share a physical folder or contain
other task folders.
> - A task-level migration hold cannot fence every alias, and capturing
a parent recursively duplicates nested task data.
> - Offline enrollment imports children first and verifies their durable
coverage before omitting them from a parent archive.
> - Snapshot retention preserves those child versions for as long as a
covering parent depends on them.
> - Existing files, task bindings, and archived states remain intact
through the storage migration.

## Linked Issues or Issue Description

Builds on merged #18826.

**What happened?**

The per-workspace importer could capture a shared project folder while
another workspace record still owned the same path. It also recursively
included runtime-created nested task workspaces in each parent import.
Packaged enrollment rejected existing workspace data instead of invoking
its migration owner.

**Expected behavior**

Require offline maintenance for overlapping paths. Import nested tasks
before their parents, verify every excluded directory against its saved
snapshot, preserve dependencies through retention, and resume
interrupted imports without replacing existing remote authority.

**Steps to reproduce**

Create a project workspace and another task workspace below its
`.paperclip/worktrees/` directory. Try importing either while ordinary
writers are running, then run offline enrollment. Interrupt a parent
import after upload and resume it.

## What Changed

- Add physical-overlap admission checks and an offline entry-point
guard, including global running and queued executions.
- Import pending records in leaf-first pages through the existing
migration owner.
- Verify nested snapshots by downloading, restoring, and comparing the
original source before and after parent upload; check transitive
dependencies.
- Publish parent dependency receipts under child snapshot row locks.
Retention pins those children and revisits them when the parent is
deleted.
- Index imported-child dependency lookups with a partial GIN index; a
PostgreSQL query-plan regression covers 2,500 unrelated snapshots.
- Propagate cancellation through capture and object storage operations
and preserve existing retry journals.
- Wire workspace migration into packaged maintenance and document its
preservation contract.

## Verification

- The original overlap and duplicate-parent regressions failed before
implementation.
- 73 migration and retention tests pass against real PostgreSQL and
files, including interrupted uploads, resumed batches, equal-path
aliases, changed or missing child data, nested descendants, and active
or queued executions.
- Six S3-compatible storage integration tests pass, including migration
through the maintenance owner and restoration of both parent and child
after deleting the original filesystem.
- The standalone process case was initially skipped because the worktree
had no bundle. After rebuilding the server and runtime bundle, that test
passed against PostgreSQL and S3: authenticated ready/export receipts,
preserved decision secret, blocked application routes, and clean SIGTERM
shutdown.
- Server typecheck, server build, runtime bundle build, and
module-boundary checks passed. Full repository CI passed on
97136a20b9 after targeted retries of
infrastructure-interrupted jobs.
- Review fixes resolve physical paths through symlinked ancestors and
persist each parent journal’s original capture scope, including
pre-change journals. All three new regressions failed before their
fixes. An initial abort/retry test exposed JSON object-key ordering in
the saved scope; deep equality fixes that case, and the full migration
suite passed.
- Three new fixtures initially omitted required workspace fields; one
also omitted its task binding. The fixtures now satisfy the existing
contract; production validation was not relaxed.
- The parent PR's full local suite stopped on a chat retry-notice
timeout and a following fake-timer failure. All four relevant cases
passed separately without source changes; this is not a claim that the
full local suite passed.
- No staging enrollment or live rolling-deploy result is claimed by this
PR.

## Risks

The offline flag is an internal maintenance contract: Cloud must
actually stop every predecessor deployment before starting the process.
Deep or large trees require additional object downloads and source
comparisons and may exceed one maintenance window; import journals
support retry. Dependency lookups are company-scoped and event-driven,
with no added idle polling. Unavailable archived sources and unresolved
recovery/copyback still block migration and require separate owner work.
Unsupported local providers and cleanup_failed workspaces deliberately
remain enrollment blockers: filtering them from both the batch and
eligibility would abandon local files when the volume is detached. Two
regression tests prove that rejected records keep their files and do not
acquire remote authority. Original folders and the original volume
remain retained. The additional review request to ignore parallel-write
policy, stale task ownership, or a missing task binding in offline mode
is intentionally not applied: stopping deployments does not establish
those semantics. #18841 documents the separate owner actions and adds
three offline regressions preserving their files and blocking detach.
The batch reports the first failed workspace and supports journaled
retry; it does not promise aggregate diagnostics.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository tools, and code
execution. This session does not expose a more specific serving model ID
or context-window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used with available version and
capability details
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have described the issue in-PR using the bug template
- [x] I have not referenced internal or instance-local issues or links
- [x] My branch name describes the change and contains no internal task
ID
- [x] I have run the focused tests locally and they pass
- [x] I have added tests for the changed behavior
- [x] I have updated relevant documentation
- [x] I have considered and documented risks
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address reviewer findings before merge

---------

Co-authored-by: Paperclip <[email protected]>
2026-11-03 14:50:14 -06:00
DottaandPaperclip e0276e0166 fix(workspaces): identify remote command timeouts (#18827)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Remote workspaces prepare repositories and save files through
sandbox commands.
> - Each command has a timeout and a known operation.
> - The transport discarded that information when a command timed out.
> - A failed clone then appeared as the same generic error as other
failures.
> - This pull request keeps the operation and timeout in a fixed
diagnostic.
> - Operators can identify the failed stage without exposing command
output.

## Linked Issues or Issue Description

**What happened?**

A remote workspace setup command reached its 300-second transport limit
before provider work started. The workspace layer reported only
`remote_workspace_operation_failed`. That message concealed the timeout
and the affected operation. A later full clone on the same sandbox image
passed in 107 seconds, so the original clone delay remains unexplained.
This PR repairs the loss of diagnostic information. It does not claim to
prevent the underlying delay.

**Expected behavior**

A transport timeout must remain a failure. Its diagnostic must identify
the operation and configured limit. It must exclude raw output and must
not automatically repeat a command whose remote outcome is unknown.

**Steps to reproduce**

1. Return `timedOut: true` from the sandbox command boundary during
workspace preparation or cleanup.
2. Observe that the base implementation reports only
`remote_workspace_operation_failed`.
3. Apply this change. Preparation reports
`remote_workspace_operation_timed_out: prepare (configured timeout
300000ms)`. Cleanup retains its separate 10-second limit.

**Paperclip version or commit**

The regression was reproduced on master base
`367f29478d14003cc5c0f161b7ce8f3164993634`. The fixed current head is
`be45ce6594254bd6b94023a550d8b655dd704bb0`.

**Deployment mode**

Hosted Paperclip with native Runner and Daytona sandbox execution. Local
acceptance also uses disposable PostgreSQL and filesystem commands.

Related work: #18403 introduced remote workspace authority. #18705 added
a fixed full-disk diagnostic. I searched recent clone and
remote-workspace PRs and found no duplicate timeout-reporting repair.
This is a defect fix and does not add a roadmap feature.

## What Changed

- Give each internal workspace command a closed operation name: prepare,
repository, archive helper, capture, restore, or cleanup.
- Preserve that operation and the configured limit in a typed error when
the transport reports a timeout. Do not claim a measured elapsed time.
- Keep other failure diagnostics and exact safe-message checks
unchanged.
- Preserve typed cleanup timeouts in the operation cleanup journal while
retaining generic diagnostics for untrusted errors.
- Add preparation, cleanup, and real-PostgreSQL lifecycle regressions.
Document the timeout and the need to reconcile saved receipts before
recovery.

## Verification

- Red: both transport regressions failed on the base implementation. A
separate real-PostgreSQL cleanup regression reproduced the review
finding: the operation journal replaced a typed timeout with a generic
error.
- Green: all 62 remote transport and archive tests and all 50
real-PostgreSQL workspace lifecycle cases passed. The lifecycle suite
ran with an explicit disposable database; it was not skipped. Command:
`pnpm exec vitest run --project @paperclipai/server
server/src/services/remote-workspace-transport.test.ts
server/src/services/remote-workspace-snapshot-archive.test.ts`.
- Safety: a timeout with exit code zero still fails. Raw stdout and
stderr stay out of the error. A timeout cannot claim the fixed
unfinished-Git diagnostic. No command retry occurs. Existing saved files
remain unchanged. Existing archive, restore, receipt, symlink,
credential-isolation, and full-disk controls pass. The cleanup lifecycle
cases retain the acknowledged snapshot, preserve working files, avoid
duplicate uploads, record only a safe typed timeout, and settle cleanup
on the next normal reconciliation. An untrusted error that mimics the
timeout prefix stays generic.
- Live acceptance: production transport code ran against actual Daytona
SDK 0.203.0 timeouts on the incident image, with 4 CPUs, 8 GiB RAM, and
a 10 GiB disk. A controlled one-second SDK limit produced the generic
baseline error and the fixed exact preparation timeout. The current
`be45ce6594` code was rechecked for both preparation and cleanup. Each
returned the typed error and its own configured limit (300 seconds and
10 seconds) while the controlled SDK fault remained one second. Each
case made one command call. Each disposable sandbox was deleted before
the next case.
- The first live-probe attempt used an incorrect error-name check. It
failed its acceptance check and remains recorded. The probe was
corrected to use the same SDK timeout base class as production. Its
first sandbox was deleted. This was a probe setup failure, not red
evidence for the repair.
- A separate same-image probe ran the real full repository preparation
successfully in 107 seconds. The affected command path is unchanged
except for error reporting. The original task was retried once through
the product on deployed source
`adb49a44346849e751548b2fb640693dbfad4b6f`. The requested memo and its
first document revision persisted after reload and matched the
downloaded attachment. The run succeeded, the task reached Done, its
execution lock cleared, and final workspace save and result commit
succeeded. The diagnostic PR is not deployed.
- Recursive typecheck and build passed on `be45ce6594`. `git diff
--check` passed. The first full-suite attempt stopped before tests
because the offline install had skipped an embedded PostgreSQL native
postinstall. The package's official symlink hydration fixed `initdb`.
- The next Mac run passed 350 chat cases and hit one unchanged 15-second
chat deadline. That case then passed in isolation, and all six full chat
shards passed. The broader Mac matrix was later stopped as partial when
CI results became available. No full single-command Mac pass is claimed.
- Full Vitest file coverage uses the exact successful CI merge tree,
`d264fd98e7fb6eff4a9e6b00d402f88894cc0e57`. Its canonical inventory has
2,899 files. Passing CI covered 2,057 files. The remaining 842 ran
locally through the standard exact-file harness. The inventory and
selection checks show no unassigned files. All four changed source/test
files are byte-identical between the PR head and that merge tree.
- The first remaining-file run passed 771 files. The database group
passed 68 files and reported four deadline/readiness failures in three
unchanged files. Those same three files passed all 12 cases when run
sequentially on Node 24.22.2, matching CI's major version. Assertions,
readiness limits, and test deadlines did not change. The first attempt
used Node 26.4.0 with a parallel UI workload; this does not isolate the
contribution of runtime version versus load. Both attempts remain
recorded.
- [GitHub
CI](https://github.com/paperclipai/paperclip/actions/runs/41699549115)
is green on current head `be45ce6594`: `ci / verify`, typecheck, build,
all selected server/chat/workspace groups, Runner checks, and all eight
browser shards passed. Greptile is 5/5 with no open threads. The prior
P2 was resolved by removing new code comments.
- Structured Paperclip review remains queued because its own previous
accepted result exhausted workspace export recovery. A fresh
current-head request is saved, but normal Retry returns HTTP 409
(`accepted_result_without_new_input`). Its advisory cleanup finding is
fixed in `be45ce6594` and covered by red/green lifecycle evidence. This
check is informational. The branch requires `ci / verify` and `ci /
e2e`, and both pass. The independent review-service limitation remains
recorded; this PR is not deployed.

## Risks

Timeout error text changes. Consumers that compare the old generic
message may need to accept the new diagnostic. No such production
comparison was found in this repository. API shapes, deadlines, retries,
ownership, credentials, and model selection do not change. A timeout
still does not prove that the remote process stopped or that a save
failed before publication. Existing recovery must reconcile that state.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, code execution, and browser
tools. The host does not expose a more specific 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 <[email protected]>
2026-11-03 14:13:16 -06:00
DottaandPaperclip 2035118526 fix(workspaces): preserve archived data during remote migration (#18826)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Remote workspace storage lets the web server run without a
persistent volume.
> - Existing deployments also contain archived and historical task
workspaces.
> - Retention can remove their folders while preserving encrypted
restore points or Git reconstruction records.
> - The importer rejected those records and only accepted the task's
current workspace.
> - This pull request imports verified saved data without reopening or
rebinding tasks.
> - The benefit is preservation of existing work during an explicit
storage migration.

## Linked Issues or Issue Description

Refs #18403 and #17472.

**What happened?**

An explicit remote-workspace import rejected archived records,
historical task bindings, and removed folders with valid retained data.

**Expected behavior**

Import the recorded files or exact Git commit. Preserve archived state
and the task's current binding. Refuse missing, expired, corrupt, or
changed sources.

**Steps to reproduce**

Archive a task directory with a restore point, remove its folder, and
run the remote-workspace import. Repeat with a Git restore point
containing an unpushed commit and staged and unstaged edits. The
previous importer reports an inactive workspace or missing source.

## What Changed

- Preserve lifecycle state and current task binding while importing
archived and historical records. Recheck the binding before publication
and on resume.
- Expose the task-directory owner's validated restore-point reader.
Reuse it for ordinary restoration and migration.
- Restore archived directories and Git data into private scratch space.
Support saved Git recipes and exact legacy branch-deletion receipts.
- Recover pruned unpushed Git commits from the encrypted bundle and its
explicit prerequisites.
- Keep independent snapshot download, restoration, source checks, writer
guards, encryption, and atomic publication.
- Document the supported import sources and the remaining
tenant-enrollment gaps.

## Verification

- Before production edits, two new regressions failed on archived status
and historical task binding.
- Final focused verification: 58 tests pass across remote-workspace
migration and isolated task-directory restoration.
- Real PostgreSQL, Git repositories, encrypted storage, and restored
file bytes cover cold restoration after removing the old folder and
source object, pruned unpushed history, index and worktree edits,
corrupt/missing/expired sources, binding changes, and source-folder
races.
- A Git fixture initially lacked runtime ownership metadata. Correcting
the fixture made the valid retained-source case pass; no production
validation was weakened.
- Full repository typecheck and build passed. Full `pnpm test:run`
stopped in chat integration shard 2 after two failures: a retry-notice
timeout and a subsequent fake-timer error. All four cases in that
retry-notice group passed in a separate focused rerun without source
changes; the complete suite has not passed locally. All exact-head CI
jobs passed. The separate Paperclip Review app check is still queued;
all existing review threads are resolved. Server typecheck passed after
the review fixes.
- Both review findings were reproduced: the real cleanup owner's
verified-tip receipt was rejected, and a lost upload reply for a legacy
Git source failed with `operation_content_changed`. The fixes accept the
owner's scoped receipt metadata and rebuild reconstructed Git indexes
from their staged trees. Both regressions now pass, including retained
staged and unstaged edits.
- Additional coverage restores from a fresh HTTP clone after deleting
the original repository, imports a nested local checkout with encrypted
settings, and rejects an unapproved nested source without publishing
remote authority.
- This PR does not claim a completed tenant enrollment or a new
zero-downtime deployment proof.

## Risks

Imports can fail when old receipts or repository objects are
unavailable. They retain their migration journal and leave the original
state intact. Operators must resolve pending writers and recovery before
importing. Shared physical roots, nested worktrees, and unresolved
sandbox recovery require separate migration work. This change does not
remove those blockers or alter automatic retention.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository tools, and code
execution. This session does not expose a more specific serving model ID
or context-window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with 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 linked existing issues or described the issue in-PR
following the relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change and contains no internal task
ID or instance-derived details
- [x] I have run the focused tests locally and they pass; full
typecheck/build passed and the full-suite failure is documented
- [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 <[email protected]>
2026-11-03 13:36:22 -06:00
Nicky LeachandClaude Opus 5.5 8ab89f41e4 fix(build): typecheck test projects in 4 GiB (#18738)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Contributors and agents typecheck this repository, often on machines
with limited memory
> - paperclipai/paperclip#18737 made `pnpm -r typecheck` fit in 4 GiB
because the server now checks in layers
> - The test projects outside `pnpm -r` still import server source
through relative paths, so each one checks most of the server as one
program
> - These projects peaked between 6.9 GiB and 14.9 GiB, so they fail on
a 4 GiB machine
> - This pull request makes these projects read the server layers as
declarations, and adds a CI step that keeps them that way
> - The benefit is that every TypeScript project in the repository can
now be checked in 4 GiB, and a later change cannot silently undo this

## Linked Issues or Issue Description

Refs paperclipai/paperclip#18737, which is merged. This pull request
builds on the server layers that it added. No issue exists for this gap.

**What happened?**

`tsc -p tests/runner-e2e/tsconfig.json` peaked at 14.9 GiB at default
settings. `tests/speko-native` peaked at 12.2 GiB,
`server/src/__tests__/helpers/maintenance` at 9.6 GiB, and
`tests/lifecycle-baseline` at 6.9 GiB. On one thread, `tests/runner-e2e`
still peaked at 7.7 GiB.

**Expected behavior**

Each project passes with 4 GiB of memory.

**Steps to reproduce**

Run `tsc -p tests/runner-e2e/tsconfig.json` in a Linux container with
`--memory=4g --memory-swap=4g`. The kernel stops the process.

## What Changed

- Added `tsconfig.layers.json` to the four projects. Each file extends
the project's `tsconfig.json` and references the generated server
declarations.
- Added `scripts/typecheck-test-projects.mjs`. It builds the server
declarations and then checks the named projects, or all four. It takes
`--low-memory`.
- Changed `test:e2e:runner:typecheck` and
`test:lifecycle-baseline:typecheck` to call that script.
`docker-runner-check.yml` calls the first script by name, so that
workflow needs no edit.
- Added `--declarations-only` to `server/scripts/typecheck-layers.mjs`.
The script now also generates `tsconfig.declarations.json`, so
hand-written configs do not name individual layers.
- Exported `runPnpm` from `scripts/typecheck-low-memory.mjs`, so the new
script can use it.
- Added a step to the `Typecheck + Release Registry` job. It runs
straight after the server typecheck and reuses its declarations through
`--reuse-declarations`.
- The script now fails if a project loads server source, even when the
types pass. Test support files, declaration files and JSON files do not
count. This check does not enforce a memory limit.
- Added a unit test that fails if a new project under `tests/` is
neither in `PROJECTS` nor named as a single program.
`tests/runner-acceptance` is the one named project.
- Added `server/src/types/express.d.ts` to
`tests/lifecycle-baseline/tsconfig.json`. That plain config failed alone
with 11 TS2339 errors, and it now passes.
- Exported `isTestFile` from `server/scripts/typecheck-layers.mjs`, so
the source check uses the same rule as the layers.
- Turned off `declaration` in the layered configs. These projects emit
nothing, and declaration checks gave a false TS2883 error when
`tests/runner-e2e` read the server as declarations.
- Added tests, one of which runs the real compiler. Updated
`doc/DEVELOPING.md` and `tests/lifecycle-baseline/README.md`.

The references are not in each project's `tsconfig.json` because Vitest
reads that file. A test showed that Vitest fails when a referenced
config does not exist yet. The `tsconfig.json` files do not change, so
editors and Vitest see the same configs as before.

No new `package.json` scripts were added, as `.greptile/rules.md`
requires.

## Verification

I tested commit `9763880fab`, which is based on `1c9ca5829f`. I copied
the commit into a named Docker volume and ran a fresh install. Each row
ran in its own Linux arm64 container with `--memory=4g --memory-swap=4g`
and 8 CPUs. The first row started with no build output. All rows used
`--low-memory`. CI does not set a memory limit. I used the limit only
for this measurement.

| Project | Errors | Peak (cgroup) | Max process RSS | OOM kills | Time
|
| --- | --- | --- | --- | --- | --- |
| `tests/runner-e2e` | 0 | 3.34 GiB | 3.06 GiB | 0 | 57 s |
| `tests/speko-native` | 0 | 3.60 GiB | 3.27 GiB | 0 | 51 s |
| `tests/lifecycle-baseline` | 0 | 3.43 GiB | 3.15 GiB | 0 | 50 s |
| `server/src/__tests__/helpers/maintenance` | 0 | 3.39 GiB | 3.13 GiB |
0 | 52 s |

`PAPERCLIP_TYPECHECK_LOW_MEMORY=1 pnpm test:e2e:runner:typecheck` and
`PAPERCLIP_TYPECHECK_LOW_MEMORY=1 pnpm
test:lifecycle-baseline:typecheck` also passed in the same limit, with
peaks of 3.55 GiB and 3.52 GiB.

I also ran the CI sequence in one container with the same limit: `pnpm
--filter @paperclipai/server typecheck`, then `node
scripts/typecheck-test-projects.mjs --low-memory --reuse-declarations`.
Both passed, with a peak of 3.46 GiB. The new step took 16 s.

The server layer build sets the peak in each row. Against the
declarations, the projects peaked between 0.9 GiB and 2.2 GiB at default
settings on macOS.

Server source files that each project loads, without test support,
declaration or JSON files:

| Project | Plain `tsconfig.json` | `tsconfig.layers.json` |
| --- | --- | --- |
| `tests/runner-e2e` | 1,345 | 0 |
| `tests/speko-native` | 1,647 | 0 |
| `tests/lifecycle-baseline` | 1,343 | 0 |
| `server/src/__tests__/helpers/maintenance` | 1,716 | 0 |

I counted these on `3f0f31b2f9`. The source check in the script
confirmed 0 for all four projects on this commit.

**Limits of this proof:**

- Without `--low-memory`, the 4 GiB run does not pass. The kernel
stopped the layer build for `tests/runner-e2e` on `3f0f31b2f9`, and the
script exited with status 1. I did not repeat this run on the final
commit. The script does not turn on low-memory mode by itself.
- The Linux runs used arm64, not x64.
- The new CI step has not run in CI. `pr-trusted.yml` runs from
`@master`, so the step first runs after this pull request merges. Check
its first run.

**Other checks on this commit:**

- `server/scripts/typecheck-layers.test.mjs`: 38 tests pass. One test
uses the real compiler to show that the source check tells a layered
config from a config with no references.
- With the references removed from
`tests/speko-native/tsconfig.layers.json`, the script exits with status
1 and reports 1,644 server source files.
- With the declarations removed, `--reuse-declarations` exits with
status 1 and reports TS6053.
- A type error in a test project makes the script exit with status 1.
- `tests/lifecycle-baseline/tsconfig.json` passes with 0 errors.
- `tests/lifecycle-baseline` Vitest suite: 533 tests pass.
- Workflow tests that read `pr-trusted.yml` and CI routing: 114 tests
pass.

**Earlier checks:**

- `server/src/__tests__/server-package-build-script.test.ts` passed 10
tests on `3f0f31b2f9`. This test failed in `ci / General tests (server
(9/12))` on an older head. It tested a build script split that the final
#18737 removed.
- A changed signature in `server/src/services/run-liveness.ts` failed
`tests/lifecycle-baseline/authority.test.ts` through the rebuilt
declarations, before the rebases. When I restored the signature, the
check passed.

**Not run locally:**

- `pnpm test:run` and `pnpm build`. CI covers these.

**Failures that this pull request does not cause:**

`pnpm test:e2e:runner:unit` has 13 failures on this commit, in
`cursor-cloud-replay-fault`, `first-task`, `operating-receipts`,
`pi-controls-evidence` and `workflow-security`. The same 13 tests fail
on `master` at `6ef37398c8`. A comparison of the test names shows no
differences. PR CI does not run this suite.

## Risks

- Low risk to runtime behavior. No shipped code changes.
- **Slower check:** `pnpm test:e2e:runner:typecheck` now builds the
server layers on each run. Before the rebase, it took 30 s on macOS at
default settings, about the same as the old single program for that
project. On this commit it took 57 s in low-memory mode in the 4 GiB
container. The new CI step reuses the declarations, so it took 16 s.
- **Narrower check:** Server source is now checked only by the server's
own layers, not again under each project's compiler options. The
projects also skip declaration-portability checks. These projects emit
nothing, so those checks had no output to protect.
- **Unchanged path:** `tests/runner-e2e/native-completion-checks.mjs`
still runs `tsc -p tests/runner-e2e/tsconfig.json` as one program. It
needs about 15 GiB at default settings. I did not change it because that
file and its inputs are part of a fingerprinted source contract, and no
workflow calls it.
- **Layer growth:** The new checks do not measure memory. If the layers
or the import cycle grow, the checks still pass and the peak rises. This
is intentional. Crossing 4 GiB does not fail a build.
- **Project list:** The CI step checks only the projects in `PROJECTS`.
The unit test covers new projects under `tests/`, but not a new project
in another directory.
- **Reused declarations:** `--reuse-declarations` trusts the
declarations on disk. CI uses it straight after the server typecheck on
the same commit. If that typecheck fails, the job stops first. Stale
declarations can pass locally, so `doc/DEVELOPING.md` warns against
this.
- **Job dependency:** A type error in a test project now fails
`Typecheck + Release Registry`, and so `ci / verify`.
- **Not covered:** The new step runs no tests. `node
scripts/typecheck-low-memory.mjs` and the Docker image build also have
no new guard.

> 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

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

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-03 11:09:12 -08:00
DottaandPaperclip 4a38ff9c92 fix(ci): make Cloud readiness reruns and fixtures reliable (#18823)
## Thinking Path

> - Paperclip manages agent work and preserves task state.
> - Cloud readiness verifies an exact source revision before release.
> - Its workspace tests must wait for execution and database cleanup to finish.
> - Two attempts hit the same ten-second polling limit and read a running row.
> - The second attempt also lost AWS runners and exceeded a fixture cleanup limit.
> - This pull request waits for owned execution, bounds client shutdown, and gives verification reruns an independent runner pool.
> - Assertions, source identity and required verification gates stay in place.

## Linked Issues or Issue Description

**What happened?**

[Cloud readiness run 41684102136](https://github.com/paperclipai/paperclip/actions/runs/41684102136) failed twice on master `367f29478d14003cc5c0f161b7ce8f3164993634`.

Attempt 1 failed `heartbeat-workspace-busy.test.ts:473`: expected `succeeded`, received `running`. Attempt 2 failed the same helper in the inherited-workspace resume case at line 744. The helper polls for ten seconds, then returns the active row as if the wait finished.

Attempt 2 also exceeded the 30-second `workspace-preparation.test.ts` teardown limit. Its client close had no deadline, and database plus directory cleanup shared the default hook budget despite the fixture's existing 90-second startup budget.

Other attempt-2 failures show runner shutdown or lost communication. Several shutdown signals arrived at 18:05:17 UTC. Read-only AWS inspection confirms that all eight infrastructure-failed instances carry `runs-on-workflow-job-interrupted=true`. Five have explicit `Server.SpotInstanceTermination` reasons, including the server-1 runner that lost communication. Three were terminated through the fleet's shutdown path. CloudTrail records the Rust runner's interruption tag at 18:03:37 UTC and its termination by the RunsOn fleet role at 18:05:31 UTC, despite EC2 showing a user-initiated termination. Repeating the entire workflow reused the same Spot fleet.

**Expected behavior**

Wait for owned heartbeat execution before reading final state or scheduled retries. Bound database-client shutdown and allow the fixture's cleanup cost class to finish. Rerun verification on GitHub-hosted runners after an interrupted fleet attempt. Preserve all checks and report prior failures.

**Steps to reproduce**

1. Delay the test adapter beyond the helper's ten-second polling window.
2. Invoke the ordinary local shared-workspace run or resume the deferred sandbox run.
3. Observe the old helper return `running` before the execution completes.
4. Evaluate the old runner expressions with `github.run_attempt = 2`; they still choose the interrupted AWS pool.

**Paperclip version or commit**

Base: `367f29478d14003cc5c0f161b7ce8f3164993634`.

**Deployment mode**

GitHub Actions Cloud readiness, with disposable PostgreSQL fixtures.

Related: #11293 fixed the earlier gap between cancellation and retry-row insertion. This change waits for the complete execution instead. Duplicate searches covered the workspace test, retention fixture and runner shutdown reports.

## What Changed

- Replace both workspace-test polling helpers with the existing heartbeat completion barrier. Keep final status, retry and coordination assertions.
- Bound workspace-preparation client shutdown to one second. Apply the existing embedded-PostgreSQL fixture budget to its teardown.
- Use AWS only for the first exact-source attempt. Route later verification, chaos and cloud-migrator planning/build attempts to GitHub-hosted runners. The publisher keeps its existing routing. The final branch also incorporates upstream #18821's CODEOWNER routing fix.
- Add runner-expression coverage for numeric, string, missing and later attempt values. Document retry routing.

## Verification

- The original workflow's failed assertions and interrupted jobs remain linked above. A local two-suite run also passed all 157 assertions and failed only the original 30-second teardown hook.
- All 341 workflow contracts pass. After the master synchronization, all 347 routing and CI-planner controls pass. Actionlint passes for the three changed workflows.
- A matched delayed-adapter comparison uses disposable PostgreSQL 17 databases, the same 11-second adapter delay, and the same 90-second experimental outer budget. The old helper fails with `running`; the completion barrier passes. Production test deadlines stay unchanged.
- Earlier experiments hit a 30-second experimental outer limit and the macOS host's shared-memory limit during native database startup. The completed comparison uses task-owned databases on an existing test server. Those earlier attempts are not counted as passes.
- All 31 workspace-busy cases pass with normal test deadlines. The 126-case workspace fixture also passes with its 90-second teardown budget.
- Final-head cleanup measurements: main client close 7 ms, owned database cleanup 20,938 ms, and workspace-home removal 826 ms. A measured fixture held 124,494 PostgreSQL files (208 MiB) after repeated table resets. The measured database cleanup alone consumes most of the old 30-second hook budget. The separate baseline reproduced a timeout at that hook; the fixture now uses its established 90-second cost class.
- Repository typecheck and build pass on synchronized head `2aceb3abd144a569efddabed3b7e0550ea23ceb2`.
- All 157 focused cases pass on the synchronized branch, including complete PostgreSQL 18 fixture shutdown. [Full hosted CI](https://github.com/paperclipai/paperclip/actions/runs/41697804471) passes on the final head, including the required verification and E2E aggregates, all server/chat/workspace groups, Runner lanes and all eight browser shards. The separate GitHub quality-review workflow also passes. All 60 reported checks are terminal: 54 pass, three are informational neutral results, and three are expected skips. There are no failures or pending checks. The complete local `pnpm test:run` command was not run; targeted fixtures already encountered this host's shared-memory limit. Hosted CI supplies the full Vitest and browser matrix.
- Greptile and Paperclip Review both give synchronized head `2aceb3abd144a569efddabed3b7e0550ea23ceb2` 5/5. There are no open review threads.
- Module-boundary and whitespace checks pass.

## Risks

- Hosted reruns can have different cache hit rates and queue times. They execute the same source and verification commands.
- The first attempt retains the existing AWS Spot fleet policy. AWS evidence establishes several interruption-triggered failures; later verification attempts use an independent pool. This does not change the fleet's external capacity policy.
- The completion barrier waits for owned background execution. A hung execution still fails the test's outer deadline.
- The larger teardown budget remains finite. PostgreSQL's bounded stop protocol and cleanup errors still fail the hook.
- No production execution, database schema or provider contract changes.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository inspection, code edits and test execution. The runtime does not expose the exact serving model ID or context-window size.

## Checklist

- [x] I have included a thinking path that traces from project context to this change
- [x] I have specified the model used (with version and capability details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [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 <[email protected]>
2026-11-03 13:08:28 -06:00
DottaandPaperclip 83b5f7d486 fix(ci): keep CODEOWNER PRs on AWS as master advances (#18821)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Its PR checks use reserved AWS capacity for authorized maintainers.
> - The routing gate waited for GitHub capacity and compared an old test
merge with a newer base branch.
> - Normal merges into master then sent trusted PR jobs to the GitHub
queue.
> - This change restores AWS admission and validates the immutable merge
selected for the run.
> - Current CODEOWNER and rerun identity checks still control PR
execution.

## Linked Issues or Issue Description

Related: #18376 moved the gate to GitHub-hosted capacity. #16042
introduced reserved AWS admission.

**What happened?**

In PR #18811, GitHub created test merge
`608d862ff1650db108a3832a2c67f8795924f0e7` at 17:17 UTC. Master advanced
at 17:25. The gate started at 17:30 and rejected the older merge parent
because it did not contain the newer master tip. The authorized PR then
selected GitHub-hosted runners. The gate itself also needed a
GitHub-hosted slot.

**Expected behavior**

Authorized maintainers use reserved AWS admission and PR workers. Normal
base advancement does not change runner placement. Current owner checks,
PR head checks, run-attempt checks, and base-history validation remain
required.

**Steps to reproduce**

1. Start a PR run whose author and sender are current CI CODEOWNERS.
2. Advance master while its routing job waits.
3. Run the gate against the original event merge and the current base
tip.
4. Before this change, the gate selects `ubuntu-latest`. After this
change, it selects the public PR AWS fleet.

**Paperclip version or commit**

Reproduced on master `367f29478d14003cc5c0f161b7ce8f3164993634`.

**Deployment mode**

GitHub Actions with RunsOn Fleet on AWS.

## What Changed

- Restore the reserved routing fleet for the synchronized maintainer
admission roster.
- Check the immutable event merge and its PR head. Accept base snapshots
between the event base and the current base tip.
- Allow GitHub to regenerate its live merge ref while the original run
waits.
- Retain stacked-base validation and all current CODEOWNER, repository,
author, sender, and rerun-attempt checks.
- Add advancement, regenerated-merge, unrelated-history, and
admission-placement regressions, including a stacked parent that
descends from the event base but diverges from the live base.
- Align the developing and CI profiling guides with reserved AWS
admission and current CODEOWNER checks before PR execution.

## Verification

- `node --test .github/scripts/tests/ci-routing.test.mjs
scripts/__tests__/e2e-shard.test.mjs`: 103 passed.
- `node --test .github/scripts/tests/ci-profile.test.mjs
.github/scripts/tests/ci-plan.test.mjs`: 31 passed.
- `actionlint .github/workflows/ci-routing.yml`: passed.
- `git diff --check`: passed.
- Mutation check: removing the snapshot-to-live-base comparison in a
temporary workflow copy makes the new side-branch regression fail,
proving it protects that ancestry guard.
- [Greptile:
5/5](https://github.com/paperclipai/paperclip/pull/18821#issuecomment-6612170970)
with a successful check on `abd6a8ce347228219d8e5127f34f57151788830e`;
both review threads are resolved.
- A regression reproduction on the original workflow selected GitHub
instead of AWS. The patched workflow passes that scenario.
- A read-only execution of the patched routing step against the actual
event for run
[41682867664](https://github.com/paperclipai/paperclip/actions/runs/41682867664),
using live GitHub metadata, selected
`runs-on/fleet=paperclip-public-pr-x64/env=public-ci`. This did not
allocate a runner or rerun CI.
- Full repository typecheck, Vitest, and build were not run in this
worktree, which has no installed dependencies. The change is limited to
workflow routing, its tests, and documentation. Broader CI checks remain
queued.

## Risks

- A stale admission roster can allocate the five-minute metadata gate.
The gate still checks current CODEOWNERS before it authorizes any PR
code on AWS.
- The routing runner group must authorize
`ci-routing.yml@refs/heads/master`. The fleet already exists; live
placement still needs confirmation after merge.
- Rewritten base history, changed PR heads, and unavailable identity or
ancestry evidence continue to deny AWS PR execution.
- Existing runs retain their previously resolved workflow. New runs use
this change after it reaches master.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, repository inspection, code
execution, and live GitHub API tools. The runtime does not expose the
exact serving model variant or context-window size.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Required CI checks protect its server and Runner contracts.
> - Recent full runs waited on general server files and the final Runner
lane.
> - That Runner lane ran package tests before native server tests.
> - PR jobs also rebuilt unchanged debug workspace binaries.
> - This pull request overlaps those workloads and reuses exact trusted
build inputs.
> - The benefit is a shorter critical path with the same test coverage
and AWS authorization.

## Linked Issues or Issue Description

Refs #18544, #18773, #18774, #18798, #18799.

**What existing behavior does this improve?**

The wall time of required PR tests and the precision of affected-test
selection.

**Current behavior**

General server and native files use two workers. The second Runner
Vitest lane runs its package shard and then the native server group.
Each Runner job compiles debug workspace units again. A Vite raw import
also retains its consumer for unrelated source edits.

**Proposed behavior**

Use three isolated workers for general server files and four for native
files. Reuse the two Runner slots for a complete package suite and a
complete native server suite. Restore all debug binaries only on an
exact input key. Record raw imports as explicit file inputs.

## What Changed

- Separate the Runner package and native server workloads without adding
CI jobs. Preserve the old command arguments for master-pinned workflows,
local commands, and other shard counts.
- Increase file workers while retaining fork isolation, serial cases,
and the shared-database guard.
- Add an exact debug workspace cache. Derive binary targets from Cargo.
Include source, fixtures, generated inputs, manifests, compiler
identity, flags, Cargo configuration, and checkout path in its key.
Discard incomplete restores. Only verified master pushes can save it.
- Keep older PR checkouts working when they lack the new cache helper.
- Track Vite `?raw` imports as repository file inputs. Keep unknown
loaders, untracked files, and opaque effects conservative.
- Add cache and coverage guards and document the behavior. AWS runner
authorization is unchanged.
- Keep paired test benchmarking available when install hooks correctly
disable materialized dependency reuse. The old benchmark asserted a
cache hit in that safe fallback.
- Wait for active dispatch work to finish before asserting idle reads.
Analyze the two 16,400-row database fixtures before their queries so
they have current planner statistics. Suppress only PostgreSQL notices
inside the cleanup transaction to avoid logging every cascaded table;
warnings/errors remain visible. Row counts, real queries, assertions,
database reset, and timeouts remain unchanged.

## Verification

- 196 affected CI tests passed, including routing, coverage, selection
failure fallbacks, shared-database serialization, and existing/new cache
guards. Final worker/cache adjustments also passed their focused checks.
- `pnpm build` and `pnpm -r typecheck` passed locally. Full test
coverage is being verified by the Linux CI profile; `pnpm test:run` was
not repeated as a full serial local run. The paired CI benchmark passed.
Both paired benchmarks passed, and full CI passed again after
integrating newer master commits. The external Paperclip Review app is
the only pending status.
- Actual debug artifacts remained fresh after simulating checkout
timestamps: Cargo completed in 0.82 seconds; the content key was
unchanged. This is local reuse evidence, not a measured GitHub cache
transfer.
- Same real UI inventory for a retention-test-only change: 861 files;
retained selection fell from 413 to 401. All 12 omitted files are
raw-input consumers; edits to their inputs remain covered by regression
tests.
- The first local worker comparison is invalid. Development dependency
links disappeared during its second run. The two-worker baseline also
had two macOS path assertions in company-skills. Neither result is
counted as a successful speedup. Dependencies were restored. A local
repeat was interrupted before producing results. The completed Linux
comparison below supplies the valid paired evidence.
- [Paired Linux
benchmark](https://github.com/paperclipai/paperclip/actions/runs/41671244016):
baseline and candidate ran the same 113 general-server files and 1,777
cases on one host. Wall time fell from 381.29s to 346.89s (9.0%). Both
exited successfully and file/case inventories matched exactly. The
fixture command took 31.77s versus 28.65s. This is one sequential pair,
excluding setup, queueing and the full CI critical path.
- [Repeat paired Linux
benchmark](https://github.com/paperclipai/paperclip/actions/runs/41675496862):
the same 113 files and 1,974 cases took 485.06s in the baseline versus
368.54s in the candidate (24.0%). File and case inventories again
matched exactly. The baseline includes newer master changes than the
first pair, so these are two independent comparisons, not repeated
timings of an identical tree.
- Initial full AWS profile: package suite passed 4,891 tests (289 files,
one skipped); native suite passed 172 tests (9 files, 12 pre-existing
platform/feature skips). Native execution took 239.06 seconds; the older
sample ran 165 native tests in 391.29 seconds. The package lane now
takes 11m01 including setup and debug compilation. This is an indicative
cross-run comparison, not a paired full-CI speedup claim.
- The initial run failed two UI fixtures, one serialized inbox
settlement assertion, and one dispatch idle assertion. The two UI
failures reproduced locally. The fixes preserve authorization,
admission, and zero-idle-read assertions. All four affected files passed
locally: 48/48 tests. Token gates passed. All four also passed in the
next AWS profile. That profile passed every other workload but timed out
the 16,400-row keyed attention fixture at its existing 180s limit; this
is accounted for by the explicit fixture-statistics change. Local
focused comparisons pass both large cases before and after; their
roughly 33s test execution is unchanged locally, so this is not claimed
as an additional speed gain. A full local file run passed both large
cases (7.57s / 18.05s) but hit the unchanged 30s teardown deadline on
its first test while emitting thousands of cascade notices. Cleanup now
suppresses those notices within its transaction; the complete file then
passed 34/34 in 107.72s. The latest-head AWS run passed the previously
failing shard: the keyed bulk case took 40.25s (previously exceeded
180s), the keyless case took 8.02s, and the complete attention file took
89.61s. The 16,400-row fixtures and original timeouts remain intact.
- [Full CI profile before master
integration](https://github.com/paperclipai/paperclip/actions/runs/41675497699)
passed on commit `5858956d4ac2c99c911ad2447b63e1f8fc84e31e`, including
all tests, build, typecheck and final verification. Greptile reviewed
that exact commit at 5/5; the only actionable review thread is resolved.
The separate Paperclip Review app is still queued.
- [Final merged-head
CI](https://github.com/paperclipai/paperclip/actions/runs/41678956312)
passed on `499aa206a6e03bf8f3249692ea731367a000591d`: 48 successful jobs
and one intentionally skipped file-selection job. Backup checkpoint
qualification and legacy security status passed. Greptile reviewed this
exact commit at 5/5. The PR is conflict-free; only the separate
Paperclip Review app remains queued.
- Integrated current master after #18798 and #18799 merged the inbox and
UI fixture fixes separately. Preserved master's inbox settlement fix and
verified its entire file (6/6 tests). Those three files are no longer
part of this PR's diff. Removed the temporary `profile-ci` label after
both successful benchmark pairs; the normal planner still schedules
every job because the CI configuration changes are global inputs.
- Second AWS profile: native tests took 229.97s with the same 172 passes
/ 12 skips; package tests took 427.44s with 4,891 passes / 10 skips. The
complete package job took 10m44, including setup and 79s debug
compilation.
- The PR uses the trusted workflow from master. Candidate worker/layout
code runs now; workflow-level debug restores begin after merge and the
first successful master cache writer.

## Risks

- Extra workers can expose load-sensitive tests. The final full CI
profile passed, but one run is not a long-term flake-rate measurement.
- The cache is an optimization only. Misses rebuild, and test commands
still execute. A new Cargo workspace member requires a cache-contract
review.
- Compiler alternatives remain experiments. Required TypeScript checks
are unchanged.

Typechecker experiment: three sequential checks per tool on identical
built projects, with TypeScript 7.0.2, Bun 1.4.4, and tsrs
0.20.1-ts7.1.0-beta. Median seconds (TypeScript / Bun / tsrs): shared
0.429 / 0.083 / 0.157; UI 2.554 / 0.390 / 0.740; server 9.949 / 1.194 /
1.782. TypeScript and Bun passed all three projects. All tools caught
the same four deliberate assignment, property, nullability, and
generic-constraint errors. tsrs reported one additional server TS2345
diagnostic in the run-dispatch adapter, so it did not pass the project
comparison. These local compiler times exclude generation and build
steps; they are not full CI savings.

## Model Used

OpenAI GPT-6 Codex. Tool use and code execution assisted this change.
The exact deployment identifier 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 <[email protected]>
2026-11-03 11:17:53 -06:00
DottaandPaperclip 8c2502503d fix(github): preserve repository access during PR webhooks (#18805)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Agents need current repository access before they can start work.
> - Managed GitHub access checks run while webhook delivery can update
the same grant.
> - Pull request events update webhook health but do not change
repository permissions.
> - The refresh guard treated those health updates as access changes and
could exhaust its retry.
> - This change keeps repository preparation available during normal
pull request activity.

## Linked Issues or Issue Description

**What happened?**

A pull request webhook can make a managed GitHub repository refresh fail
with `github_access_changed`. Repository discovery then omits the failed
connection. Workspace admission can report that repository access is no
longer available, even after the checkout succeeds.

**Expected behavior**

Pull request activity must preserve valid repository access.
Installation changes and newer access snapshots must still stop a stale
refresh.

**Steps to reproduce**

1. Start a managed GitHub access refresh.
2. Process a pull request edit while the provider request is in flight.
3. Repeat on the bounded retry. The old guard rejects both checks
because `lastWebhookAt` changes.

## What Changed

- Use `accessRevision` and `lastAccessRefreshAt` as the existing access
fences. Stop using webhook delivery time as an access fence.
- Allow the existing commit merge to retain the latest webhook timestamp
and health after a pull request event.
- Exercise real webhook processing and PostgreSQL during refresh. Cover
a pull request event, an access conflict followed by that event, and
installation suspension.
- Document the distinction between access changes and delivery health.

## Verification

- Red: both pull request race cases failed on the old code with
`github_access_changed`.
- Green: all seven focused refresh cases pass. These include the
existing concurrent access-change checks and the new suspension case.
- Command: `pnpm exec vitest run
server/src/__tests__/tool-access-service.test.ts -t 'binds a managed
GitHub identity'`.
- Full affected group: 526 tests pass across the tool-access,
repository, and webhook suites.
- `pnpm -r typecheck` and `pnpm build`: pass.
- Current-head CI: 54 checks pass. The duplicate Paperclip review is
neutral. Two Storybook checks are skipped. Greptile and the completed
Paperclip review both rate this commit 5/5 with no findings. There are
no unresolved review threads.
- The duplicate local `pnpm test:run` did not complete. Its first four
chat shards passed. It was stopped after CI completed on this commit;
the full CI test matrix passed. The complete affected local group passed
separately.
- Retained earlier failure: the first affected-group attempt passed 525
tests and had one socket hang-up in the profile attention feed test.
That test passed alone without changes. The unchanged base tool-access
suite also passed all 498 tests, and the complete fixed affected group
then passed. The cause of the socket closure remains unproven.
- Environment: source checkout on macOS arm64, Node 26.4.0, pnpm 9.15.4,
disposable PostgreSQL. Provider responses and Cloud transport use
controlled fixtures. The webhook handler, refresh logic, transactions,
and saved grant reads are real.
- Live acceptance remains unverified. The available CLI token cannot
list GitHub App installations. The affected staging deployment still
serves the old code. This PR does not deploy or recover the original
task.

## Risks

- Removing an access fence could admit stale permission data. The access
revision and refresh timestamp checks remain. The real suspension
webhook regression confirms that stale provider data cannot restore
removed access.
- This change adds no retry, timer, schema change, or credential
forwarding.

## Model Used

OpenAI Codex, based on GPT-6. The exact runtime model ID and context
window are not exposed in this session. Used reasoning, code editing,
and terminal 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 <[email protected]>
2026-11-03 11:15:49 -06:00
Nicky LeachandClaude 6ef37398c8 fix(build): fit typecheck and image build in 4 GiB (#18737)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Contributors, agents, and image builds run `tsc` to check and
compile the repository.
> - The repository uses TypeScript 7, which is a native binary. Node
heap flags do not limit it.
> - The server is one large program, so one `tsc` process needs about 10
GiB.
> - The operating system stops `tsc` under a 4 GiB memory limit.
> - This pull request checks the server in layers, removes the server
from the CLI program, and removes declarations and checks from the image
emit.
> - The benefit is that the full typecheck and the image `tsc` step now
complete in 4 GiB.

## Linked Issues or Issue Description

I found no duplicate pull request or issue. A search for `tsc`, memory,
typecheck, and OOM found only runtime memory work, which is a different
subject.

**What happened?**

`tsc` used too much memory, and the kernel stopped it with exit code 137
in resource-constrained environments. Measured peaks before this change:

| Command | Peak |
| --- | --- |
| Server `tsc --noEmit` | About 10 GiB |
| Server `tsc` emit, as in the Docker build | About 10.7 GiB |
| CLI `tsc --noEmit` | About 7 GiB |
| `pnpm -r typecheck`, whole process tree | About 10.2 GiB |

**Expected behavior**

The typecheck and the image `tsc` step complete under a 4 GiB memory
limit.

**Steps to reproduce**

1. Start a Linux container with `--memory=4g --memory-swap=4g --cpus=4`.
2. Install the dependencies on the base commit.
3. Run `pnpm -r typecheck`. The kernel stops the process with exit code
137.
4. Run `pnpm --filter @paperclipai/server build`. The `tsc` step needs
more than 4 GiB.

**Paperclip version or commit**

Base commit `09162fe893`, with TypeScript 7.0.2.

**Deployment mode**

Not applicable. This issue applies to development, CI, and image builds.
It does not change the server at runtime.

## What Changed

- Add `server/scripts/typecheck-layers.mjs`. It reads the import graph
on each run and runs one `tsc` process per layer. The layers are the
files below the largest import cycle, the cycle, the files above the
cycle, and the tests. Each layer reads the earlier layers as
declarations.
- Add `scripts/run-tsc.mjs`. It adds `--singleThreaded` when
`PAPERCLIP_TYPECHECK_LOW_MEMORY=1` is set. The server layers and the UI
typecheck use it.
- Add `scripts/typecheck-low-memory.mjs`. It checks one package at a
time in this mode. It has no `package.json` entry, as
`.greptile/rules.md` requires.
- Change the server `typecheck` script to build only the Runner
TypeScript output. It does not build the Runner release binary now.
`pnpm build` and `typecheck:build-gaps` still run the full Runner build
and its generated-file checks.
- Change the `tsc` step of the server `build` script to
`scripts/run-tsc.mjs`. The `Dockerfile` sets `PAPERCLIP_TSC_EMIT_ONLY=1`
for that build, so the image emits JavaScript without checks or
declarations. Without this variable, `build` still does a full check for
npm packages and CI.
- Use a non-literal specifier for the `@paperclipai/server` import in
`cli/src/commands/run.ts`. The import result already had no useful type,
and the literal specifier added the full server to the CLI program.
- Add a layered server typecheck step to the `pr-trusted` typecheck job,
because `typecheck:build-gaps` does not run this script.
- Update `AGENTS.md` and `doc/DEVELOPING.md`.

## Verification

Results on Linux use a clean copy of this branch, a new `pnpm install`,
Node 24, Rust 1.97.1, 4 CPUs, and a 4 GiB memory limit with no swap.
Results before the merge of `master`:

| Command | Result | Peak non-reclaimable memory |
| --- | --- | --- |
| `node scripts/typecheck-low-memory.mjs` | Exit 0, 0 OOM kills | 3,492
MiB |
| `PAPERCLIP_TSC_EMIT_ONLY=1 pnpm --filter @paperclipai/server build` |
Exit 0 | 3,656 MiB, from the Rust release build |
| Server single-program `tsc --noEmit --singleThreaded` | Exit 137, 1
OOM kill | Reached the limit |

Results on macOS on the merged head, with peak memory for the full
process tree:

| Command | Before | After |
| --- | --- | --- |
| `pnpm -r typecheck` | 10.2 GiB, exit 0 | 5.47 GiB, exit 0 |
| `node scripts/typecheck-low-memory.mjs` | Not available | 3.81 GiB,
exit 0 |
| CLI typecheck | 7.06 GiB | 1.68 GiB, before the merge |

Other checks:

- Compiler tests: new tests run the real compiler on a small fixture.
They prove that the layers emit declarations and pass, that a renamed
export fails the layer that imports it, and that a changed type fails
the test layer.
- Declaration imports: a new compiler test proves that types from
`.d.mts` and JSON imports stay correct in later layers. On the real
server, a wrong use of `AgentFileManifest` in the cycle layer failed
with `TS2353`.
- Coverage: a test on the real server source asserts that each source
file is in one layer and that no import goes to a later layer. The test
layer contains the remaining files. `src/__tests__` stays out of the
root list, as in `server/tsconfig.json`.
- Error detection: I added one type error to one file in each of the
four layers. The typecheck reported all four errors and exited with 1.
- Cross-layer errors: I renamed an export in the lower layer. The cycle
and upper layers reported the error and the typecheck exited with 1. I
reverted these temporary edits.
- Emit: full `tsc` and `--noCheck --declaration false --declarationMap
false` gave byte-identical JavaScript and source maps for 2,249 files on
the base commit.
- Runtime: `scripts/server-boot-smoke.sh node
server/dist-runtime/index.mjs` passed with a bundle from the emit-only
build.
- CLI: the esbuild bundle keeps the dynamic import, and `node --check`
passed.
- Tests on the merged head: `typecheck-layers`, `prepare-runner-vendor`,
`cli-invocation-safety`, the CLI `runtime` tests, and all 503
`.github/scripts/tests` tests passed. Before the merge,
`release-verify-workflow` and `runner-release-crate-cache` passed.
- Full `pnpm --filter @paperclipai/server build` passed before the
merge.

Failures during this work:

- The first low-memory run failed in the UI with `TS5023: Unknown
compiler option '-b'`, because the flag came before `-b`. I corrected
the argument order and added a test that runs build mode with the flag.
- The first rename check did not rename the export, because BSD `sed`
does not support `\b`. Its exit 0 was not evidence. I ran the check
again with `perl`, and the result above is from that run.
- The first version of the real-compiler fixture had a `TS2774` error in
its own source. The test found this error, and I corrected the fixture.
- CI failed `server-package-build-script.test.ts` on `1abc5e5a43`,
because that test reads the steps of the server `build` script, and I
had moved them to `build:prepare` and `build:assets`. My first search
for readers of this script did not include `server/src`. I removed those
scripts and `build:image`, restored one `build` script, and kept the
test unchanged.
- `GOMEMLIMIT` did not help enough, and I removed it from the plan. On
these steps, `3GiB` saved at most about 300 MiB. With default checkers,
the cycle layer was stopped with and without it, and the lower layer
went from 7 seconds to 151 seconds.

Not run or not proven:

- I did not run the full `pnpm test:run`, a root `pnpm build`, or a full
`docker build`. I expect CI to run the test and build gates.
- I did not measure the Docker build steps other than the server build.
- I did not run the 4 GiB checks on x86-64.
- I did not measure the merged head in the 4 GiB container. The macOS
result on the merged head is 3.81 GiB, and the merge added one source
file to the lower layer.

## Risks

- The margin is small. In the 4 GiB container, the cycle layer peaks
about 600 MiB below the limit, and the Rust release build in the image
build peaks about 440 MiB below it. Growth of the largest import cycle
uses this margin first.
- `node scripts/typecheck-low-memory.mjs` applies only to `pnpm -r`
packages. These commands still check the server as one program and need
more than 4 GiB: `pnpm build`, `test:e2e:runner:typecheck`,
`test:lifecycle-baseline:typecheck`, and the `tests/speko-native`
project.
- A local flow that used the Runner binary from a previous typecheck
must now run `pnpm --filter @paperclipai/paperclip-runner build`.
- The image does not include server declarations now. I found no
consumer in this repository. A downstream image that reads
`server/dist/*.d.ts` must change.
- An image build does not find type errors now. A typecheck in CI must
find them before the image build.
- The import scan uses a regular expression. A false edge can only make
a layer larger. A missed edge causes a `TS6307` failure, so it cannot
skip a file.
- The new CI step adds about one minute to the typecheck job.

> 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`) through Claude Code, with
reasoning, tool use, and code execution. The runtime does not show 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

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

---------

Co-authored-by: Claude <[email protected]>
2026-11-03 08:53:35 -08:00