## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Cloud releases wait for source verification before deployment.
> - That verification reuses compiled Rust dependencies to finish
sooner.
> - PR jobs save large pnpm stores under separate merge refs and
different lockfile keys.
> - Those copies compete with master build caches for the repository's
10 GB cache limit.
> - This PR makes PR dependency caches restore-only and reuses
master-compatible keys.
> - A separate pin update will activate the reviewed workflow.
## Linked Issues or Issue Description
**What happened?**
PR merge refs accumulated roughly 700 MB copies of the same pnpm store.
Master Rust caches disappeared, and Cloud readiness run
[34656098157](https://github.com/paperclipai/paperclip/actions/runs/34656098157)
rebuilt dependencies after cache misses. The repository currently has a
10 GB limit. GitHub rejected a request for 50 GB; that setting needs
separate organization/billing access.
**Expected behavior**
PR jobs should reuse downloaded packages without evicting post-merge
compilation caches through duplicate uploads.
**Steps to reproduce**
1. Run several PRs while the checked-in lockfile needs policy
regeneration.
2. Compare the setup-node keys in PR jobs and master jobs.
3. List Actions caches by ref, key, and archive size. The PR keys repeat
across merge refs.
**Paperclip version or commit**
f12b647ae, before this change.
**Deployment mode**
GitHub Actions, with GitHub-hosted and allowlisted AWS PR runners.
Refs #13267 (empty pnpm store prevention). Searched open issues and PRs
for pnpm cache duplication and found no duplicate implementation. This
change leaves the paused capacity documentation PR #13280 alone.
## What Changed
- Replace setup-node cache writes with pinned `actions/cache/restore` in
all seven PR install job definitions.
- Restore against the checked-in lockfile before downloading the
regenerated policy artifact. Keep every install frozen against that
artifact.
- Allow an OS/architecture-specific pnpm fallback and disable automatic
setup-node caching.
- Remove dependency-store caching from the resolution-only policy job.
- Add eight regression tests, update the existing stacked-lockfile cache
assertion, and document cache behavior and storage settings.
## Verification
- Passed 505 workflow, routing, cache, and source-verification tests:
`node --test '.github/scripts/tests/*.test.mjs'
scripts/__tests__/e2e-shard.test.mjs
scripts/__tests__/run-vitest-stable-shard.test.mjs
scripts/__tests__/release-verify-workflow.test.mjs
scripts/cloud-source-verification.test.mjs`.
- Passed `actionlint .github/workflows/pr-trusted.yml` and `git diff
--check`.
- The AWS routing gate is unchanged. Author, event sender, and rerun
actor must still be allowlisted.
- This definition PR does not change the active `pr.yml` pin. After
review and merge, authorize its immutable merge SHA additively and
activate it in a separate PR. Verify a populated restore and no cache
uploads in an allowlisted PR.
- All 32 checks passed or were intentionally skipped on
`44b31eca590f61b75cae646de43c491b6c4deae7`, including full native Runner
verification, application build, server/workspace tests, and browser
shards. Current-head Greptile is 5/5 with no findings. No application
code changes in this PR.
## Risks
- New dependencies present only in a PR may download again on each run
until master saves a cache containing them. Frozen installation remains
the source of dependency resolution.
- Missing or expired stores fall back to normal package downloads.
- Existing PR copies remain until expiry or a separate targeted cleanup.
No cache entries are deleted here.
- The workflow only takes effect after the separate immutable pin
rotation. Storage billing settings are not changed by this PR.
## Model Used
OpenAI GPT-6 through Codex, with reasoning, repository tools, and code
execution. The exact serving model ID and context window are not exposed
by 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 version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Legacy conversation adapters can run in Daytona sandboxes.
> - A server restart during provisioning can occur before the invocation
event exists.
> - Recovery then lacks the old adapter identity and leaves a hold that
ordinary user retries cannot clear.
> - A remote launch can also fail when its host relay looks for Node in
the sandbox PATH.
> - This pull request records the adapter at claim time and restores
explicit user continuation after verified cleanup.
> - Users can recover from the task or inbox while the failed run and
uncertain action history remain intact.
## Linked Issues or Issue Description
Refs #13237, #13239, #13254. Those changes cover recorded conversation
runs, native user continuation, and explicit remote Stop. This change
covers legacy failure before `adapter.invoke` and exact task/inbox
Retry.
Refs #9771 for overlapping generated-command quoting. This change also
supplies the absolute host Node executable. Refs #13163 and #13264 for
the separate native restart and retained-workspace work.
**What happened?**
A legacy Daytona run interrupted during provisioning became
`process_lost` without an invocation event. Recovery preserved an
execution hold, and Retry or a new task reply could not resume it.
Cleanup could also run before the Daytona plugin was ready. On a macOS
host, a subsequent ACP relay launch failed with `env: node: No such file
or directory` because the remote launch environment did not contain the
host Node path.
**Expected behavior**
An interrupted conversation can continue after its previous execution
stops. Explicit Retry and new user replies should start a fresh turn
with the task history. Cleanup failures must remain visible and
recoverable. The host relay must use the host Node executable.
**Steps to reproduce**
1. Use a legacy Claude adapter with a Daytona environment.
2. Interrupt the server after it acquires the sandbox lease and before
it records `adapter.invoke`.
3. Restart and inspect the task hold.
4. Retry from the task or inbox, or send a new task reply.
5. Confirm the old sandbox has stopped and one new response arrives.
**Paperclip version or commit**
Reproduced from master at `3bafac12f796fbea02e609e1074a9639f872e9c4`.
The branch is rebased on `51b0e01ea`, including #13261 and #13270.
**Deployment mode**
Built from source on macOS with a real Daytona sandbox and the legacy
Claude ACP adapter.
## What Changed
- Count new browser specs with the scheduler's median duration in the
shard-balance check. This fixes a false policy failure after new specs
arrive from both branches. The balance threshold is unchanged.
- Persist server-owned adapter identity in the queued-to-running claim
before provisioning starts.
- Wait for provider plugin startup before restart cleanup. Keep failed
cleanup leases as active ownership blockers.
- Admit exact board retries and new user comments after verified
termination. Retain the old run, task history, approvals, and unknown
action outcomes.
- Adopt repeated Retry requests. Permit one scoped cleanup attempt per
explicit user Retry after the automatic limit, with an activity record.
A later user Retry can recover after a transient provider failure;
automatic attempts remain capped.
- Resume replies deferred during cleanup, including historical legacy
startup failures.
- Launch the host ACP relay through the absolute host Node executable.
- Add a task-level Retry button and return actionable blockers when
retry admission is refused.
- Add database regressions and three browser recovery journeys. Exclude
installed third-party dependency skills from the shipped-skill audit.
## Verification
- Current head: `d23c84181`, rebased on `51b0e01ea`. Conflict resolution
retains the saved-message recovery, local stop receipts, and wait
reasons from #13270 alongside exact legacy Retry support.
- Real Daytona: interrupted the server after lease acquisition and
before adapter invocation. Restart cleanup confirmed provider
termination. Task Retry cleared a seeded historical hold and a real
Claude agent returned `Recovery verified.` in the task. Removed the
disposable sandbox and environment after testing.
- All three browser recovery journeys passed again after the final
rebase. Task Retry, Inbox Retry, and a new reply each produced one fresh
successor, completed the task, preserved the failed run, and retained
the answer after reload.
- All 29 e2e/server shard-partition tests passed. The balance check now
uses the scheduler's median fallback for unmeasured specs, with the same
balance threshold.
- Server typecheck passed after rebuilding the generated runner
dependencies. The combined recovery/route run passed 136 of 137 tests.
Its remaining route test timed out during the first cold module import
at its explicit 10-second limit; an isolated rerun reproduced that
timeout and passed the other 51 route cases. The complete CI suite
passed on this head. The same route file passed all 52 cases in CI,
including the first cold import in 7.5 seconds.
- Before the final rebase, recursive typecheck, full build, UI token
gates, 132 targeted server tests, and the complete [CI
workflow](https://github.com/paperclipai/paperclip/actions/runs/34650004085)
passed. The subsequent CI failure was the shard-balance accounting
mismatch fixed here.
- Greptile reviewed `d23c84181` at 5/5 with no outstanding actionable
findings. The complete [current CI
workflow](https://github.com/paperclipai/paperclip/actions/runs/34653327949)
passed on attempt 2. All test, typecheck, build, and canary jobs passed
on the first attempt. Docker setup timed out fetching BuildKit from
Docker Hub; retrying that job and its dependent aggregate succeeded.
## Risks
- Recovery admission changes executable authority. Company, task, agent,
user, approvals, process ownership, and provider termination checks
remain required.
- Explicit continuation starts a fresh conversation with history. It
does not certify unknown external action outcomes or rerun
non-conversation adapters automatically.
- Changing task status alone does not clear an execution hold. The task
now offers an explicit Retry action.
- Historical adapter claims and invocation events take precedence over
current agent settings. Known process or webhook runs retain their hold.
Pre-upgrade rows with no adapter evidence may receive only a new
explicit user turn after termination proof; they do not become eligible
for automatic replay.
- No schema migration or sandbox-image change is required. This branch
has not been deployed to production.
## Model Used
OpenAI GPT-6 through Codex, with repository inspection, code execution,
browser automation, and test execution. The exact deployment model ID
and context window are not exposed in this session.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip uses GitHub Actions to verify changes before release.
> - The Paperclip Runner has a separate verification boundary.
> - The build job currently runs this verification before the workspace
build.
> - This pull request moves runner verification into its own parallel
job.
> - The benefit is clearer CI results and less wait time for independent
work.
## Linked Issues or Issue Description
**What existing behavior does this improve?**
The trusted PR and release verification workflows run Paperclip Runner
verification inside the Build job.
**Subsystem affected**
Cross-cutting (GitHub Actions CI workflows).
**Current behavior**
The Build job runs `pnpm --filter @paperclipai/paperclip-runner
check:all` before it builds the workspace. A runner verification failure
appears as a Build failure. The workspace build cannot run in parallel
with runner verification.
**Proposed behavior**
Each workflow has a `Verify Paperclip Runner` job with the same
checkout, dependency install, and command. The Build job only builds its
required outputs. Both jobs run after the same gate and policy jobs.
**Reason and benefit**
The runner command is an independent verification boundary. A dedicated
job gives it a clear status and allows it to run in parallel with Build.
**Breaking changes**
None. The same runner verification command still runs in both workflows.
## What Changed
- Added a dedicated `Verify Paperclip Runner` job to the trusted PR
workflow.
- Added a dedicated `Verify Paperclip Runner` job to the release
verification workflow.
- Kept the Build jobs independent and retained their existing build
commands.
- Updated the trusted-workflow policy test for the additional
dependency-install job.
## Verification
- Ran `git diff --check`.
- Ran `node --test ./scripts/__tests__/e2e-shard.test.mjs`.
- Ran `pnpm exec prettier --check .github/workflows/pr-trusted.yml
.github/workflows/release-verify.yml`.
- Confirmed both jobs retain their prior runner, dependency, and policy
prerequisites.
## Risks
Low risk. The runner verification job repeats the existing setup. It
adds one parallel GitHub Actions runner to each affected workflow.
## Model Used
OpenAI Codex, GPT-5.6, 128k context window, reasoning and tool-use
capabilities.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - GitHub Actions builds the Docker images that ship Paperclip, and
downstream deployments consume the `-cloud` image variant on every
master merge.
> - PR #12769 slimmed the Docker build context with a broad
`.dockerignore` block for `packages/paperclip-runner`, and the block
also removed three files the image build itself reads.
> - The image build re-runs the runner's generated-file drift checks, so
it found no committed capability contract in the context and failed on
every master commit after the merge.
> - PR CI never runs those checks against the Docker context, so the
pull request stayed green and the breakage only appeared post-merge, on
every image build.
> - This pull request restores the three files with narrow
`.dockerignore` exceptions and adds a PR CI job that runs the drift
checks against the exact Docker build context.
> - The benefit is that image publishing works again now, and the next
context-slimming regression fails the pull request instead of every
post-merge image build.
## Linked Issues or Issue Description
Refs #12769 (the context-slimming change that exposed this) and #12608
(which committed the generated contract outputs the image build checks).
**What happened?**
Every `Docker` workflow run on master failed from 2026-09-04 12:58Z
onward, in both the `build-and-push` and `build-and-push-cloud` jobs.
The failing step reported `Generated contract drift:
generated/capability/capability-contract.md` from
`check:capability-contract` inside `pnpm --filter @paperclipai/server
build`. The committed contract file is current — regeneration on a full
checkout is a no-op. The file was simply absent from the build context:
the new `packages/paperclip-runner/**/*.md` ignore rule strips the
committed drift-check outputs
(`generated/capability/capability-contract.md`,
`generated/capability/downstream-handoff.md`), and the
`packages/paperclip-runner/docs` rule also strips
`docs/capability-contract.md`, which `check:capability-inventory` reads
next in the chain. No cloud image published for eight hours, which
stalled every downstream deployment that consumes the canary images.
**Expected behavior**
The Docker build context must contain every file the image build reads,
and a change that removes one must fail the pull request that introduces
it, not every image build after the merge.
**Steps to reproduce**
1. Check out master at any commit from `af3023f1` onward.
2. Run `docker buildx build -f .github/docker-context-checks.Dockerfile
.` (the probe added by this PR), or start the real `Docker` workflow
build.
3. Observe `Generated contract drift:
generated/capability/capability-contract.md` — while `node
packages/paperclip-runner/scripts/generate-capability-contract.mjs
--check` passes on the same checkout outside Docker.
**Paperclip version or commit**
`d593463ab` (master tip at diagnosis time; first failing commit
`af3023f1`).
**Deployment mode**
GitHub Actions image builds (`docker.yml`), consumed by managed cloud
deployments.
## What Changed
- `.dockerignore`: narrow exceptions (last match wins) re-include the
committed drift-check outputs
(`!packages/paperclip-runner/generated/**`) and the inventory check's
documentation input
(`!packages/paperclip-runner/docs/capability-contract.md`). Every other
exclusion from #12769 stays: no crate declares an explicit `[[test]]`
target, so cargo builds without the `tests` directories, and the image
build chain never runs the excluded smoke scripts.
- `.github/docker-context-checks.Dockerfile` (new): a small probe that
COPYs the real build context — identical `.dockerignore` semantics — and
runs the dependency-independent drift checks inside it
(`generate-capability-contract.mjs --check`,
`check-capability-inventory.mjs`). ajv installs in an isolated directory
for schema validation only; codegen checks such as
`generate-protocol-schema-module` stay out because their emitted bytes
vary with the ajv release and would raise false drift alarms outside the
locked dependency tree.
- `.github/workflows/pr-trusted.yml`: new `docker_context_integrity` job
builds the probe on every full-CI pull request, and the existing
`verify` aggregate now requires its result, so the guard gates merges
through the same required check as the other lanes.
- Activation note: `pr.yml` pins `pr-trusted.yml` by commit SHA, so the
new job starts gating pull requests after the usual follow-up `ci:
activate ...` pin bump once this merges. The `.dockerignore` fix needs
no activation — `docker.yml` reads it directly, so image builds recover
on the first master commit after this merges.
## Verification
- `docker buildx build -f .github/docker-context-checks.Dockerfile .` on
master (before the `.dockerignore` fix): fails with the exact production
error, `Generated contract drift:
generated/capability/capability-contract.md`.
- Same command with the `.dockerignore` exceptions applied: passes,
which also proves BuildKit honors the `!` exceptions, including the file
inside the excluded `docs` directory.
- `node scripts/generate-capability-contract.mjs --check` on a full
checkout: passes both before and after, which confirms the committed
contract was never stale — only missing from the context.
- Static sweep of every script in the image build chain (`build`,
`build:typescript` and their `check:*` steps) against the ignore rules:
the three restored files are the only build inputs the #12769 block
strips.
- YAML for `pr-trusted.yml` lints clean.
## Risks
- Low. The `.dockerignore` exceptions only re-add three committed files
to the build context; image contents do not change otherwise.
- The probe job adds one context transfer and two Node scripts per
full-CI pull request run (about one to two minutes, no dependency
install beyond one isolated ajv package).
- The `verify` aggregate now also requires the new job, mirroring the
existing pattern for the other lanes; on non-full-CI runs the job skips
and `verify` asserts the skip, unchanged from how the other lanes
behave.
- The new job only takes effect for pull requests after a follow-up pin
bump in `pr.yml` (same two-step flow as every `pr-trusted.yml` 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 Fable 5 (Anthropic, model id `claude-fable-5`), extended
thinking, agentic tool use in Claude Code: GitHub Actions log forensics
to isolate the failing check, static analysis of the build-chain scripts
against the ignore rules, and local docker buildx runs to reproduce the
failure and verify the fix.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass (the docker probe, both
failing-before and passing-after; the drift checks themselves on a full
checkout)
- [x] I have added or updated tests where applicable (the probe IS the
regression test for this class)
- [x] I have updated relevant documentation to reflect my changes
(inline comments in `.dockerignore` and the probe explain the invariant)
- [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
## Thinking Path
Paid cells now reuse the AWS image's system Chrome, but Playwright video
recording still resolves its revision-pinned FFmpeg helper from the
Playwright cache. Run 33875618534 proved Chrome qualification succeeds
and then failed before provider startup because that helper was absent.
The same run also exposed that generic lock repair can churn unrelated
package platform metadata, so the automated repair paths need
resolution-only regeneration rather than lockfile-only metadata refresh.
## What Changed
- install Playwright FFmpeg only on the AWS/system-Chrome path
- retry the small helper installation up to three times before provider
secrets are exposed
- keep the GitHub-hosted Chromium fallback unchanged
- bind static coverage to the exact FFmpeg step block and its pre-secret
ordering
- add pnpm `--resolution-only` to all four automated lock-repair paths
while retaining full transitive resolution
- require resolution-only repair in the shared workflow regression
The actual generated lockfile correction remains bot-owned by PR #12828
and is intentionally not committed here.
## Verification
- `node --test
.github/scripts/tests/lockfile-refresh-workflows.test.mjs`
- `actionlint -ignore SC2012` on all modified workflows
- focused Prettier checks
- `git diff --check`
- prior run 33875618534: system Chrome 151 qualified; missing Playwright
FFmpeg was the sole cell startup failure
## Risks
Low. The new network operation is limited to Playwright's pinned FFmpeg
payload, happens before paid credentials are exposed, and leaves the
hosted-runner path unchanged. Resolution-only is still a full
dependency-resolution pass, unlike lockfile-only, while avoiding
unrelated current-platform metadata churn.
## Model Used
GPT-5
Use full dependency resolution in automated lockfile repair paths, add regression coverage, and refresh the stale Rollup snapshot.
Co-Authored-By: Dotta <cryppadotta@users.noreply.github.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: lockfile-bot <lockfile-bot@users.noreply.github.com>
## Thinking Path
> - Paperclip uses GitHub Actions to protect changes before they enter
`master`.
> - GitHub evaluates every pull request in a native stack against the
stack base.
> - The current workflow therefore starts the complete CI matrix for
every layer in a stack.
> - A large stack can queue many copies of the same integrated
verification and delay every pull request.
> - GitHub provides stack position and base metadata so workflows can
select merge-relevant layers.
> - This pull request keeps policy and required check names on every
layer, but runs full CI only for ordinary pull requests, the top layer,
and the lowest unmerged layer.
> - The benefit is much lower CI load without weakening the
required-check contract.
## Linked Issues or Issue Description
**What existing behavior does this improve?**
The trusted pull request workflow currently runs every test, build,
canary, and E2E lane for every pull request in a native stack.
**Subsystem affected**
GitHub Actions pull request verification.
**Current behavior**
A stack with 61 pull requests can start 61 complete CI matrices after a
cascading rebase.
**Proposed behavior**
Run the always-on policy job and stable required-check aggregators for
every layer. Run the complete verification matrix only for ordinary pull
requests, the top stack layer, and the lowest unmerged stack layer.
**Reason and benefit**
The top layer verifies the integrated stack. The lowest unmerged layer
verifies the current merge candidate. Middle layers keep
branch-protection checks without consuming the complete runner matrix.
**Breaking changes**
Middle stack layers no longer run the complete CI matrix. Their `ci /
verify` and `ci / e2e` checks still require the policy job to pass and
require every expensive lane to be intentionally skipped.
## What Changed
- Add a fail-safe stack scope decision to the trusted PR runner gate.
- Run typecheck, general tests, build, serialized tests, canary, and E2E
shards only for ordinary, top, and lowest-unmerged pull requests.
- Preserve the required `ci / verify` and `ci / e2e` names on every
layer.
- Make the required aggregators distinguish valid middle-layer skips
from failures or missing scope decisions.
- Add regression coverage for ordinary, top, bottom, middle, and
malformed stack metadata.
## Verification
- `node --test scripts/__tests__/e2e-shard.test.mjs` — 11 tests passed.
- `actionlint .github/workflows/pr-trusted.yml .github/workflows/pr.yml`
— passed.
- `git diff --check origin/master...HEAD` — passed.
- The caller remains pinned to the current trusted workflow. A separate
activation change must advance the immutable SHA after this pull request
lands.
## Risks
- Incorrect stack classification could skip important jobs. Missing or
malformed stack metadata defaults to full CI.
- Middle-layer required checks depend on the policy job and verify that
all expensive jobs have the `skipped` result.
- The reusable workflow change does not become active until the
immutable caller SHA advances in a separate change.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex with GPT-5. The exact deployment suffix and context
window are not exposed. The model used reasoning, repository tools, code
execution, Git, and GitHub API access.
## 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 <noreply@paperclip.ing>
## Thinking Path
> - Paperclip uses trusted GitHub Actions workflows to verify every pull
request
> - Native stacked pull requests use another pull request branch as
their base
> - A parent layer can change a package manifest without committing
`pnpm-lock.yaml`
> - A child layer can inherit that manifest change without changing a
manifest itself
> - The current policy skips lockfile regeneration for that child and
downstream frozen installs fail
> - This pull request validates the complete merge tree and shares a
regenerated lockfile only when needed
> - The benefit is reliable stacked pull request verification without
weakening the trusted workflow boundary
## Linked Issues or Issue Description
**What happened?**
A stacked child pull request inherited a package manifest change from
its parent. The child did not change a manifest itself. The policy job
skipped lockfile regeneration. Downstream jobs tried to restore an
artifact that did not exist and then failed during frozen dependency
installation.
**Expected behavior**
The policy job must validate the complete pull request merge tree. It
must upload a regenerated lockfile when the checked-in lockfile is
stale, including on a stacked child layer.
**Steps to reproduce**
1. Create a parent pull request that changes `package.json` without
committing `pnpm-lock.yaml`.
2. Create a child pull request on that branch without another manifest
change.
3. Run the trusted pull request workflow for the child.
4. Observe that frozen dependency installation fails because no
`pr-lockfile` artifact exists.
**Paperclip version or commit**
`f173ee09fa5c2ced7806bba47b54c3df853ab4df`
**Deployment mode**
GitHub Actions trusted pull request workflow.
**Agent adapter(s) involved**
Not adapter-specific. This is a core CI workflow bug.
## What Changed
- Regenerate the lockfile from every checked-out merge tree.
- Compare the generated lockfile with the checked-in copy before upload.
- Download the artifact only when the policy job reports that it
uploaded one.
- Fail closed when a reported artifact is missing.
- Add a workflow contract test for stacked lockfile handling.
- Keep the caller pinned to the last merged trusted SHA; after this
implementation merges, a separate activation PR will advance the
immutable pin to its merge commit.
## Verification
- `actionlint .github/workflows/pr-trusted.yml`
- `node --test scripts/__tests__/e2e-shard.test.mjs`
## Risks
- The policy job runs one lockfile-only install for every pull request.
This can add a small amount of CI time.
- A missing artifact now fails immediately when the policy job reports
an upload. This is intentional because it exposes workflow corruption.
- No runtime or product behavior changes.
- The implementation/activation split is intentional: unmerged
PR-authored workflow code must never execute on trusted runners.
## Model Used
OpenAI Codex with model `gpt-5`, reasoning, 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
## Thinking Path
> - Paperclip uses pull request CI to validate each proposed change
> - The existing workflow defines every heavy job in a PR-controlled
file
> - A trusted reusable workflow now contains the synchronized CI
definition
> - The caller must use an immutable default-branch SHA
> - This pull request replaces the duplicate job list with that pinned
caller
> - The benefit is automatic secure runner selection without workflow
drift
## Linked Issues or Issue Description
Refs #12436
Refs #12438
**What existing behavior does this improve?**
This improves how the pull request workflow selects trusted CI capacity.
**Subsystem affected**
Cross-cutting CI automation.
**Current behavior**
The active workflow contains a duplicate list of all heavy jobs. It
cannot use the administrator-controlled runner gate.
**Proposed behavior**
The active workflow calls the synchronized trusted workflow at an
immutable SHA. The trusted workflow selects GitHub-hosted or isolated
AWS capacity from the validated contributor identity.
**Reason and benefit**
The thin caller prevents pull request changes from replacing the
external-runner security gate. It also keeps runner selection automatic.
**Breaking changes**
The check names gain the reusable workflow job prefix. AWS routing
remains disabled until the canary starts.
## What Changed
- Replaced the duplicated heavy CI job list with one reusable-workflow
call.
- Pinned the call to the reviewed default-branch commit.
- Limited the caller token to actions, contents, and pull request read
access.
## Verification
- actionlint on both workflow files
- Trusted-routing tests
- Confirmed the pinned SHA contains the workflow and is an ancestor of
master
- Full AWS and GitHub runner-boundary verification with routing disabled
## Risks
The check context names change when GitHub expands the reusable
workflow. The rollout verifies the new aggregate contexts before branch
rules change. The repository kill switch remains off during this pull
request.
## Model Used
OpenAI Codex with GPT-5, 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 linked a related public PR or described the issue
with the matching template fields
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal
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
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip uses pull request CI to test changes before merge.
> - The e2e PR lane runs Playwright specs in a shard matrix.
> - Each shard builds a list of spec files for its matrix entry.
> - The workflow passed that list after a literal `--` separator.
> - Playwright did not receive the list as file filters.
> - This pull request removes the separator and adds a guard test.
> - The benefit is that each e2e shard runs only its assigned specs.
## Linked Issues or Issue Description
Refs #10629.
**What happened?**
The e2e shard step used `pnpm run test:e2e -- $specs`. The shard spec
list was not applied as Playwright file filters.
**Expected behavior**
Each e2e shard should pass only its selected specs to Playwright.
**Steps to reproduce**
1. Inspect `.github/workflows/pr.yml` at the merge commit for #10629.
2. Find the `e2e_shards` command that invokes `pnpm run test:e2e`.
3. See the literal `--` before `$specs`.
**Paperclip version or commit**
`86767951`
**Deployment mode**
GitHub Actions PR CI.
## What Changed
- Removed the literal `--` from the e2e shard `pnpm run test:e2e $specs`
invocation.
- Added a regression test that checks the workflow passes `$specs`
without that separator.
## Verification
- `node --test scripts/__tests__/e2e-shard.test.mjs`
## Risks
Low risk. This changes one CI command and one workflow guard test. The
main risk is shell argument handling in the workflow, and the guard now
covers the expected command shape.
## Model Used
OpenAI GPT-5 through Codex. The run used shell and GitHub CLI tool
access. The runtime did not expose a context window size.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The pull request workflow protects changes with a Playwright e2e
lane.
> - That lane already uses a weighted file partition so slow specs do
not cluster by test count.
> - Recent green PR runs showed the two e2e shard jobs were slower than
the next slow required lane.
> - The largest spec is indivisible, so a third shard lets that spec run
alone and lets the rest split by duration.
> - This pull request changes only the PR e2e shard matrix and the guard
test.
> - The benefit is a shorter expected PR critical path while the
required `e2e` aggregate check name stays stable.
## Linked Issues or Issue Description
Refs #9923
**What existing behavior does this improve?**
The `pull_request` workflow Playwright e2e lane.
**Subsystem affected**
Cross-cutting: GitHub Actions CI and test scripts.
**Current behavior**
The PR workflow runs the weighted Playwright e2e partition across two
jobs. Recent green runs showed those jobs as the slowest required
checks.
**Proposed behavior**
The PR workflow runs the same e2e spec set across three weighted jobs.
The aggregate required check stays named `e2e`.
**Reason and benefit**
The third shard lets the slow smoke-lab spec run alone while the rest of
the catalog stays balanced. This should shorten the PR critical path.
The win is bounded by fixed per-job setup time.
**Breaking changes**
None. The required aggregate check contract is preserved.
## What Changed
- Change the PR e2e shard matrix from two entries to three entries.
- Update the shard guard test to expect three shards.
- Floor the balance bound at the largest single spec weight.
- Assert that the workflow does not define more shard indexes than
`SHARD_COUNT`.
## Verification
- `node --test ./scripts/__tests__/e2e-shard.test.mjs` passes with 6
tests.
- The recorded-weight partition is complete and non-overlapping: 168.0s,
116.5s, and 114.4s.
- I checked `ROADMAP.md` and found no overlapping roadmap-level core
feature.
- I searched public GitHub PRs and issues for related e2e shard work. I
found related PR #9923 and no open duplicate for this branch or change.
## Risks
- This adds one extra GitHub Actions runner to the PR e2e lane.
- The wall-clock win is bounded by fixed per-job setup.
- Behavior risk is low because the aggregate required check remains
named `e2e`.
## Model Used
OpenAI Codex, GPT-5, tool-enabled coding agent in this repository. The
runtime did not expose the context-window size.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Cody <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The pull request workflow is the main merge gate for changes to that
app.
> - The Playwright e2e lane is expensive because every spec shares one
isolated server and runs serially.
> - Splitting that lane across runners shortens the critical path, but
the public required-check contract still needs a check named exactly
`e2e`.
> - This pull request shards the real e2e work while preserving a fast
aggregate `e2e` job for branch protection.
> - The benefit is a faster PR workflow without making otherwise-good
PRs unmergeable because a legacy required check disappeared.
## Linked Issues or Issue Description
No public GitHub issue exists for this CI follow-up.
Related prior CI work:
- Refs #8360
- Refs #9168
- Refs #9516
Bug report:
### What happened?
Sharding the PR e2e lane directly at the workflow job level changes the
emitted check names to shard-specific names, while existing branch
protection expects a check named exactly `e2e`.
### Expected behavior
The PR workflow should be able to run e2e specs across multiple runners
while still emitting a stable aggregate check named `e2e`.
### Steps to reproduce
1. Open a PR against `master`.
2. Run the PR workflow with the e2e lane split only as a matrix job.
3. Observe that the shard checks complete, but a required check named
exactly `e2e` never appears.
### Paperclip version or commit
Current `master`.
### Deployment mode
GitHub Actions pull request workflow.
## What Changed
- Added `scripts/e2e-shard.mjs`, which partitions default Playwright e2e
specs by recorded per-spec duration.
- Added `scripts/e2e-shard-durations.json` with measured e2e spec
durations so the slow smoke-lab spec does not dominate one runner.
- Split the PR workflow e2e lane into two `e2e_shards` matrix jobs and
added a fast aggregate job named exactly `e2e`.
- Added `scripts/__tests__/e2e-shard.test.mjs` to lock the shard
partition, ignored-spec sync, manifest coverage, and aggregate
required-check contract.
## Verification
- `node --test scripts/__tests__/e2e-shard.test.mjs`
- `node --test scripts/__tests__/run-vitest-stable-shard.test.mjs`
- `git diff --check upstream/master..HEAD`
- Searched GitHub for duplicate or related e2e-shard / required-check
PRs and issues before opening this PR; no direct duplicate was found.
## Risks
Low risk. The main risk is that the duration manifest can drift as specs
are added or runtimes change; missing specs fall back to the median
known duration, and the focused shard test catches empty, overlapping,
or badly imbalanced partitions.
## Model Used
OpenAI GPT-5 via Codex CLI coding agent, with shell/tool execution and
repository inspection.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>