## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Published container images are the deployable unit for self-hosted
and managed instances, so merge-to-image latency bounds every deploy
iteration
> - The docker workflow configures BuildKit caching, but builds still
ran ~12+ minutes essentially cold
> - Two causes: the most expensive layer (four CLI toolchains + apt) is
ordered after the always-changing app copy so it can never cache, and
the type=gha cache's 10GB repo cap means the two multi-arch mode=max
jobs evict each other
> - This pull request reorders the tool layer above the app copy (with a
weekly epoch so @latest tools keep advancing) and switches both jobs to
registry-backed cache in ghcr
> - The benefit is that warm builds shrink to roughly the app build +
push, targeting the sub-5-minute range together with the amd64-only
cloud variant
## Linked Issues or Issue Description
No existing public issue — inline description following the feature
request template:
**Subsystem affected**
CI / release publishing (docker workflow, Dockerfile)
**Problem or motivation**
Despite `cache-from/cache-to` being configured, image builds run
effectively cold: (1) the production stage installs four CLI toolchains
+ apt packages *after* `COPY --from=build /app /app`, and since the app
copy changes every commit, that most-expensive layer rebuilds every
build, per arch; (2) the `type=gha` BuildKit cache is capped at 10GB per
repository, and two multi-arch `mode=max` jobs overflow and evict each
other's entries.
**Proposed solution**
Order the tool/OS layer before the app copy (it references nothing from
`/app`), refresh it weekly via a `CLI_TOOLS_CACHE_EPOCH` build arg so
the `@latest` tools don't freeze in the cache, and move both jobs to
registry-backed BuildKit cache (`:buildcache` / `:buildcache-cloud` refs
in ghcr, no size cap, separate refs so the parallel jobs don't clobber
each other).
**Alternatives considered**
Pinning CLI tool versions instead of the weekly epoch — more
deterministic, but adds a version-bump chore; the weekly epoch preserves
current freshness semantics with bounded staleness. Keeping type=gha
with `mode=min` — smaller cache but loses intermediate-stage reuse,
which is where most of the win is.
**Roadmap alignment**
Not on ROADMAP.md; CI/publishing speed improvement only.
## What Changed
- `Dockerfile`: the production stage's tool/OS `RUN` (npm --global CLIs,
apt, `/paperclip` setup) moves above `COPY --from=build /app /app`; new
`CLI_TOOLS_CACHE_EPOCH` arg consumed by that layer. The `cloud` stage is
unaffected — it only layers plugin dists on top of the finished
production stage.
- `.github/workflows/docker.yml`: both jobs stamp the ISO week into
`CLI_TOOLS_CACHE_EPOCH`, and both switch `cache-from/cache-to` from
`type=gha` to `type=registry` with per-job refs.
- Includes the one-line amd64-only cloud-variant commit from #10570 so
the two PRs can't conflict; if #10570 merges first, this PR rebases down
to a single commit automatically.
## Verification
- Image content is unchanged by layer reordering: the moved `RUN`
references nothing from `/app`, and Docker layer ordering only affects
caching, not the final filesystem (tool installs and app copy touch
disjoint paths).
- The cache ref is written only by this workflow — `docker.yml` runs on
master/tag pushes, never on PRs — so the workflow's existing "no shared
caches into build inputs" supply-chain stance is unchanged (BuildKit
layer cache was already accepted via type=gha; the registry backend has
the same writer trust).
- Runtime proof lands with the first two master builds after merge: the
first warms the cache, the second should show the tool layer and
deps/build stages as CACHED in the build log, with wall clock dropping
accordingly. I'll be watching those as part of managed-deploy work.
## Risks
- Low. Worst case the registry cache misses (cold-build behavior, same
as today). The weekly epoch means CLI tools update at most a week late
inside images; a release built mid-week ships the tools from that week's
first build. Cache refs add two small artifacts to ghcr.
## Model Used
Claude Fable 5 (`claude-fable-5`, extended thinking, via 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 (no test-affecting changes)
- [x] I have added or updated tests where applicable (n/a — build config
and layer ordering only)
- [x] I have updated relevant documentation to reflect my changes
(in-file comments document both mechanisms)
- [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
> - Paperclip is the open source app people use to manage AI agents for
work
> - Published container images are how both self-hosted users and
managed-deployment hosts run it
> - The repository publishes two variants: the self-hosted image and a
cloud variant for managed deployments
> - The cloud variant was built for amd64+arm64, but its only consumers
are managed-deployment hosts, which run amd64
> - The QEMU-emulated arm64 half dominates the build's wall clock,
delaying every merge-to-deployable-image cycle
> - This pull request drops arm64 from the cloud variant only, keeping
the self-hosted image multi-arch
> - The benefit is roughly halving the time from merge to a deployable
cloud image, with no change for any actual consumer
## Linked Issues or Issue Description
No existing public issue — inline description following the feature
request template:
**Subsystem affected**
CI / release publishing (docker workflow)
**Problem or motivation**
The cloud image variant builds for `linux/amd64,linux/arm64`, but the
arm64 half runs under QEMU emulation and dominates the job's wall clock
— while no consumer of the cloud variant runs arm64 (managed-deployment
hosts are amd64). Every deploy iteration pays ~double the necessary
build time.
**Proposed solution**
Build the cloud variant amd64-only. The self-hosted image keeps
`amd64+arm64` so ARM users (Apple Silicon, ARM servers) are unaffected.
**Alternatives considered**
Keeping multi-arch but building arm64 on native arm64 runners with a
manifest merge — faster than QEMU and worth doing for the self-hosted
image if its build time becomes a pain point, but unnecessary complexity
for a variant with no arm64 consumers.
**Roadmap alignment**
Not on ROADMAP.md; CI/publishing speed improvement only.
## What Changed
- `.github/workflows/docker.yml`: the `build-and-push-cloud` job's
`platforms` is now `linux/amd64` (with a comment explaining why). The
self-hosted `build-and-push` job is untouched.
## Verification
- Build-config-only change; the workflow runs on merge to master. The
published `-cloud` manifest will be amd64-only, which its consumers
already pull.
- No test changes: nothing at runtime differs on any platform that
actually runs the image.
## Risks
- Low. If an arm64 consumer of the cloud variant ever appears (e.g.
local `docker run` on Apple Silicon for debugging), it would fall back
to emulation on the consumer's machine or need this reverted — a
one-line change. The self-hosted image's platform matrix is unchanged.
## Model Used
Claude Fable 5 (`claude-fable-5`, extended thinking, via 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 (no test-affecting changes)
- [x] I have added or updated tests where applicable (n/a — CI platform
matrix only)
- [x] I have updated relevant documentation to reflect my changes
(in-workflow comment documents the rationale)
- [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
> - Paperclip is the open source app people use to manage AI agents for
work
> - Instances commonly run from published container images, and
operators need to observe which build a container actually serves
> - `/api/health` now reports the running build commit, and server-info
already falls back to `PAPERCLIP_BUILD_COMMIT` when git is unavailable
> - But published images carry no `.git` and never received
`PAPERCLIP_BUILD_COMMIT`, so containers report `commit: null` — verified
live against a current image
> - That leaves the new deployment-verification field inert exactly
where it matters most: containerized deploys
> - This pull request bakes the exact build commit into both image
variants at build time, mirroring how `PAPERCLIP_BUILD_VERSION` is
already stamped
> - The benefit is that containers report their true commit on
`/api/health`, so deploy tooling can verify a rollout actually shipped
## Linked Issues or Issue Description
Companion to #10563 (which exposed the `commit` field on `/api/health`).
Inline description following the bug report template:
**What happened?**
A container from a published image responds to `GET /api/health` with
`"commit": null`. The image has no `.git` directory and the
`PAPERCLIP_BUILD_COMMIT` fallback that `server-info` supports is never
provided at build time, so git metadata resolves as unavailable.
**Expected behavior**
A container reports the commit it was built from, the same way it
already reports its build version via the baked
`PAPERCLIP_BUILD_VERSION`.
**Steps to reproduce**
Run any published image (e.g.
`ghcr.io/paperclipai/paperclip:sha-c4f6264-cloud`) and `curl
/api/health` — `commit` is `null` even though the build commit is known
at image-build time.
**Paperclip version or commit**
`sha-c4f6264-cloud` (first image containing #10563).
## What Changed
- `Dockerfile`: new `PAPERCLIP_BUILD_COMMIT` build arg, exported as an
ENV in the production stage (the `cloud` stage inherits it), directly
parallel to `PAPERCLIP_BUILD_VERSION`. Empty for local `docker build`,
which keeps the normal fallbacks.
- `.github/workflows/docker.yml`: both build jobs pass
`PAPERCLIP_BUILD_COMMIT=${{ github.sha }}`.
## Verification
- Reviewed the plumbing end-to-end: `build-commit.ts` reads
`PAPERCLIP_BUILD_COMMIT` (validated as a full SHA), `server-info.ts`
`readGitInfo` falls back to it when the git CLI fails, producing
`available: true, fullSha` — which `/api/health` surfaces as `commit`.
- Verified live that a current published image reports `commit: null`;
this change repairs that on the next build. Post-merge, the first master
image should report its commit — I'll be verifying that as part of
managed-deploy validation.
- No test changes: the fallback path is already covered by existing
server-info tests; this PR only supplies the env at image build.
## Risks
- Low. Two build-time stamps; no runtime code changes. A wrong SHA would
only mislabel the build (same failure mode `PAPERCLIP_BUILD_VERSION`
already carries), and `${{ github.sha }}` is the exact commit the
workflow builds.
## Model Used
Claude Fable 5 (`claude-fable-5`, extended thinking, via Claude Code
with tool use and code execution); diagnosis included live probes of a
running container's `/api/health`.
## 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 (no test-affecting changes;
server suites unaffected)
- [x] I have added or updated tests where applicable (n/a — build-time
stamps only)
- [x] I have updated relevant documentation to reflect my changes
(Dockerfile comments document the arg)
- [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 is the open source app people use to manage AI agents for
work
> - Contributors use the PR template to describe changes before review
> - The linked-issue gate reads literal template labels, not freeform
prose
> - The template did not state that rule, so a good prose summary could
fail on first submission
> - The gate also skipped two issue template paths that the PR template
already points to
> - This pull request makes the template and the gate match the same
contract
> - The benefit is that a good-faith author can pass the check on the
first try
## Linked Issues or Issue Description
No public issue exists.
Related public PR: #7762.
The problem is a mismatch between the PR template and the linked-issue
gate.
The template gave a bare placeholder and did not explain the
literal-label rule.
The gate also missed the enhancement and docs issue templates.
## What Changed
- Replaced the bare PR-template placeholder with labeled inline
skeletons for bug, feature, and adapter paths.
- Added one sentence that says the gate reads literal labels on separate
lines.
- Added the enhancement and docs issue template field sets to the
linked-issue gate.
- Added and updated tests for prose-only bodies, template skeleton
bodies, and extra issue template coverage.
## Verification
- `node --test .github/scripts/tests/check-pr-linked-issue.test.mjs`
- The branch contains one commit:
`561f1ed3a1434ed4562306f74d40968163ef1444`
## Risks
Low risk.
The three-field minimum stays in place.
The main change is clearer author guidance in the PR template.
## Model Used
OpenAI Codex, GPT-5, tool use.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` /
`Closes: #` / `Refs: #` OR (b) described the issue in-PR following the
relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Many contributors are AI agents, and they write the pull request
text.
> - PR descriptions vary in clarity, and long or ambiguous prose slows
down review.
> - Simplified Technical English (ASD-STE100) is a controlled language
that makes technical text short, clear, and unambiguous.
> - This pull request adds one line to `CONTRIBUTING.md` and one line to
the pull request template. Both lines instruct the author to write PR
text in Simplified Technical English (ASD-STE100).
> - The benefit is clearer, faster-to-review pull request descriptions.
## Linked Issues or Issue Description
No related GitHub issue exists. This is a small documentation change. It
sets a writing-style rule for pull request text: authors must use
Simplified Technical English (ASD-STE100).
## What Changed
- Add one line to `CONTRIBUTING.md` (in "Writing a Good PR message")
that requires Simplified Technical English (ASD-STE100).
- Add one comment line to `.github/PULL_REQUEST_TEMPLATE.md` that gives
the same instruction to PR authors.
## Verification
- Read the two changed files. Confirm each new line is present and
correct.
- `git diff --stat origin/master` shows only the two files, with 4
inserted lines.
## Risks
Low risk. The change is documentation only. It adds guidance text and
does not change code or behavior.
## Model Used
Claude Opus 4.8 (claude-opus-4-8), 1M context window, extended thinking,
tool use.
## 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
- [ ] 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 4.8 (1M context) <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The CI workflow is responsible for producing and publishing the
Docker images that power Paperclip deployments
> - The cloud image publish was previously coupled to the stock publish
job, so a failure or delay in one path could gate the other
> - That coupling makes the release pipeline less resilient than it
needs to be
> - This pull request gives the cloud publish its own top-level job so
both publishes can run in parallel without a `needs:` dependency
> - The benefit is better failure isolation and less wasted time when
one publish path is slow or broken
## Linked Issues or Issue Description
No public GitHub issue was found for this change.
Problem statement:
- The cloud image publish was implemented as trailing work inside the
stock publish job.
- That setup meant the cloud publish could be delayed or skipped if the
stock job failed early.
- The desired behavior is for the cloud publish to run independently so
a failure in one publish path does not gate the other.
Proposed solution:
- Split the cloud publish into its own top-level workflow job.
- Keep the same cloud-specific build settings and cache behavior.
- Preserve the existing top-level concurrency behavior.
Alternatives considered:
- Keeping both publishes in one job with conditionals or later steps.
Rejected because it still couples success and runtime between the two
publish paths.
## What Changed
- Split the cloud image publish into a separate top-level Docker
workflow job.
- Removed the dependency coupling so the cloud job does not need the
stock job.
- Expanded the drift-guard test to assert the two-job structure and the
absence of `needs:` on the cloud job.
## Verification
- The workflow YAML was parsed successfully and confirmed to contain two
jobs: `build-and-push` and `build-and-push-cloud`.
- The cloud job was confirmed to have no `needs:` entry.
- The drift-guard assertions were reproduced in a dependency-free
harness and passed.
- PR #10408 completed GitHub Actions with all required checks green,
including the e2e shards.
- Greptile review completed at 5/5 with no unresolved comments.
- No documentation files changed because this is a workflow/test-only
change.
## Risks
- The workflow now duplicates the prep steps across two runners, so any
shared setup change must be kept in sync between both jobs.
- The new job increases workflow surface area slightly, which can make
future maintenance more verbose.
- Overall risk is low because the change is limited to CI orchestration
and test coverage.
## Model Used
OpenAI Codex (GPT-5, tool-using code assistant)
## 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 repository uses GitHub Actions workflows to build and publish
Docker images
> - A workflow-level concurrency policy controls whether newer pushes
cancel in-flight builds
> - The current job-level setting can cancel a running image build
before it finishes publishing
> - That leaves the Docker image pipeline brittle when a new push
arrives during an active publish
> - This pull request moves concurrency to the workflow level and
disables cancel-in-progress so running builds finish
> - The benefit is that only pending work is superseded, while a build
already publishing is allowed to complete
## Linked Issues or Issue Description
No public GitHub issue exists for this change. This PR addresses the
Docker workflow concurrency behavior directly: it ensures in-flight
image builds are not cancelled by newer pushes, while still serializing
builds per ref.
## What Changed
- Moved the Docker workflow concurrency block from the job level to the
workflow level.
- Set `cancel-in-progress: false` so an active build can finish
publishing.
- Added a drift-guard test that parses `.github/workflows/docker.yml`
and asserts the workflow-level concurrency policy remains `false`.
## Verification
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/cloud-image-bundled-plugins.test.ts`
- `python3 -c "import yaml;
yaml.safe_load(open('.github/workflows/docker.yml'))"`
- Verified the fetched remote branch contains a single commit on top of
`origin/master`.
- Searched GitHub for duplicate or related PRs and issues; none found.
- Checked `ROADMAP.md` and did not find overlapping planned core work.
## Risks
- Low risk: the change is limited to workflow concurrency behavior and a
targeted test assertion.
- If the workflow concurrency key is changed later, the drift-guard test
will fail and require an update.
## Model Used
OpenAI Codex (GPT-5, tool use; context window not surfaced 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 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] 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; it ships as a Docker image that self-hosters and managed
deployments run.
> - The server resolves its own version at runtime in
`server/src/version.ts` (`resolveServerVersion()`), which feeds
analytics and the server debug panel.
> - That resolver derives the real version from `git describe`, and
falls back to `server/package.json`'s `version` when git isn't
available.
> - But `server/package.json`'s version is a static placeholder — CI
only stamps the real CalVer at publish, so in source it is never the
real version (currently `0.3.1`).
> - A Docker image has no `.git` (it's dockerignored), so `git describe`
can't run inside it. Every image therefore falls back to the placeholder
and reports `0.3.1` in analytics and the debug panel, regardless of
which commit it was built from.
> - This PR computes the real version once on the CI build runner (where
`.git` and tags exist), bakes it into the image, and has
`resolveServerVersion()` prefer that stamp when `git describe` is
unavailable.
> - The benefit: self-hosted and cloud images report their true version
instead of a misleading placeholder, with no change to dev checkouts,
`git describe`-based resolution, or local `docker build`.
## Linked Issues or Issue Description
No public issue exists — describing the bug inline (per the bug report
template).
**What happened?**
Docker images built from `master` (and release tags) report the server
version as the `0.3.1` placeholder in analytics and the server debug
panel, instead of the real version of the commit the image was built
from.
**Expected behavior**
An image reports the real version of its build commit (e.g.
`2026.722.0+51.git.<sha>`), so operators can tell which build is
running.
**Steps to reproduce**
1. Build the server Docker image from any `master` commit (the `Docker`
workflow, `production` target).
2. Run the image and open the server debug panel (or inspect the version
reported to analytics).
3. Observe the version is `0.3.1` rather than the commit's real version.
**Root cause**
`resolveServerVersion()` derives the real version from `git describe`,
but the image has no `.git` (dockerignored), so it falls back to
`server/package.json`'s `version` — a static placeholder CI only
replaces with the real CalVer at publish time. Nothing bakes the real
version into the image.
**Paperclip version or commit:** reproduces on `master` (`4c55f0d8`) and
any published image.
**Deployment mode:** self-hosted and managed (both the `production` and
`-cloud` images).
**Installation method:** Docker image (`ghcr.io/paperclipai/paperclip`).
**Related PRs (dedup search):** #9103 (merged — added the `git
describe`-based source-install resolution this builds on) and #9637
(closed). Neither bakes a version into the image; this PR closes that
gap. No duplicate found.
## What Changed
- **`.github/workflows/docker.yml`** — checkout with full history + tags
(`fetch-depth: 0`), and a new `Compute build version` step that runs
`git describe --tags --match 'v*' --long --dirty` on the pristine runner
checkout. The result is passed as a `PAPERCLIP_BUILD_VERSION` build-arg
to both the `production` and `-cloud` image builds.
- **`Dockerfile`** — the `production` stage takes an `ARG
PAPERCLIP_BUILD_VERSION` (default empty) and bakes it into the runtime
`ENV`; the `cloud` stage inherits it via `FROM production`.
- **`server/src/build-version.ts`** (new) — `readBuildVersion()` /
`parseBuildVersion()`, mirroring `build-commit.ts`: reads
`PAPERCLIP_BUILD_VERSION` (or a `.paperclip-build-version` file) as a
single-token stamp.
- **`server/src/version.ts`** — `resolveServerVersion()` prefers the
baked build version when `git describe` is unavailable, parsing it with
the same rules as a live checkout (`parseGitDescribeVersion`), and
falling through to the existing `build-commit` stamp and package version
when unset. A live checkout's `git describe` still wins over any stamp.
- Tests for the new behavior and the precedence.
## Verification
- `pnpm --filter @paperclipai/plugin-sdk ensure-build-deps && tsc
--noEmit` in `server/` — clean.
- `vitest run server/src/__tests__/version.test.ts
server/src/__tests__/build-version.test.ts` — **23 tests pass**,
covering: stamped version used when git describe fails, stamp parsed to
real CalVer, stamp preferred over the build-commit fallback, on-tag
stamp collapses to the release version, a pre-resolved stamp used
verbatim, and a live git describe still winning over a stamp.
- `git describe --tags --match 'v*' --long` for this commit →
`v2026.722.0-51-g<sha>`, which `resolveServerVersion()` reports as
`2026.722.0+51.git.<sha>` — no longer `0.3.1`.
- Not run locally: the full multi-arch image build (CI-only). The
workflow change is verified by inspection; the version is computed on
the pristine checkout before any lockfile refresh, so it carries no
spurious `-dirty`.
## Risks
Low. Additive and image-only:
- No runtime behavior changes for dev checkouts (git describe still
primary and wins over any stamp) or for local `docker build` (empty arg
→ server keeps its existing fallbacks).
- Not a breaking change; no schema or API surface. The stamp is
informational (version reporting only).
- `fetch-depth: 0` makes the release-image checkout fetch full
history/tags — a modest cost on a workflow that already runs at release
cadence with a 60-minute budget.
- Rollback: revert the commit; images simply return to reporting the
placeholder.
## Model Used
Claude Opus 4.8 (`claude-opus-4-8`, 1M-context variant), extended
thinking, with tool use / code execution — agentic edits, `tsc` +
`vitest` runs, and a `git describe` resolution 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 (bugfix, not core feature work)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (#9103, #9637 — related, not duplicates)
- [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 (`fix/build-version-stamp`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes (no
user-facing docs affected; behavior is documented inline in `version.ts`
/ `build-version.ts` and the workflow/Dockerfile)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Managed (cloud-hosted) deployments configure instances through
`PAPERCLIP_MANAGED_CONFIG`, including a `plugins.autoInstall` key list
that the boot-time installer resolves against the bundled plugin catalog
> - The installer requires each bundled plugin's `dist/manifest.js`
(`server/src/services/bundled-plugins.ts`), but the published image only
ships the sandbox providers' *source* — they are intentionally excluded
from the pnpm workspace, and the Dockerfile never builds them
> - Every managed auto-install therefore logs `bundled plugin bundle not
present; skipping auto-install` and no sandbox provider can be
provisioned through managed config
> - Baking built plugins into the single published image would fix it
but makes every self-hosted pull carry the providers' `node_modules` for
a managed-only mechanism
> - This pull request adds a `cloud` Dockerfile target extending
`production` with built bundled plugins — parameterized by build arg and
currently just `daytona` — published alongside the default image with a
`-cloud` tag suffix
> - The benefit is working plugin auto-provisioning for managed
deployments while the self-hosted image stays byte-identical and the
cloud variant only carries what is actually deployed
## Linked Issues or Issue Description
Fixes#10158 (filed for this problem; no prior issue existed — searched
for duplicate/related PRs and issues around bundled plugins, docker
image variants, and auto-install). Summary: **What happened:** on a
managed instance with `plugins.autoInstall: ["daytona"]` delivered via
`PAPERCLIP_MANAGED_CONFIG`, boot logs `bundled plugin bundle not
present; skipping auto-install` with `pluginPath:
/app/packages/plugins/sandbox-providers/daytona`, and the plugin is
never installed. **Expected:** the advertised bundled-catalog keys are
installable from the published image. **Why:** the image ships plugin
source without `dist/` — nothing in the Dockerfile builds the
workspace-excluded sandbox providers.
## What Changed
- `Dockerfile`: new `cloud-plugins` stage (based on `build`, so
devDependencies are available for `tsc`) that installs and builds each
provider named in the `CLOUD_BUNDLED_PLUGINS` build arg standalone
(`pnpm install --ignore-workspace --no-lockfile && pnpm build`, exactly
as the providers' READMEs prescribe), asserting `dist/manifest.js`
exists per plugin and failing loudly on unknown names; new `cloud` stage
= `production` + the built plugin tree. The arg defaults to `daytona` —
the only provider managed deployments auto-install today; every entry
adds its `node_modules` to the image, so the list grows only with actual
need (a one-line workflow change).
- `.github/workflows/docker.yml`: the existing build step is pinned to
`target: production` (without this, the new trailing stage would
silently become the default build target — this pin is what keeps the
self-hosted image identical); new metadata + build-push steps publish
the `cloud` target (with `CLOUD_BUNDLED_PLUGINS=daytona`) under the same
tag set with a `-cloud` suffix (`sha-<short>-cloud`, `latest-cloud`,
`<version>-cloud`), same schema labels, reusing the GHA layer cache
## Verification
- All seven sandbox providers build standalone from a clean checkout
with the exact commands the new stage runs, each producing
`dist/manifest.js` — so the current `daytona` default works and future
list additions are known-good
- The stage's shell loop was dry-run against the checkout (directory
existence + per-plugin assertion logic)
- Workflow YAML lints clean
- **Not run:** a full multi-arch `docker build` (no local docker
daemon). The `cloud` stage is additive and the default target is pinned,
so the risk is contained to the new build step; the first master build
after merge proves it end-to-end
## Risks
- Self-hosted behavior: unchanged. The default image build is pinned to
the `production` target, which produces the same layers as before this
change; the `cloud` stages run only for the new build step.
- The plugin installs in the `cloud-plugins` stage use `--no-lockfile`
(the providers are workspace-excluded and lockfile-less by design), so
plugin dependency resolution is not pinned at image-build time. This
mirrors the existing Plugins-page install path, which resolves from npm
at install time.
- CI cost: one additional build-push per master push. It reuses the
layer cache from the production build, so the marginal work is the
single plugin's build layers.
- An unknown name in `CLOUD_BUNDLED_PLUGINS`, or a provider that stops
producing `dist/manifest.js`, fails the cloud build loudly rather than
publishing a broken variant.
## Model Used
Claude (Anthropic), model ID `claude-fable-5[1m]` via Claude Code CLI —
extended thinking and tool use (code edits, standalone plugin build
verification, workflow lint).
## Checklist
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] Self-hosted behavior unchanged (default build target pinned to
`production`)
- [x] One clear change: publish a cloud image variant with built bundled
plugins
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Docker image publish workflow (`.github/workflows/docker.yml`)
builds and pushes the multi-arch `ghcr.io` image on every master push,
so users pulling the container get the latest code
> - The two newest master runs of that workflow failed, so no images
have been published past a recent master commit
> - The failures had two distinct causes: run
[30054330748](https://github.com/paperclipai/paperclip/actions/runs/30054330748)
hit `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` (committed `pnpm-lock.yaml`
drifted from `patchedDependencies` in package metadata), and run
[30050197392](https://github.com/paperclipai/paperclip/actions/runs/30050197392)
hit `no space left on device` during the multi-arch buildx export
> - This pull request hardens the publish job against both failure
modes: it refreshes the lockfile (lockfile-only, guarded) before the
build, and frees runner disk space before buildx setup
> - The benefit is that image publishing keeps working through routine
lockfile drift and the growing multi-arch build footprint, so `ghcr.io`
images stay current with master
## Linked Issues or Issue Description
- Refs #8286 — same class of Docker-build lockfile mismatch failure
- Refs #8827 — pnpm 9.15.x pin / lockfile regeneration discussion
- Note: the immediate lockfile drift on master was fixed by #10132; the
refresh step here prevents the *next* drift from breaking image
publishing again
## What Changed
- Added a pnpm + Node setup and a **"Refresh lockfile for Docker build
context"** step to the image job in `.github/workflows/docker.yml`: runs
`pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile`,
exits cleanly if nothing changed, and **fails the job if anything other
than `pnpm-lock.yaml` was modified** by the refresh
- Added a **"Free runner disk"** step (before buildx setup) that prunes
the pnpm store, apt caches, preinstalled toolchains
(`/usr/share/dotnet`, Android SDK, Swift, Boost, PowerShell, GHC,
CodeQL/PyPy/Ruby toolcache), and dangling Docker state, logging `df -h`
before/after
- No changes outside the workflow file (54 added lines, nothing removed)
## Verification
- Pulled the logs of both failed master runs and matched each failure to
the step that addresses it:
[30054330748](https://github.com/paperclipai/paperclip/actions/runs/30054330748)
failed with `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH`,
[30050197392](https://github.com/paperclipai/paperclip/actions/runs/30050197392)
failed with `no space left on device` during the buildx export
- Confirmed pnpm `9.15.4` in the new setup step matches the repo
`packageManager` field and the version used in the Dockerfile, so the
refreshed lockfile is generated by the same pnpm the image build
consumes
- Validated the workflow YAML parses cleanly
- The workflow triggers on master pushes / manual dispatch; the
definitive check is the first master run after merge — reviewers can
also `workflow_dispatch` it from this branch if desired
## Risks
- The lockfile refresh runs with `--ignore-scripts` and a guard that
aborts on any non-lockfile change, so it cannot silently pull unexpected
code into the image; worst case it fails the job with a clear diff
- The published image could be built from a refreshed lockfile that
differs from the committed one when drift exists — that keeps publishing
alive but can mask drift on master, which still needs the committed
lockfile fixed (as #10132 did)
- Disk cleanup removes preinstalled toolchains only on the ephemeral
runner for this job; other jobs/workflows are unaffected
- Low risk overall: additive steps in a single workflow file
## Model Used
- Claude (Anthropic) — `claude-fable-5` (Claude Code agent harness,
extended thinking, tool use). Used to diagnose the failing CI runs from
logs, author the workflow changes, and prepare this PR.
## 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 (no runtime code touched;
workflow YAML validated — see Verification)
- [ ] I have added or updated tests where applicable (n/a — CI workflow
change)
- [x] I have updated relevant documentation to reflect my changes (none
needed)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending — will confirm once
checks run)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review pass)
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip uses GitHub Actions to keep generated lockfile changes
deterministic in CI
> - The workflow decides when to regenerate the lockfile based on
file/path changes
> - Patch changes can live under a top-level `patches/` directory, and
those changes also affect dependency resolution
> - If the workflow misses that path, CI can skip lockfile regeneration
when it should run
> - This pull request adds top-level `patches/` to the trigger so patch
updates participate in the existing lockfile regeneration flow
> - The benefit is that patch-related dependency changes continue to get
the same CI protection as the other manifest and workspace triggers
## Linked Issues or Issue Description
No public GitHub issue is linked here. The underlying problem is that
top-level `patches/` files are part of pnpm's dependency graph, but the
PR workflow's lockfile-regeneration gate only looked at package
manifests, workspace config, `.npmrc`, and `pnpmfile.*` changes. That
meant patch-only edits could skip `pnpm install --lockfile-only` and
leave downstream frozen-install jobs on a stale lockfile.
This PR keeps the existing manual lockfile edit guard in place. The
intended behavior is still: CI owns lockfile regeneration, and patch
changes are allowed to trigger that regeneration without letting
contributors commit `pnpm-lock.yaml` directly.
## What Changed
- Added top-level `patches/` to the PR workflow's dependency-resolution
trigger.
- Left the manual `pnpm-lock.yaml` edit blocker unchanged so CI still
owns lockfile regeneration.
## Verification
- `git diff --check .github/workflows/pr.yml`
- Verified the workflow path predicate matches
`patches/acpx@0.12.0.patch`, `package.json`,
`packages/shared/package.json`, `pnpm-workspace.yaml`, `.npmrc`,
`pnpmfile.cjs`, `pnpmfile.js`, and `pnpmfile.mjs`, while excluding
nested patch paths and unrelated files.
## Risks
- Low risk: this only broadens the workflow trigger set for lockfile
regeneration.
- The main behavioral change is that patch updates at the repository
root now participate in the same CI path as manifest and workspace
changes.
## Model Used
OpenAI Codex, GPT-5-based tool-using agent.
## 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 publishes a coordinated set of packages through its
release workflows
> - Bundled packages use a pinned npm CLI so trusted publishing works
consistently
> - The canary publisher now crashes deterministically inside npm before
useful output reaches the workflow log
> - The npm debug log that contains the underlying failure disappears
with the hosted runner
> - This pull request upgrades the pinned publish CLI and preserves both
verbose HTTP activity and npm debug logs on failure
> - The benefit is that the plausible HTTP-layer fix ships immediately,
while any remaining CI-only failure becomes diagnosable
## Linked Issues or Issue Description
- **Problem:** The canary release workflow fails on the first bundled
package with `npm error Exit handler never called!` and no preceding
diagnostic output.
- **Expected behavior:** Bundled packages publish through trusted
publishing, or the workflow retains enough npm diagnostics to identify
the actual failure.
- **Reproduction:** Run the canary release workflow in GitHub Actions;
the failure reproduced on both attempts of run 29948506814.
- **Version/commit:** Current `master` after #10024 and #10030.
- **Deployment mode:** GitHub-hosted release workflow using Node.js 24
and npm trusted publishing.
- Related: #10024, #10030.
## What Changed
- Bumped the bundled publish CLI from npm 11.16.0 to npm 11.18.0.
- Added `--loglevel verbose` to bundled npm publish invocations.
- Dumped the last 300 lines of every npm debug log after failed canary
or stable publishes, with common registry credential forms redacted.
- Updated release assertions to pin npm 11.18.0 and verify verbose
logging.
## Verification
- `pnpm test:release-registry` — 66 tests passed.
- `bash -n scripts/release-lib.sh`.
- Parsed `.github/workflows/release.yml` with Python/PyYAML.
- Smoke-tested npm log redaction with representative Authorization,
`_authToken`, and token environment values.
- Smoke-tested npm debug-log redaction against Authorization,
`_authToken`, and `npm_token` examples.
- `git diff --check origin/master...HEAD`.
- The merge-triggered canary workflow remains the live
trusted-publishing verification.
## Risks
- Low code risk: changes are isolated to the release publisher and its
workflow diagnostics.
- npm 11.18.0 could expose a different registry/runtime regression;
failure-time debug log dumping makes that actionable.
- Verbose npm output increases release log volume but does not change
package contents or dist-tags; common credential forms are redacted
before debug logs are printed.
> 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, GPT-5.3 Codex, tool-enabled coding agent with repository
and shell execution; context window size is 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 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 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>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip publishes Docker images from this repo that self-hosters
and orchestration tooling deploy; the server refuses to start when its
database is missing any schema migration the build bundles
(`ensureMigrations`)
> - Anything that deploys these images therefore needs to know an
image's schema expectations *before* deploying it — today that requires
pulling the image or checking out the matching commit, both heavyweight
for tooling that just wants to answer "will this image boot against a
database migrated to N?"
> - Getting this wrong is expensive: an image ahead of the applied
schema crash-loops at startup and fails healthchecks after deployment
resources are already created
> - This pull request labels every published image with its bundled
migration set (last migration file and count), computed at build time
from `packages/db/src/migrations` in the same tree the image is built
from
> - The benefit is image/schema compatibility verification with two
cheap registry requests (manifest + config blob), no pull, and no drift
risk between label and image contents
## Linked Issues or Issue Description
No existing public issue; inline description per the feature request
template:
- **Subsystem affected**: Docker image publishing
(`.github/workflows/docker.yml`), `packages/db` migrations
- **Problem or motivation**: deployment tooling cannot cheaply determine
which schema migrations a published image expects; the only options are
pulling the image or checking out the matching commit. Deploying an
image whose bundled migrations exceed the applied schema makes the
server refuse to start, so this check is needed *before* resources are
created.
- **Proposed solution**: OCI labels
(`io.github.paperclipai.schema.last-migration`,
`io.github.paperclipai.schema.migration-count`), computed from the
migrations directory at build time via the existing
`docker/metadata-action` step. Keys use org-based reverse-DNS (the
GitHub org) so the label contract survives product-domain migrations.
- **Alternatives considered**: a schema manifest published beside the
image (second artifact to keep in sync — rejected); encoding schema info
in tags (tags already carry semver/sha meaning — rejected).
- **Roadmap alignment**: checked `ROADMAP.md` — no overlap with planned
core work; this is build metadata only.
## What Changed
- `.github/workflows/docker.yml`: a `Compute schema migration labels`
step (`ls` + `sort` over `packages/db/src/migrations/*.sql`) feeding two
custom labels into the existing `docker/metadata-action` step.
## Verification
- Workflow YAML validated locally.
- The label-computation commands run against the current tree produce
`last=0181_decision_training_retention_policy.sql`, `count=180`.
- After merge, verify with: fetch the image config blob for a fresh
`sha-*` tag from ghcr and confirm both `io.github.paperclipai.schema.*`
labels are present.
## Risks
- Low. Labels are metadata only; no change to image contents. If the
migrations directory ever moves, the label step fails the workflow
loudly (`ls` exits non-zero) rather than publishing wrong labels.
## Model Used
- Claude (Anthropic) — model id `claude-fable-5`, via the Claude Code
CLI harness with tool use (shell, file edits). Change authored and
verified agent-assisted, human-directed.
## 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 (YAML validation; label
commands — no app code changed)
- [x] I have added or updated tests where applicable (n/a — CI metadata
only)
- [x] I have updated relevant documentation to reflect my changes (n/a —
workflow comment documents the labels)
- [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 is the open source app people use to manage AI agents for
work.
> - Releases publish the same app and package set that operators
install, so release verification should keep full release-strength
coverage.
> - The release workflow currently verifies stable and canary releases
with one serial job that typechecks, runs all tests, and builds.
> - The PR workflow already proves the test surface can be split into
grouped general suites and serialized shards without changing coverage.
> - This pull request extracts the release verify work into a reusable
workflow and fans out the independent lanes.
> - The benefit is faster stable and canary release verification while
preserving the existing publish and preview gates.
## Linked Issues or Issue Description
No public GitHub issue exists for this CI improvement.
**Subsystem affected**
Cross-cutting (multiple of the above)
**Problem or motivation**
Release verification spends most of its wall time in a single serial
test step even though the same stable test surface is already
partitioned for PR CI. Stable dispatches and master-push canaries
therefore wait on one long runner after setup, typecheck, tests, and
build run sequentially.
**Proposed solution**
Add a reusable release verification workflow with parallel typecheck,
grouped general tests, serialized test shards, and build lanes. Have
both stable and canary release verification call it with the ref they
need to verify.
**Alternatives considered**
Keeping the serial `pnpm test:run` job preserves the old shape but keeps
stable and canary releases waiting on one long runner. Skipping
verification when a source SHA already has green CI would be faster, but
adds stale-check and lookup risk beyond this change.
**Roadmap alignment**
No overlapping item found in `ROADMAP.md`; this is release CI
maintenance.
**Additional context**
The new workflow keeps the release-strength full `pnpm -r typecheck`,
uses the existing stable test grouping/sharding entry points, and leaves
publish/preview jobs unchanged.
## What Changed
- Added `.github/workflows/release-verify.yml` as a `workflow_call`
workflow accepting a `ref` input.
- Split release verification into parallel `typecheck`, `general_tests`,
`serialized_tests`, and `build` jobs with 20-minute lane timeouts.
- Mirrored the PR workflow's stable test partition: `general-server`
shards 1-3, `general-workspaces-a`, `general-workspaces-b`, and four
serialized shards.
- Replaced `release.yml` `verify_canary` and `verify_stable` job bodies
with calls to the reusable workflow while leaving publish and preview
jobs unchanged.
- Added a Node test that guards the release workflow delegation and
split verify surface.
## Verification
- `actionlint 1.7.12 .github/workflows/release.yml
.github/workflows/release-verify.yml`
- `node ./scripts/release-package-map.mjs check`
- `node --test ./scripts/__tests__/release-verify-workflow.test.mjs
./scripts/__tests__/run-vitest-stable-shard.test.mjs`
- `git diff --check`
## Risks
- Release verification now starts more jobs per release event,
increasing total runner setup/install minutes. This matches the existing
PR CI tradeoff and should reduce release wall time substantially.
- The called workflow checks out the requested ref shallowly. That is
intentional for verify lanes; publish and preview jobs still retain
their existing full-history checkouts.
> 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, GPT-5-class coding agent in local tool-use mode with shell
execution, repository editing, GitHub connector access, and medium
reasoning.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Its UI is the operator's daily surface: task lists, boards, budgets,
agent status — all built on shadcn components and Tailwind
> - Visual values (colors, spacing, type sizes, radii) were hardcoded at
~1,600 call sites: the same "small gray label" was 9/10/11px depending
on the file, charts disagreed with chips about status colors, two
toggle-switch implementations coexisted in two greens, and there was no
visual regression coverage
> - This made the UI drift-prone and made any restyle a
hundreds-of-files project, which discourages design iteration
> - This pull request extracts visual values into a single token layer
in `ui/src/index.css`, adds a Storybook visual regression suite backed
by external immutable baseline archives, and then applies a deliberate
retune reviewed change-by-change on screenshot diffs
> - The benefit is that Paperclip's look becomes a config surface:
retheming is a token edit reviewed as a snapshot diff, drift is blocked
by a token gate, and future UI PRs can prove exactly what changed
visually without committing hundreds of PNGs
## Linked Issues or Issue Description
No existing public issue covers this work (searched "design tokens",
"visual regression", "design system" across issues and PRs). Related in
spirit: Refs #8982 (theming a hardcoded panel — a one-off instance of
the same problem class this PR addresses systematically).
**Problem (feature-request form):** UI visual values are hardcoded per
call site with no source of truth and no regression coverage;
consistency depends on reviewer memory, and restyling requires mass file
edits.
**Proposed solution (this PR):** a single token layer + enforcement gate
+ externally stored visual snapshot suite, then an intentional restyle
on top of that foundation.
## What Changed
- **Token extraction (zero visual change, machine-verified during
development):** committed codemods (`scripts/codemod-*.mjs`) moved
~1,600 hardcoded color/type/spacing/radius/shadow/misc values into named
tokens in a non-inline `:root` block of `ui/src/index.css`.
- **Visual regression suite:** `pnpm test:storybook-visual` covers 255
stories × light/dark = 510 Playwright screenshots at `maxDiffPixels: 0`,
plus new primitive-coverage stories and deterministic-render fixes.
- **External visual baselines:** committed PNG snapshots were removed.
`tests/storybook-visual/baseline-manifest.json` pins an immutable
archive URL/hash/size/count, and `scripts/storybook-visual-baseline.mjs`
handles `download`, `verify`, `pack`, and trusted maintainer `upload`
flows.
- **Opt-in visual CI artifacts:** added a `Storybook Visual` workflow
that runs on manual dispatch or PRs labeled `storybook-visual`,
downloads/verifies the baseline, runs Playwright, and uploads Playwright
report/test-result artifacts for review. Normal PR runs do not mutate
baseline objects.
- **Token gate:** `pnpm check:token-gates` — zero hex literals, zero
arbitrary bracket values, zero raw font-sizes in `ui/src/components/**`
and `ui/src/pages/**`, with a documented inline allowlist for legitimate
opt-outs.
- **Theme retune (intentional, snapshot-reviewed):** new base theme
values; radius ladder derived from a single `--radius` knob; micro-type
cluster collapsed to a named ladder (`--text-nano/micro/compact` +
Tailwind `text-xs`/`text-sm`); letter-spacing collapsed to named steps.
- **One status-color vocabulary:** charts, quota/budget bar fills,
RUNNING/live chips, and liveness indicators all use the canonical
`--status-*` hues. Light-mode legibility fixes for red alert surfaces
that used dark-tuned text classes.
- **One switch:** `ToggleSwitch` restyled to the registry capsule form,
second hand-rolled implementation removed, and all call sites unified.
- **Docs:** `DESIGN.md` is the design contract; `doc/design/` holds
audit reports, decision logs, and updated guidance for external baseline
review/update workflows.
- Dead code removed (`agentStatusBadge` duplicate map), byte-identical
contrast constants consolidated, semantic renames
(`--project-seed`/`--project-none`, `--liveness-blue`).
## Verification
- `pnpm check:token-gates` — 3/3 gates CLEAN during the design-system
run
- `pnpm typecheck` && `pnpm --filter @paperclipai/ui build` — green
during the design-system run
- `node --test scripts/__tests__/storybook-visual-baseline.test.mjs` —
pass after external-baseline rework
- `pnpm exec tsc --noEmit --pretty false --module NodeNext
--moduleResolution NodeNext --target ES2022 --types
node,@playwright/test tests/storybook-visual/playwright.config.ts
tests/storybook-visual/storybook-visual.spec.ts` — pass after
external-baseline rework
- `git diff --check origin/pr/9134..HEAD` — pass after external-baseline
rework
- `find tests/storybook-visual -type f -name '*.png' -print | wc -l` —
`0`
- `node scripts/storybook-visual-baseline.mjs verify` — intentionally
fails closed until the first trusted maintainer publishes the baseline
archive and updates `baseline-manifest.json`
## Risks
- **Large but shallow:** the PR still touches many UI files due to
mechanical token extraction and retune work, but committed PNG snapshot
churn has been removed from the branch.
- **Baseline publication required before the visual suite can pass in
clean clones:** the manifest currently has placeholder archive metadata.
A trusted maintainer must publish the first immutable archive, then
update `baseline-manifest.json`.
- **Rendering platform variance:** the external baseline should be
captured in the documented Linux/Chromium environment. Future CI runs
verify against the pinned archive and fail closed on checksum/count
mismatch.
- **Visual CI is opt-in while stabilizing:** add the `storybook-visual`
label or dispatch the workflow manually to produce downloadable
Playwright report/test-result artifacts.
- **Scheduled follow-ups, deliberately out of scope:** Tailwind palette
classes map to semantic tokens in a dedicated pass; card/pill component
consolidation; ESLint ratchet. Tracked in
`doc/design/DECISION-SHEET.md`.
## Model Used
Claude Fable 5 (Anthropic, `claude-fable-5`, Mythos-class tier) with
extended thinking, running in Claude Code with tool use; mechanical
phases delegated to Claude Sonnet subagents. Follow-up external-baseline
rework assisted by OpenAI Codex (`gpt-5` coding agent with repository,
terminal, and GitHub tool use). All bulk rewrites executed via
deterministic, idempotent scripts committed in `scripts/`; intentional
visual changes were human-reviewed on screenshot contact sheets.
## 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 targeted local verification and documented the
intentional baseline-publication failure 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
- [ ] All Paperclip CI gates are green *(pending new CI run after this
rework)*
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
*(pending review)*
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code) and
OpenAI Codex
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Dotta <bippadotta@protonmail.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - CI runs a Canary Dry Run job that exercises `release.sh`, which
builds the standalone sandbox-provider packages for publish
> - That step (`scripts/build-standalone-public-packages.mjs`) built the
7 provider plugins serially — each doing `rm -rf dist && tsc` — making
it the dominant cost (~49s) inside the slowest PR check (~4.9m wall)
after the general-server lane was already sharded
> - The packages are independent (their own `node_modules` via
`--ignore-workspace`, their own `dist`), so the serial build is pure
latency with no correctness benefit
> - This pull request builds them with a bounded-concurrency pool sized
to the runner CPU count (overridable via
`STANDALONE_BUILD_CONCURRENCY`), buffering each package's output and
flushing it as one block so parallel logs stay readable, and aggregating
failures by original index
> - The benefit is a faster Canary Dry Run / PR feedback loop without
changing what gets built or published
## Linked Issues or Issue Description
No public GitHub issue exists. Inline feature/perf description:
### Problem or motivation
`build-standalone-public-packages.mjs` builds standalone provider
packages serially, making it the largest single cost inside the slowest
PR check.
### Proposed solution
Run independent per-package builds through a bounded-concurrency worker
pool sized to runner CPU count, with an env override and readable
buffered logs.
### Alternatives considered
Keep the serial build for simpler logs, but that preserves the avoidable
CI latency.
### Roadmap alignment
This is CI maintenance and does not overlap planned core roadmap work.
## What Changed
- `scripts/build-standalone-public-packages.mjs`: replaced the serial
per-package build loop with a bounded-concurrency pool (default = runner
CPU count, override via `STANDALONE_BUILD_CONCURRENCY`); per-package
stdout/stderr is buffered and flushed as a single block; failures are
aggregated by original package index so one failure neither aborts the
others mid-flight nor obscures which package broke.
- `scripts/__tests__/build-standalone-concurrency.test.mjs`: new
`node:test` unit suite covering the pool (limit respected, all items
run, ordered failure aggregation, env-override resolution).
- `.github/workflows/pr.yml`: wired the new unit test into the policy
job.
## Verification
- `node --test
./scripts/__tests__/build-standalone-concurrency.test.mjs` → 6/6 pass
- `node ./scripts/release-package-map.mjs check` → OK (29 enabled for CI
publish)
- `git diff --check origin/master..HEAD` → clean
## Risks
- Low risk. Build inputs/outputs are unchanged; only scheduling differs.
The concurrency is bounded by CPU count and overridable; output is
buffered per package so logs remain attributable. If a package fails,
all failures are still reported with their package index.
## Model Used
- Claude (Anthropic), `claude-opus-4-8`, extended thinking with tool
use.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Contributors follow `CONTRIBUTING.md` and the PR template when
opening pull requests
> - Those docs still tell contributors to manually capture and attach
before/after screenshots for UI changes
> - That requirement is now redundant: the `cutter.sh` bot automatically
captures and posts before/after UI screenshots to every PR
> - Asking contributors to also do it by hand adds friction and produces
duplicate screenshots
> - This pull request removes the manual screenshot obligation from
`CONTRIBUTING.md` and the PR template, and documents that `cutter.sh`
handles it
> - The benefit is a clearer, lower-friction contribution flow that
matches how screenshots actually get posted today
## Linked Issues or Issue Description
No public GitHub issue. Underlying change:
Our contributor docs and PR template instructed contributors to attach
before/after screenshots for any UI change. We now run a bot
(`cutter.sh`) that automatically captures and posts before/after UI
screenshots to the PR, so the manual step is no longer needed. This
change removes the manual requirement and instead documents that the bot
handles screenshots, asking contributors to simply describe the visible
change.
## What Changed
- `CONTRIBUTING.md`: removed the "Before / After screenshots"
requirement from the Path 2 PR checklist and from the "Writing a Good PR
message" section; both now explain that the `cutter.sh` bot posts UI
screenshots automatically and ask contributors to describe the visible
change instead.
- `.github/PULL_REQUEST_TEMPLATE.md`: updated the Verification guidance
to note that `cutter.sh` posts before/after screenshots automatically,
and removed the "include before/after screenshots" item from the
checklist.
- Issue templates were intentionally left unchanged — they only offer
screenshots as an optional "if helpful" field, never a requirement.
## Verification
- Docs-only change; no code paths affected.
- `grep -rin "screenshot" CONTRIBUTING.md .github/` confirms no
screenshot *requirement* remains — only the new `cutter.sh` wording and
the optional "if helpful" fields in issue templates.
- Render `CONTRIBUTING.md` and the PR template to confirm the wording
reads cleanly.
## Risks
Low risk — documentation-only change. No build, runtime, or migration
impact.
## Model Used
Claude (Anthropic), model `claude-opus-4-8`, extended thinking with tool
use.
## 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 (docs-only; no tests apply)
- [ ] I have added or updated tests where applicable (N/A — docs-only)
- [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 (pending CI)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Every PR runs the `PR` GitHub Actions workflow, whose `verify` gate
fans out into parallel test lanes (general tests, serialized server
route suites, build, typecheck)
> - The `General tests (server)` lane had grown into the run's critical
path: it executed all ~213 non-route server suites serially in a single
job (~7.2m of test time), more than 2x any other job
> - It runs serially because `server/vitest.config.ts` pins `maxWorkers:
1`, so server suites cannot parallelize within a single runner — the
only lever is spreading them across runners
> - This pull request shards that lane into 3 even partitions that run
on separate runners, mirroring the 4-way sharding already used for the
serialized route suites
> - The benefit is the lane drops from ~7.7m to ~2.4m/shard, cutting
overall PR wall time roughly in half (~8.5m → ~4.2m)
## Linked Issues or Issue Description
No public GitHub issue exists for this work, so the underlying issue is
described inline following the feature-request template.
### Problem or motivation
PR CI wall time had crept back up to ~8.5m. On a recent fully-green run,
the `General tests (server)` job took 7.72m — more than double any other
job and the clear critical path. Of that, 7.23m was pure test execution
(dependency install was a cached 0.27m). The job ran all server suites
that are not route/authz tests (213 files) one after another, because
the server vitest project pins `maxWorkers: 1`, making these suites
inherently serial within a single runner.
### Proposed solution
Shard the general-server lane across 3 parallel runners — the same
technique the route/authz suites already use — so the suite set is split
into even, deterministic partitions that run concurrently. Add a
regression test that proves the shards always cover the full suite set
with no gaps or overlap.
### Alternatives considered
- **Raise `maxWorkers` for the server project** to parallelize within
one runner — rejected: the server suites share process-level state
(DB/port), which is exactly why `maxWorkers: 1` is pinned.
- **Two shards instead of three** — would leave the lane at ~3.6m, still
above the next bottleneck (Canary Dry Run, ~4.1m wouldn't be the gate).
Three lands the lane comfortably below it.
- **Do nothing / accept the slow lane** — rejected: it gates every PR.
### Roadmap alignment
Developer-experience / CI tooling. Not core product roadmap work; does
not overlap with planned features in `ROADMAP.md`.
## What Changed
- `scripts/run-vitest-stable.mjs`: the `general-server` general-test
group now accepts `--shard-index` / `--shard-count`. It enumerates the
full server test set (the whole `server/src` tree, minus the route/authz
suites that already run in their own serialized shards) and splits it
deterministically by modulo. The non-sharded local invocation (`pnpm
test:run:general --group general-server`) is unchanged.
- `.github/workflows/pr.yml`: the `general_tests` matrix runs
`general-server` as 3 parallel shards (1/3, 2/3, 3/3). Workspace groups
are unchanged. The `verify` gate already aggregates the whole matrix
result, so the required check name is unaffected.
- `scripts/__tests__/run-vitest-stable-shard.test.mjs`: a `node:test`
suite asserting the 3 shards form a complete, non-overlapping partition
of the general-server set, that no route/authz suite leaks into it, and
that shard flags are rejected for the parallel workspace groups. Wired
into the `policy` job.
## Verification
- New partition test passes locally: `node --test
./scripts/__tests__/run-vitest-stable-shard.test.mjs` (3/3).
- Confirmed the 3 shards form a complete, non-overlapping partition of
all 213 files (71/71/71).
- Ran a live thin shard (3 real server suites, including one outside
`__tests__`) — 23 tests passed, confirming positional-include execution
works end to end.
- This PR's own CI is the authoritative check: all three `General tests
(server (n/3))` jobs went green on the prior run, collectively covering
every suite the old single job ran.
## Risks
- Low risk. No product code changes — only test orchestration and CI
matrix. Shard partitioning is deterministic and is now covered by an
automated test that fails if the partition ever develops a gap or
overlap. Modulo-on-sorted-filenames balances duration reasonably,
matching the approach already proven by the serialized route shards.
## Model Used
- Claude (Anthropic), `claude-opus-4-8`, extended thinking + tool use
(agentic coding via Paperclip).
## 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
- [ ] If this change affects the UI, I have included before/after
screenshots (N/A — no UI change)
- [x] I have updated relevant documentation to reflect my changes
(inline comments explain the sharding rationale)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending this PR's run)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [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, distributed primarily via the `paperclipai` npm package and its
`@paperclipai/*` workspace packages.
> - The release subsystem (`scripts/release-package-*`) decides which
workspace packages CI republishes at the unified calver version each
release, and `replaceWorkspaceDeps()` rewrites every internal
`workspace:` dependency to that calver version at publish time.
> - The gap: a package that publishes from CI can declare a `workspace:`
dependency on a package that is NOT enrolled for CI publish. The
dependency spec gets rewritten to a calver version that is never
actually published, so the dependent becomes uninstallable.
> - This shipped for real: a recent change made `@paperclipai/server`
depend on `@paperclipai/skills-catalog`, but skills-catalog was not on
the calver release train — so canary builds after that merge failed to
resolve `@paperclipai/skills-catalog@<calver>` and `npx
paperclipai@canary run` broke.
> - This PR addresses it durably by (a) putting every remaining internal
package on the CI release train so no internal dependency can dangle,
and (b) adding a fail-fast guard so this class of break can never ship
again.
> - The benefit is that canary and stable installs resolve all internal
dependencies, and any future unpublishable workspace edge fails the
release build with a clear, named error instead of producing a broken
package.
## Linked Issues or Issue Description
Refs #8327 (introduced the `server -> skills-catalog` runtime dependency
that surfaced the gap).
No public issue tracks this; describing it in-PR:
- **Problem:** After #8327, `npx paperclipai@canary run` failed for
builds past the merge because `@paperclipai/skills-catalog` was
rewritten to a calver version that was never published (the package was
not enrolled for CI publish). Versions before the merge still ran.
- **Root cause:** A `publishFromCi:true` package can declare a
`workspace:` dependency on a package that is not `publishFromCi:true`;
the release-time version rewrite then points at a non-existent published
version.
## What Changed
- Enrolled every remaining internal package on the calver release train
by setting `publishFromCi: true` in
`scripts/release-package-manifest.json`: `skills-catalog`,
`teams-catalog`, `plugin-workspace-diff`, `plugin-kubernetes`,
`plugin-novita-sandbox`. There are now zero `publishFromCi:false`
entries.
- `skills-catalog`, `teams-catalog`, `plugin-workspace-diff` already
existed on npm — CI simply republishes them at calver.
- `plugin-kubernetes` and `plugin-novita-sandbox` were not on npm; their
one-time first publish was bootstrapped so the
`check-release-package-bootstrap` gate passes.
- Added `findUnpublishableWorkspaceEdges()` to
`scripts/release-package-map.mjs`, wired into
`buildReleasePackagePlan()`. The release map build now fails fast
(surfaced by the `check` CI already runs) whenever a
`publishFromCi:true` package declares a runtime `workspace:` dependency
(`dependencies`/`optionalDependencies`/`peerDependencies`) on a
non-`publishFromCi:true` `@paperclipai/*` package, naming the offending
edge.
- Added tests covering positive/negative detection, all three dependency
sections, unknown-package edges, off-train edges, and the live manifest.
## Verification
- `node --test scripts/release-package-map.test.mjs` → 9/9 pass
(includes a test asserting the live manifest has no unpublishable
edges).
- `node scripts/check-release-package-bootstrap.mjs
scripts/release-package-manifest.json` → passes, naming all five
newly-enabled packages (all confirmed present on npm).
- Confirmed via `npm view` that all five packages resolve on the public
registry.
## Risks
- Low risk. The change only enrolls already-existing (or
freshly-bootstrapped) packages onto the existing release train and adds
a build-time validation. No runtime code paths change. The new guard can
only *fail* a release that was already going to ship a broken package.
## Model Used
Claude Opus 4.7 (claude-opus-4-7), extended thinking, with tool use /
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 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] If this change affects the UI, I have included before/after
screenshots (N/A — no UI changes)
- [x] I have updated relevant documentation to reflect my changes (N/A —
no doc-facing behavior change)
- [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 is the open source app people use to manage AI agents for
work
> - Contributions flow through GitHub PRs against
`paperclipai/paperclip`, but most contributors author their work inside
their own private Paperclip instance
> - That tooling names a working branch after the internal issue/task —
e.g. `PAPA-42-why-did-this-break`
> - When that branch is pushed for a PR, the public PR's head branch
carries instance-local context: it's not meaningful to reviewers and
leaks instance-derived identifiers
> - We just documented the issue-reference side of this (#8292), but
said nothing about branch names
> - This pull request adds a **Branch Naming** section to
`CONTRIBUTING.md` and a matching PR-template checklist item, telling
contributors to rename branches to descriptive, change-scoped names
before pushing
> - The benefit is public PRs whose branch names describe the change and
contain no instance-local details
## Linked Issues or Issue Description
Fixes: #8311
Refs: #8292
## What Changed
- `CONTRIBUTING.md`: new **Branch Naming** subsection — rename
instance-named branches (e.g. `PAPA-42-...`) to descriptive,
change-scoped, kebab-case names before pushing, with examples and the
rename commands.
- `.github/PULL_REQUEST_TEMPLATE.md`: new checklist item asserting the
branch name describes the change and contains no internal ticket id or
instance-derived details.
## Verification
Docs-only change. Rendered Markdown locally and confirmed the new
**Branch Naming** section and the new checklist item appear as intended.
This PR's own branch (`docs/contributor-branch-naming`) follows the new
guidance. No code paths affected.
## Risks
Low risk — documentation and PR-template text only; no runtime, build,
or schema impact. Automatically renaming branches at push time in the
tooling is intentionally out of scope and tracked as separate follow-up
work.
## Model Used
Claude Opus 4.7 (claude-opus-4-7), extended thinking, agentic tool use
via Claude Code.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [ ] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [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>
Bumps
[actions/download-artifact](https://github.com/actions/download-artifact)
from 4 to 8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/download-artifact/releases">actions/download-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v8.0.0</h2>
<h2>v8 - What's new</h2>
<blockquote>
<p>[!IMPORTANT]
actions/download-artifact@v8 has been migrated to an ESM module. This
should be transparent to the caller but forks might need to make
significant changes.</p>
</blockquote>
<blockquote>
<p>[!IMPORTANT]
Hash mismatches will now error by default. Users can override this
behavior with a setting change (see below).</p>
</blockquote>
<h3>Direct downloads</h3>
<p>To support direct uploads in <code>actions/upload-artifact</code>,
the action will no longer attempt to unzip all downloaded files.
Instead, the action checks the <code>Content-Type</code> header ahead of
unzipping and skips non-zipped files. Callers wishing to download a
zipped file as-is can also set the new <code>skip-decompress</code>
parameter to <code>true</code>.</p>
<h3>Enforced checks (breaking)</h3>
<p>A previous release introduced digest checks on the download. If a
download hash didn't match the expected hash from the server, the action
would log a warning. Callers can now configure the behavior on mismatch
with the <code>digest-mismatch</code> parameter. To be secure by
default, we are now defaulting the behavior to <code>error</code> which
will fail the workflow run.</p>
<h3>ESM</h3>
<p>To support new versions of the @actions/* packages, we've upgraded
the package to ESM.</p>
<h2>What's Changed</h2>
<ul>
<li>Don't attempt to un-zip non-zipped downloads by <a
href="https://github.com/danwkennedy"><code>@danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/download-artifact/pull/460">actions/download-artifact#460</a></li>
<li>Add a setting to specify what to do on hash mismatch and default it
to <code>error</code> by <a
href="https://github.com/danwkennedy"><code>@danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/download-artifact/pull/461">actions/download-artifact#461</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/download-artifact/compare/v7...v8.0.0">https://github.com/actions/download-artifact/compare/v7...v8.0.0</a></p>
<h2>v7.0.0</h2>
<h2>v7 - What's new</h2>
<blockquote>
<p>[!IMPORTANT]
actions/download-artifact@v7 now runs on Node.js 24 (<code>runs.using:
node24</code>) and requires a minimum Actions Runner version of 2.327.1.
If you are using self-hosted runners, ensure they are updated before
upgrading.</p>
</blockquote>
<h3>Node.js 24</h3>
<p>This release updates the runtime to Node.js 24. v6 had preliminary
support for Node 24, however this action was by default still running on
Node.js 20. Now this action by default will run on Node.js 24.</p>
<h2>What's Changed</h2>
<ul>
<li>Update GHES guidance to include reference to Node 20 version by <a
href="https://github.com/patrikpolyak"><code>@patrikpolyak</code></a>
in <a
href="https://redirect.github.com/actions/download-artifact/pull/440">actions/download-artifact#440</a></li>
<li>Download Artifact Node24 support by <a
href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/415">actions/download-artifact#415</a></li>
<li>fix: update <code>@actions/artifact</code> to fix Node.js 24
punycode deprecation by <a
href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/451">actions/download-artifact#451</a></li>
<li>prepare release v7.0.0 for Node.js 24 support by <a
href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/452">actions/download-artifact#452</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/patrikpolyak"><code>@patrikpolyak</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/download-artifact/pull/440">actions/download-artifact#440</a></li>
<li><a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/download-artifact/pull/415">actions/download-artifact#415</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/download-artifact/compare/v6.0.0...v7.0.0">https://github.com/actions/download-artifact/compare/v6.0.0...v7.0.0</a></p>
<h2>v6.0.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3e5f45b2cf"><code>3e5f45b</code></a>
Add regression tests for CJK characters (<a
href="https://redirect.github.com/actions/download-artifact/issues/471">#471</a>)</li>
<li><a
href="e6d03f6737"><code>e6d03f6</code></a>
Add a regression test for artifact name + content-type mismatches (<a
href="https://redirect.github.com/actions/download-artifact/issues/472">#472</a>)</li>
<li><a
href="70fc10c6e5"><code>70fc10c</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/461">#461</a>
from actions/danwkennedy/digest-mismatch-behavior</li>
<li><a
href="f258da9a50"><code>f258da9</code></a>
Add change docs</li>
<li><a
href="ccc058e5fb"><code>ccc058e</code></a>
Fix linting issues</li>
<li><a
href="bd7976ba57"><code>bd7976b</code></a>
Add a setting to specify what to do on hash mismatch and default it to
<code>error</code></li>
<li><a
href="ac21fcf45e"><code>ac21fcf</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/460">#460</a>
from actions/danwkennedy/download-no-unzip</li>
<li><a
href="15999bff51"><code>15999bf</code></a>
Add note about package bumps</li>
<li><a
href="974686ed50"><code>974686e</code></a>
Bump the version to <code>v8</code> and add release notes</li>
<li><a
href="fbe48b1d27"><code>fbe48b1</code></a>
Update test names to make it clearer what they do</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/download-artifact/compare/v4...v8">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Contributions flow through GitHub PRs against
`paperclipai/paperclip`, but most contributors author their work inside
their own private Paperclip instance
> - Those instances mint instance-local issue ids and deep links
(`PAPA-123`, `/PAP/issues/...`, `agent://...`, localhost/tailnet URLs)
that only the author can open
> - When contributors paste those references into a public PR, reviewers
hit clutter and broken links and have to ask for cleanup
> - There was no written guidance telling contributors (human or agent)
to keep those references out
> - This pull request adds that guidance to `CONTRIBUTING.md` and
surfaces it as a PR-template checklist item plus an inline hint
> - The benefit is cleaner PRs that reference only public, openable
GitHub issues/PRs, with internal context restated in plain English
## Linked Issues or Issue Description
Fixes: #8292
## What Changed
- `CONTRIBUTING.md`: new **No Internal Issue References** subsection
explaining what to leave out (internal ticket ids, instance UI/deep
links, localhost/tailnet URLs) and to restate useful context in plain
English; added a cross-reference sentence from the existing "Link Issues
or Describe Them In-PR" guidance.
- `.github/PULL_REQUEST_TEMPLATE.md`: new checklist item, plus an inline
hint in the Linked Issues comment block, directing contributors to
reference only public GitHub issues/PRs.
## Verification
Docs-only change. Rendered Markdown locally and confirmed the new
`#no-internal-issue-references` anchor resolves from the cross-reference
link, and that the template checklist item appears in the rendered list.
No code paths affected.
## Risks
Low risk — documentation and PR-template text only; no runtime, build,
or schema impact. An optional automated CI gate was intentionally
deferred to a follow-up to avoid regex false positives.
## Model Used
Claude Opus 4.7 (claude-opus-4-7), extended thinking, agentic tool use
via Claude Code.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] I have run tests locally and they pass
- [ ] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [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
- [ ] 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
> - CI/CD pipelines use GitHub Actions to automate builds and
deployments of services
> - The `agent-runtime-images` workflow uses `docker/login-action` for
authenticating with Docker registries before pushing images
> - `docker/login-action` v3 is now superseded; v4 was released with
Node 24 as the default runtime and updated internal dependencies
(`@actions/core`, AWS SDK, `@docker/actions-toolkit`)
> - This pull request bumps `docker/login-action` from v3 to v4 in the
`agent-runtime-images.yml` workflow
> - The benefit is staying on a supported runtime version and receiving
upstream security and dependency updates
## Linked Issues or Issue Description
Automated dependency bump — no upstream issue. The underlying need is
routine dependency maintenance:
- **Change type:** Dependency version bump (GitHub Actions)
- **Scope:** Single workflow file, single-line version tag change
- **Motivation:** v4 modernizes the action runtime to Node 24 and
receives updated `@actions/core` (3.x) and AWS SDK dependencies with
security patches
## What Changed
- Bumps `docker/login-action` from `v3` → `v4` in
`.github/workflows/agent-runtime-images.yml`
- v4 switches to Node 24 as the default Actions runtime (requires runner
v2.327.1+; GitHub-hosted runners satisfy this automatically)
- No changes to the action's inputs, outputs, or behavior — the API is
fully backward-compatible
## Verification
- All CI checks pass (green) on this PR
- No input/output interface changes; the login step behavior is
identical
- GitHub-hosted runners automatically meet the Node 24 runtime
requirement (runner v2.327.1+)
## Risks
Low risk. This is a one-line version bump on a widely-used, officially
maintained Docker action. The v4 release has no breaking changes to
inputs or outputs. The only new requirement (Node 24 / runner v2.327.1+)
is satisfied by GitHub-hosted runners automatically.
## Model Used
None — automated Dependabot bump. PR description updated by Claude
Sonnet 4.6 (Anthropic, 200k context, tool use enabled) to satisfy the PR
template requirement.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [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
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Summary
- Adds `@nickyleach` as a codeowner alongside `@cryppadotta` and
`@devinfoley` on all 13 existing entries
- Grants approval rights on `.github/**`, release scripts, docs,
`skills/**`, and package files
## Thinking Path
The task is to give @nickyleach the same CODEOWNERS coverage as the
existing owners. The simplest approach is to append `@nickyleach` to
every existing entry in `.github/CODEOWNERS`. No new paths are
introduced — this is a pure access grant.
## What Changed
- `.github/CODEOWNERS`: added `@nickyleach` to all 13 path patterns
alongside the existing `@cryppadotta` and `@devinfoley` owners
## Verification
- Reviewed the diff: every existing CODEOWNERS line now includes
`@nickyleach` as a third owner
- GitHub will enforce the new ownership on future PRs touching the
covered paths
## Risks
- Low risk: CODEOWNERS is additive-only; no existing owner is removed
- The change takes effect immediately on merge — future PRs touching
covered paths will require @nickyleach approval (or any of the three
owners)
## Model Used
claude-sonnet-4-6
## Test plan
- [ ] Verify GitHub picks up the new CODEOWNERS entry and shows
@nickyleach as a required reviewer on future PRs touching these paths
Closes PAP-61
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Paperclip <noreply@paperclip.ing>
> [!NOTE]
> This is **stage 3 of 3** of the staged Kubernetes contribution: stage
1 is the kubernetes sandbox-provider plugin (#5790), stage 2 is the
provider backend/hardening refresh filed separately, and this stage
ships the runtime images those sandboxes run.
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandboxed agent execution (Refs #248) runs each agent turn in an
isolated environment; the kubernetes sandbox provider (stage 1, #5790)
schedules those runs as hardened pods
> - A sandbox pod needs a runtime image with the harness CLI
preinstalled: installing CLIs at run start is slow, flaky, and needs
network egress the sandbox should not have
> - There is no first-party image family for this, so every deployer
would have to hand-roll Ubuntu + Node + CLI images per harness and solve
signal handling, non-root, and image chaining themselves
> - This PR ships the agent-runtime image family: a hardened base
(non-root uid 1000, tini, git, the agent shim) plus one derived image
per harness, a buildx bake file that chains them, and a publish workflow
with cosign keyless signing
> - The benefit is that any sandbox infrastructure, the kubernetes
provider or otherwise, gets ready-made, signed, security-hardened
per-harness runtime images that are verified in production across five
harnesses
## Linked Issues or Issue Description
Refs #248 (sandboxed agent execution proposal) and #5790 (the kubernetes
sandbox provider, stage 1 of this contribution, which consumes these
images as per-run runtime images via its adapter defaults).
No issue covers the image gap itself, described in-PR: sandbox providers
reference `ghcr.io/paperclipai/agent-runtime-*` images, but the
repository contains neither the Dockerfiles nor the workflow that builds
and publishes them. Without this, self-deployers cannot reproduce or
audit the images their agent runs execute in.
## What Changed
- `docker/agent-runtime/Dockerfile.base`: foundation image. Ubuntu 22.04
+ Node 22 + git + tini (PID 1, signal propagation) + non-root
`paperclip` user (uid/gid 1000) + the agent shim compiled in a Go build
stage. `WORKDIR /workspace`, entrypoint `tini -- paperclip-agent-shim`.
- One derived Dockerfile per harness: `opencode` (opencode-ai), `pi`
(@mariozechner/pi-coding-agent), `codex` (@openai/codex), `gemini`
(@google/gemini-cli, plus headless auth-mode settings), `claude`
(@anthropic-ai/claude-code, symlinked as `claude-code`). Each installs
the CLI as root, returns to uid 1000, and asserts the binary is on PATH
at build time.
- `acpx` and `hermes` Dockerfiles are included in the bake group but are
not in the default publish scope (hermes is a stub until a CLI package
exists).
- `docker/agent-runtime/buildx-bake.hcl`: builds the whole family in one
pass. Derived targets chain off the `base` target through bake
`contexts` (the literal registry in each `FROM` is overridden to
`target:base` at build time, so no intermediate push is needed).
`REGISTRY` (default `ghcr.io/paperclipai`) and `VERSION` are overridable
variables.
- `tools/agent-shim/`: a small Go shim that runs as the container
command. It reads `/run/paperclip/runtime-command.json` (`{ "command",
"args" }`), resolves the harness CLI on PATH, and `syscall.Exec`s it so
SIGTERM from the kubelet reaches the harness directly. Harness-agnostic,
with unit tests.
- `.github/workflows/agent-runtime-images.yml`: builds and pushes the
default scope (base, opencode, pi, codex, gemini, claude) for
linux/amd64 on `workflow_dispatch` (explicit version tag) or pushes to
`master` touching these paths, then signs every digest with cosign
keyless OIDC. Uses only `GITHUB_TOKEN`; no extra secrets.
- `docker/agent-runtime/README.md`: image lineup, base contents, local
build instructions, the runtime-command contract, and the security
model.
Additive only: nothing in the product loads these images. Deployments
opt in via their sandbox provider configuration (for example the
kubernetes plugin's image settings).
## Verification
- `cd tools/agent-shim && go build ./... && go test ./... && go vet
./...`: all passing.
- `docker buildx bake -f docker/agent-runtime/buildx-bake.hcl --print
base opencode pi codex gemini claude`: resolves cleanly; every tag and
build context lands on `ghcr.io/paperclipai/agent-runtime-*` and derived
targets map the base ref to `target:base`.
- Workflow YAML validated (parses, single job, no org-specific secrets).
- This exact image family (built from these Dockerfiles, bake file, and
workflow) is what runs agent execution in production on paperclip.inc,
verified end-to-end across five harnesses (opencode, pi, codex, gemini,
claude): each as a full loop from assigned issue to per-run runtime
image in a sandboxed pod to completed run.
## Risks
- Low risk: purely additive, nothing in paperclip-server or the UI
references these files. The workflow only triggers on its own paths.
- Derived images install harness CLIs `@latest` at build time; a broken
upstream CLI release would surface at image build, not at run time, and
the PATH assertion fails the build rather than shipping a broken image.
- The hermes image is an explicit stub (documented in its Dockerfile)
until a hermes CLI package exists; it is outside the default publish
scope.
- cosign signing is keyless OIDC with the workflow identity; no
long-lived signing keys are introduced.
## Model Used
Claude Opus 4.8 (claude-opus-4-8, 1M context, extended thinking, tool
use via Claude Code).
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots (no UI changes)
- [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: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The commitperclip review workflow runs a security gate as part of CI
on every PR
> - The security script's header promises it always exits 0 and stays
silent/informational, but PRs that triggered a flag were failing with a
5-minute timeout
> - Two compounding bugs: `findExistingDraftAdvisory` paginated without
an upper bound, and the workflow step did not have `continue-on-error:
true`, so any hang inside the script turned into a hard `review` check
failure that blocked merge
> - This pull request caps the advisory pagination at 20 pages and adds
`continue-on-error: true` to the workflow step, aligning runtime
behavior with the script's documented "always exit 0" contract
> - The benefit is that future PRs flagged by the security gate no
longer block merge on a 5-minute timeout, and the gate stays
silent/informational as intended
## Linked Issues or Issue Description
Fixes: #7849
## What Changed
- `.github/workflows/commitperclip-review.yml`: added
`continue-on-error: true` to the `Run security gates` step so a hang or
non-zero exit cannot fail the `review` check (matches the script's
documented "always exit 0" contract).
- `.github/scripts/check-pr-security.mjs`: capped
`findExistingDraftAdvisory` pagination at 20 pages (= 2000 advisories)
and short-circuited with a `console.warn` when the cap is hit; if no
match is found within the cap, callers will simply create a new draft
instead of hanging forever.
- `.github/scripts/tests/check-pr-security.test.mjs`: added a test
asserting the pagination cap is enforced.
## Verification
- `node .github/scripts/tests/check-pr-security.test.mjs` — 31/31 pass,
including the new cap test.
- Step-level guarantee: `continue-on-error: true` makes the `Run
security gates` step non-blocking for the job, so even an unexpected
hang/timeout in this step can no longer fail the `review` check.
## Risks
- Low risk. Pagination cap is a defensive bound; the worst case is a
duplicate draft advisory (acceptable — the workflow continues).
`continue-on-error: true` is exactly what the script header already
promised; the workflow now matches its stated contract.
## Model Used
- Claude (claude-opus-4-7), extended thinking, tool use
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The PR pipeline runs a "Canary Dry Run" step that validates
dependency PRs against a regenerated `pnpm-lock.yaml` before merge
> - When a dependabot (or other) PR's regenerated lockfile is
byte-identical to `HEAD`, `git commit` exits non-zero with "nothing to
commit, working tree clean"
> - The surrounding `bash -e` step propagates the non-zero exit, failing
the Canary Dry Run for PRs that should be allowed through
> - This pull request guards the commit with `git diff --cached --quiet`
so the step only commits when there is a real staged change
> - The benefit is that dependabot PRs (#7571 storybook bump, #7572) and
any future PR where regen happens to match HEAD stop getting spuriously
blocked by the canary step
## Linked Issues or Issue Description
Refs #7571
Refs #7572
PR #7571 (dependabot: storybook 10.3.5 → 10.4.2) and PR #7572 both fail
on the exact same line of the `Canary Dry Run` step:
```
+ git -c user.email=ci@paperclip.local -c user.name=CI commit --no-verify -m 'ci(canary): stage regenerated lockfile'
On branch master
nothing to commit, working tree clean
Error: Process completed with exit code 1.
```
The step regenerates `pnpm-lock.yaml`, stages it, and unconditionally
commits. When the regenerated lockfile matches `HEAD` exactly (which
happens for some dependabot bumps where the lockfile resolution did not
actually change), `git commit` exits 1 and `bash -e` fails the entire
step.
## What Changed
- `.github/workflows/pr.yml`: Wrap the `git commit` inside the Canary
Dry Run step in `if ! git diff --cached --quiet; then ... fi`, so the
commit is skipped when there is no staged diff.
## Verification
- Reproduced the failure on PR #7571 and PR #7572 (identical stack trace
at the `git commit` line of the Canary Dry Run step).
- Local sanity check: `git diff --cached --quiet` returns 0 (no diff)
when the regen is a no-op and non-zero when there is a real change,
which matches the intended branching.
- Once merged, dependabot PRs that previously stalled on this step
should re-run the Canary Dry Run cleanly.
## Risks
- Low risk: the change only adds a guard before an existing `git
commit`. The `else` branch (`git checkout -- pnpm-lock.yaml` when no
artifact lockfile was used) is unchanged. No behavior change when there
*is* a regen diff.
## Model Used
- Claude (Anthropic) — `claude-opus-4-7`, extended thinking off, tool
use enabled.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass (workflow YAML change;
verified via shell sanity check of the guard condition)
- [ ] I have added or updated tests where applicable (no test harness
for workflow YAML)
- [x] If this change affects the UI, I have included before/after
screenshots (n/a — CI workflow only)
- [x] I have updated relevant documentation to reflect my changes (n/a)
- [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
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Issue-thread interactions are one of the core ways agents pause for
structured board or user decisions.
> - Adding a new interaction kind currently requires coordinated changes
across shared contracts, server behavior, UI cards, fixtures,
CLI/MCP/plugin SDK helpers, and agent guidance.
> - The checkbox-confirmation rollout established a good end-to-end
pattern, but contributors needed a durable maintainer checklist for
repeating that work.
> - This pull request adds a developer/maintainer skill that captures
that workflow inside the repo under `.agents/skills`.
> - The benefit is a reusable implementation guide for future
interaction-card work without installing that guidance on runtime
Paperclip agents.
## Linked Issues or Issue Description
Paperclip issue: [PAP-10457](/PAP/issues/PAP-10457)
This PR documents the process for adding a new issue-thread interaction
family end-to-end. There is no GitHub issue for this Paperclip-internal
skill addition.
## What Changed
- Added `.agents/skills/create-issue-interaction-ui/SKILL.md` as a
developer/maintainer skill.
- Covered shared contract, server route/service behavior, UI card
wiring, fixtures/Storybook, CLI/MCP/plugin SDK helpers, agent guidance,
invariants, and focused verification.
- Referenced the checkbox-confirmation rollout (`4d5322c82`, PR `#7649`)
as the canonical worked example.
## Verification
- `git diff --check origin/master..HEAD`
- `NODE_ENV=test pnpm exec vitest run
server/src/__tests__/paperclip-skill-utils.test.ts`
- Reviewed the added skill markdown for scope, location, and workflow
completeness.
## Risks
Low risk. This is a documentation/skill-only change under
`.agents/skills`; it does not change runtime code, database schema, API
behavior, or installed production-agent guidance.
> 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 coding agent based on GPT-5, with shell and GitHub CLI tool
access. Exact hosted model variant and context-window size were not
exposed by the runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies, and
`github.com/paperclipai/paperclip` is the public intake for bugs and
feature work
> - PR #7575 (just merged) shipped a first cut of
`.github/ISSUE_TEMPLATE/`: bug / feature / adapter forms plus a chooser
config that disables blank issues — enough to stop greeting filers with
an empty textarea
> - Reviewing [PR
#4189](https://github.com/paperclipai/paperclip/pull/4189) surfaced
additional, complementary items worth landing now rather than later: PII
handling (logs and config paste fields routinely contain agent
transcripts, encrypted-secrets paths, and tokens), richer environment
capture for bugs (install method, adapters, DB mode, access context),
and a subsystem picker on feature/enhancement so the queue can be
filtered by area
> - Items 7–10 from #4189 (CONTRIBUTING.md change, AGENTS.md §11,
`require-issue-link.yml`, GitHub Discussions contact link) are
intentionally deferred — each is its own decision and belongs in a
separate, focused PR
> - The auto-label workflow that earlier revisions of this branch
included was dropped per reviewer feedback — labeling on issue open is a
separate concern from the template content and should land (if at all)
in its own PR
> - This pull request extends the merged templates with PII warnings + a
mandatory privacy checkbox on `bug_report.yml`, additional optional env
fields, a required subsystem dropdown on `feature_request.yml`, and two
net-new templates (`enhancement.yml`, `docs_issue.yml`)
> - The benefit is that newly filed issues land with enough environment
context and subsystem hint to triage on first read, and paste-field PII
is gated by an explicit reviewer-readable checkbox
## Linked Issues or Issue Description
Refs #7575
PAPA-521 (follow-up). This is the second half of the issue-templates
work — PR #7575 shipped the baseline, this PR adopts items 1, 2, 3, 4,
and 6 from PR #4189 on top. Item 5 (auto-label workflow) was dropped per
reviewer feedback.
## What Changed
- `bug_report.yml` — added PII privacy notice with redaction guidance
and anonymizer links; pre-submission preflight checkboxes; optional
fields for install method, adapter checkbox list (Claude Code / Codex /
Cursor / Droid / Hermes / external plugin / core), database mode (PGlite
/ external Postgres), and access context (board / agent); inline PII
warnings on the logs and config paste fields; mandatory privacy
checklist at submission time
- `feature_request.yml` — added a required subsystem dropdown (`server/`
· `ui/` · `packages/db` · `packages/shared` · `packages/adapters` ·
`packages/plugins` · cross-cutting · unsure)
- `enhancement.yml` — new template for improvements to existing
behaviour, with current-vs-proposed-behaviour fields and a required
breaking-changes field
- `docs_issue.yml` — new lightweight doc-issue form (type / location /
description / suggestion)
No workflow changes in this PR.
## Verification
- All 4 changed YAML files round-trip through `js-yaml` cleanly
- Diff vs. master: 4 files changed, +254/-3 (`.github/ISSUE_TEMPLATE/`
only)
- Post-merge reviewer steps:
1. Visit `github.com/paperclipai/paperclip/issues/new/choose` — expect
five template cards (bug, feature, enhancement, adapter, docs) and the
existing Discord/Roadmap contact links; no "Open a blank issue" option
2. Open each template in turn — required fields should block submission
when empty (in particular: privacy checklist on the bug form, subsystem
dropdown on feature/enhancement, breaking-changes field on enhancement)
## Risks
- Low risk overall. Files are `.github/ISSUE_TEMPLATE/` config only — no
runtime effect on the server, UI, CLI, or any package, and no workflows
touched
- If a YAML form has a schema quirk that `js-yaml` accepts but GitHub's
form renderer rejects, GitHub silently falls back to a blank issue for
that template. The reviewer render check above is the mitigation
> 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 4.7 — provider Anthropic, exact model ID `claude-opus-4-7`,
extended-thinking + tool use, running inside the Paperclip Claude Code
harness.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (refs #7575, #4189)
- [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 run tests locally and they pass (n/a —
`.github/ISSUE_TEMPLATE/` content only; `js-yaml` parse check run
instead)
- [ ] I have added or updated tests where applicable (n/a — repo has no
test coverage for `.github/` content)
- [ ] If this change affects the UI, I have included before/after
screenshots (n/a — only the GitHub.com "New issue" page is affected;
render check happens post-merge on github.com)
- [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 (will verify after open)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(greploop will run after open)
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Contributor onboarding leans on a small set of CI gates
("commitperclip") that read PR metadata and enforce the rules in
CONTRIBUTING.md
> - We recently landed two new contributor rules: every PR must either
link an existing issue or inline an issue-template-shaped description,
and the author must affirm they searched for duplicate PRs first
> - The existing `check-pr-linked-issue` gate only accepted
`Fixes/Closes/Resolves #N`, and there was no gate at all for the
dedup-search affirmation, so the new rules were unenforced
> - This pull request widens the linked-issue gate (accept `Refs #N`,
accept inline template-shaped descriptions) and adds a new dedup-search
gate wired into `run-quality-gates`
> - The benefit is that the rules we ask contributors to follow are now
mechanically enforced, lowering review noise without raising contributor
friction
## Linked Issues or Issue Description
Refs #4260 — the PR cited in the original rule discussion, whose body is
the canonical example of the inline-issue-description shape this gate
now accepts.
**Problem or motivation**
The repo recently adopted two contributor rules: (1) link the issue your
PR fixes or inline a description in the issue-template shape, and (2)
confirm you searched for duplicate PRs before opening one. Neither rule
was enforced — the existing linked-issue check only matched
`Fixes/Closes/Resolves #N`, and nothing looked for the dedup-search
affirmation. Reviewers had to remember and re-state the rules by hand on
every PR.
**Proposed solution**
Widen `check-pr-linked-issue.mjs` and add a new
`check-pr-dedup-search.mjs` so commitperclip enforces the rules already
documented in CONTRIBUTING.md and the PR template:
- Accept `Refs #N` as a valid link verb alongside `Fixes`, `Closes`,
`Resolves`.
- Accept a PR body with an inline issue-template-shaped description (≥3
fields matched against the bug, feature, or adapter templates) as a
valid alternative to a linked issue.
- New `check-pr-dedup-search.mjs` looks for a checked checkbox affirming
the author searched for similar/duplicate/prior PRs, wired into
`run-quality-gates.mjs` with the same skip/override semantics as the
other gates.
**Alternatives considered**
- A single regex over the whole PR body looking for issue-template field
names — rejected, too brittle and gave no useful error message when it
failed. The per-field counter lets us tell the author exactly how many
template fields we matched and which template they're closest to.
- Making the dedup-search check live inside `check-pr-linked-issue` —
rejected, the two rules are orthogonal and a separate gate gives a
cleaner error message and respects `[skip-quality-gates]` independently.
**Roadmap alignment**
This is contributor-workflow plumbing for rules that already landed on
`master`. It is not a roadmap feature and does not overlap with planned
core work.
## What Changed
- `check-pr-linked-issue.mjs`: accept `Refs #N`; add
`hasInlineIssueDescription` (per-template field counter, ≥3 fields) so a
fully inlined description satisfies the gate.
- `check-pr-dedup-search.mjs`: new gate, looks for a checked
dedup-search checkbox in the PR body, with clear failure guidance.
- `run-quality-gates.mjs`: wire the new gate in alongside the existing
checks.
- `CONTRIBUTING.md`: "Before You Start: Search First" callout pointing
at the PR-template checkbox the gate enforces.
- Tests: unit tests for `Refs #N`, the inline-description threshold
(bolded labels, plain labels, headings), and the dedup-search gate
across checked/unchecked/missing/skip-prefix paths.
## Verification
- `node --test .github/scripts/tests/check-pr-linked-issue.test.mjs` →
all pass
- `node --test .github/scripts/tests/check-pr-dedup-search.test.mjs` →
all pass
- Full repo test matrix: 116/116 pass locally
For the gate itself, this PR exercises both new code paths: the body
inlines a feature-template-shaped description with ≥3 matched fields,
and the dedup-search checkbox below is checked.
## Risks
Low risk. The change is contributor-CI plumbing — no runtime or
migration impact. The widened linked-issue gate is strictly more
permissive (it can only flip prior FAILs to PASS), and the new
dedup-search gate honors the existing `[skip-quality-gates]` prefix, so
an author can always bypass it the same way as the other gates if
needed.
## Model Used
Claude Opus 4.7 (`claude-opus-4-7`), extended-thinking enabled, tool use
for filesystem + shell.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Test infrastructure across server, ui, packages/* runs on Vitest
> - Dependabot opened a narrow bump (3.2.4 → 3.2.6), but the wider
workspace is on 3.2.4 and the major-version bridge to v4 needs a
coordinated change set across configs and tests
> - Staying on 3.x indefinitely leaves us behind on Vitest 4 (perf,
pool, and config improvements) and forces repeated patch-only dependabot
churn
> - This pull request upgrades Vitest to 4.1.8 across the workspace,
updates `server/vitest.config.ts` and `scripts/run-vitest-stable.mjs`
for the new API, and adjusts two UI tests for the new assertion
semantics
> - The benefit is a single, coherent Vitest 4 upgrade that supersedes
#7570 and gets us on the supported major line
## What Changed
- Bump `vitest` from `3.2.4` to `4.1.8` across root, `server`, `ui`, and
all `packages/*` (including plugin examples and sandbox providers)
- Update `server/vitest.config.ts` for Vitest 4 config surface
- Update `scripts/run-vitest-stable.mjs` to match the new runner
behavior
- Adjust `ui/src/components/CommentThread.test.tsx` and
`ui/src/components/MarkdownEditor.test.tsx` for Vitest 4 matcher/timing
semantics
- Refresh `pnpm-lock.yaml`
## Verification
- `pnpm install` resolves cleanly with the new lockfile
- `pnpm -w -r test` (server, ui, packages) runs under Vitest 4.1.8
## Risks
- Major-version Vitest bump: behavioral changes in pools, fake timers,
and matcher strictness can surface flake. Test config and the two UI
tests were updated to match v4 semantics; broader test runs should be
watched on CI before merge.
- Supersedes dependabot PR #7570 (3.2.4 → 3.2.6); that PR should be
closed.
## Model Used
- Claude (Anthropic) — `claude-opus-4-7`, extended thinking, tool use
enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [ ] I have run tests locally and they pass
- [ ] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Closes#7570
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The CI pipeline (`.github/workflows/pr.yml`) gates every PR with a
`policy` job and a fleet of downstream test/build/release jobs
> - `policy` blocks committing `pnpm-lock.yaml` from non-dependabot,
non-`chore/refresh-lockfile` branches and (when manifests change)
already regenerates the lockfile in memory — but throws it away
> - Every downstream job runs `pnpm install --frozen-lockfile`, so
manifest-only PRs from human/agent branches deadlock: they can't commit
the lockfile and can't pass `--frozen-lockfile` either, and
`refresh-lockfile.yml` only runs `on: push: master`
> - This pull request closes the loop by uploading the regenerated
lockfile as a workflow artifact (`pr-lockfile`) from `policy` and
restoring it in each downstream job before `pnpm install
--frozen-lockfile` runs
> - The benefit is that any manifest-only PR (e.g. a routine dep bump)
goes green end-to-end without a separate refresh-lockfile round-trip,
and unrelated PRs are unaffected because the download step is
best-effort (`continue-on-error: true`)
## What Changed
- `.github/workflows/pr.yml::policy`: add `id: regen_lockfile` to the
existing manifest-detection step, emit a `regenerated` output, and
conditionally `actions/upload-artifact@v4` `pnpm-lock.yaml` as
`pr-lockfile` (retention 1 day) when a manifest changed
- `.github/workflows/pr.yml` (every downstream job —
`typecheck_release_registry`, `general_tests` [3 matrix], `build`,
`verify_serialized_server` [4 matrix], `canary_dry_run`, `e2e`): add a
`Restore regenerated PR lockfile (if policy uploaded one)` step using
`actions/download-artifact@v4` with `continue-on-error: true`, placed
before the existing `pnpm install --frozen-lockfile`
- `.github/workflows/pr.yml::canary_dry_run`: guard the pre-existing
`git checkout -- pnpm-lock.yaml` so it no longer clobbers an
artifact-restored lockfile when one is present
## Verification
- `node -e "require('js-yaml').load(...)"` parses the file; all 6
downstream jobs have the restore step, `policy` has the upload step
- This branch is itself a manifest-untouched PR, so `policy` will not
upload an artifact and every downstream job will skip the download
silently — the existing `--frozen-lockfile` path is exercised end-to-end
with no behavioral change for non-manifest PRs
- Once merged, the next manifest-touching PR (PAPA-530's #7581) will
exercise the artifact path and is expected to go green
## Risks
- **Behavior on non-manifest PRs:** the download step uses
`continue-on-error: true` so a missing artifact is silently ignored; net
behavior on PRs that don't change manifests is identical to today
- **Concurrency / cross-PR artifact bleed:** artifacts are scoped to a
single workflow run, so two concurrent PRs can't see each other's
`pr-lockfile`
- **`canary_dry_run`:** the prior unconditional `git checkout --
pnpm-lock.yaml` was a defensive no-op when `--frozen-lockfile` is
honored; the new conditional preserves that for non-manifest PRs and
additionally preserves the artifact-restored lockfile for manifest PRs
## Model Used
- Claude (Anthropic) — `claude-opus-4-7`, extended thinking, tool use
enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [ ] I have run tests locally and they pass
- [ ] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - CONTRIBUTING.md and the PR template are the entry point for new
contributors and set quality expectations for every PR
> - Today they don't tell contributors to search for duplicate/related
work first, don't require a linked GitHub issue, and don't spell out the
merge bar (gates green + Greptile 5/5)
> - Letting duplicate or under-reviewed PRs land creates extra work for
reviewers and erodes quality
> - This PR updates CONTRIBUTING.md and the PR template to require a
search-first workflow, a linked issue, a clear merge bar, and explicit
encouragement to help other contributors land their PRs
> - The benefit is fewer duplicate PRs, easier triage, higher-quality
merges, and a kinder collaboration culture
## What Changed
- `CONTRIBUTING.md`: added a "Before You Start: Search First" section
directing contributors to search GitHub for duplicate PRs and related
issues, with a note that a fresh PR is okay if an old one is dead and
would be painful to merge.
- `CONTRIBUTING.md`: require every PR to link a GitHub issue in the
body, plus references to related/duplicate PRs and issues.
- `CONTRIBUTING.md`: added a section encouraging contributors to help
finish other people's PRs, thank collaborators, and be kind.
- `CONTRIBUTING.md`: tightened the merge bar — all Paperclip CI gates
must be green and Greptile must be 5/5 with no open P2s,
recommendations, or follow-ups.
- `CONTRIBUTING.md` + `.github/PULL_REQUEST_TEMPLATE.md`: updated the
thinking-path opener to "Paperclip is the open source app people use to
manage AI agents for work" (consistent across examples and template).
- `.github/PULL_REQUEST_TEMPLATE.md`: checklist gains items for
duplicate/related search, linked issue, gates green, and the Greptile
5/5 bar.
## Verification
- Docs-only change. Reviewed both files render correctly on GitHub.
- No code or tests affected; no CI behavior change beyond the new
checklist items contributors are asked to tick.
## Risks
- Low risk. Documentation and template only — no runtime impact.
Stricter contributor expectations may slow some PRs in the short term.
## Model Used
- Claude, `claude-opus-4-7` (extended thinking, tool use).
## Related
- Issue: #4188 — "Add issue templates and enforce issue-link check on
PRs" (the linked-issue requirement here is a step toward what #4188 asks
for, done in CONTRIBUTING/template rather than CI).
- Prior PR: #4260 — "docs: expand CONTRIBUTING.md and add issue
templates" (open; different scope — issue templates + broader expansion.
This PR is narrower and focused on the search-first + merge-bar
updates.)
## 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 linked the GitHub issue this PR addresses (and any
related/duplicate issues)
- [x] I have run tests locally and they pass (docs-only change; no tests
to run)
- [x] I have added or updated tests where applicable (N/A — docs)
- [x] If this change affects the UI, I have included before/after
screenshots (N/A — docs)
- [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 (will verify after push)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Most of the inbound feedback loop lives in GitHub Issues: bugs,
feature ideas, and new-adapter requests
> - Today the "New issue" page is a blank textarea, which means reports
arrive without a Paperclip version, without repro steps, and without a
clear separation between adapter requests and feature requests
> - That makes triage slow and routes a lot of low-information issues
back to the requester for follow-up questions before any work can start
> - This pull request adds `.github/ISSUE_TEMPLATE/` with three YAML
issue forms — bug, feature, adapter — plus a `config.yml` that disables
blank issues and points contributors at Discord `#dev` and `ROADMAP.md`
> - The benefit is that new issues come in with the fields a maintainer
needs to triage on the first read, and feature requests are nudged
toward the roadmap/Discord coordination path before they turn into
uncoordinated PRs
## What Changed
- Added `.github/ISSUE_TEMPLATE/config.yml` — sets
`blank_issues_enabled: false` and adds contact links for Discord `#dev`
and the roadmap.
- Added `.github/ISSUE_TEMPLATE/bug_report.yml` — required fields: what
happened, expected behavior, repro steps, Paperclip version/commit, and
a deployment-mode dropdown. Optional: Node version, OS, logs, additional
context.
- Added `.github/ISSUE_TEMPLATE/feature_request.yml` — required fields:
problem/motivation, proposed solution. Optional: alternatives,
ROADMAP.md alignment, additional context.
- Added `.github/ISSUE_TEMPLATE/adapter_request.yml` — required fields:
agent/provider name, why useful, how invoked. Optional:
willing-to-implement checkboxes, additional context.
Field structure and labels are lifted from @amitdhanda48's earlier
markdown-template proposal (#4260); this PR ports those four files to
GitHub's YAML issue-form schema so required fields are enforced at
submission time. The bundled `CONTRIBUTING.md` rewrite from that
proposal is intentionally out of scope and left for a separate decision.
## Verification
- `node -e
"require('js-yaml').load(require('fs').readFileSync(f,'utf8'))"` against
each of the four YAML files — all parse.
- Diff against `master` is `.github/ISSUE_TEMPLATE/` only: `git diff
--stat master...HEAD` shows 4 files, +162 lines, 0 deletions.
- Reviewer step: after merge, visit the repo's "New issue" page on
GitHub and confirm the three forms render in the chooser, blank issues
are disabled, and the Discord/roadmap contact links appear.
## Risks
- Low risk. Files are GitHub-config only — they have no runtime effect
on the server, UI, CLI, or any package. The only behavioral change is on
github.com's "New issue" page.
- If a YAML form has a schema mistake that `js-yaml` accepts but
GitHub's form renderer rejects, GitHub silently falls back to a blank
issue for that template. The mitigation is the post-merge render check
above.
## Model Used
- Claude (Anthropic) — `claude-opus-4-7`, extended-thinking + tool use,
running inside the Paperclip Claude Code harness.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass (n/a — no code paths
exercised by the test suite; YAML parse-check run instead)
- [ ] I have added or updated tests where applicable (n/a — repo has no
test coverage for `.github/` config)
- [ ] If this change affects the UI, I have included before/after
screenshots (n/a — only the GitHub.com "New issue" page is affected;
render check happens post-merge on github.com)
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies, so its
automation and CI surfaces need to stay reliable enough for agent-driven
maintenance work.
> - Dependabot configuration is part of that maintenance path because it
determines how dependency updates reach the repo and CI.
> - PR #7483 changed `.github/dependabot.yml` to group all npm
patch/minor updates by dependency type.
> - That immediately produced a 26-package grouped PR that was difficult
to merge and contradicted the board's decision to return to
one-PR-per-bump.
> - This pull request reverts only the grouping behavior while
preserving the rest of the Dependabot schedule, labels, limits, and
ignore rules.
> - The benefit is a narrower, more reviewable Dependabot flow that
restores the previous operational behavior without changing unrelated CI
settings.
## What Changed
- Removed the npm `groups:` block from `.github/dependabot.yml`.
- Left the existing weekly schedule, labels, open PR limit, and
major-version ignore rules unchanged.
- Isolated the revert onto a clean branch containing only commit
`446453516c` so this PR does not include unrelated local CI work.
## Verification
- `ruby -e 'require "yaml"; YAML.load_file(".github/dependabot.yml")'`
- `git diff --check origin/master..PAPA-522-revert-dependabot-grouping`
- Inspect the PR diff and confirm only `.github/dependabot.yml` changed.
## Risks
- Low risk. This only removes the grouping stanza and restores previous
Dependabot behavior.
- Operationally, this returns to a higher number of smaller Dependabot
PRs, which is the intended tradeoff for easier mergeability and triage.
> 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 via the local `codex_local` Paperclip adapter,
GPT-5-based coding agent backend with tool use and shell execution
enabled. The exact backend model ID/context window is not exposed by
this adapter 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
- [ ] I have run tests locally and they pass
- [ ] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge