Commit Graph
5 Commits
Author SHA1 Message Date
Nicky Leach e2cfdc7e0c feat(plugins): expose GitHub credential broker to task providers (#18178) 2026-10-29 10:52:44 -07:00
DottaandPaperclip 7d8d1fbc1d fix(adapter-utils): provide a default author for local commits (#18094)
<!-- entire-trail-link-start -->
https://entire.com/gh/paperclipai/-/trails/209/changes/paperclip/209
<!-- entire-trail-link-end -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents use managed launchers for Git and GitHub commands.
> - These launchers exclude host credentials and host Git identity.
> - A checkout without an author cannot save a local commit when managed
GitHub identity is absent.
> - A local author default belongs in the launcher that owns this
isolation.
> - This pull request supplies a private Paperclip default and preserves
explicit and managed identity selection.
> - Agents can save local work without manual author setup or extra
GitHub access.

## Linked Issues or Issue Description

**What happened?**

An ordinary `git commit` fails with `Author identity unknown` in a fresh
checkout without author settings or a usable managed GitHub identity. An
agent must set its own author to continue.

**Expected behavior**

The managed launcher supplies a default local author. Repository and
command settings override that default. A current managed identity
retains its existing precedence.

**Steps to reproduce**

Run the managed launcher in a fresh Git repository without author
settings or broker credentials. Add a file, then run `git commit -m
work`.

**Paperclip version or commit**

Reproduced at `063cfd43813cabf18417605fb9eefb9aacf95c8d` on Linux with
Node 25.9.0 and pnpm 9.15.4.

**Deployment mode**

Managed sandbox execution. The shared launcher also serves local and SSH
execution that selects managed access.

Related: #16662 changes precedence for a connected GitHub account. #8945
assigns identity to managed worktrees. This repair supplies the missing
anonymous launcher default and keeps the current managed precedence. It
does not require either change.

### Hypothesis and thesis for a fix

**Hypothesis:** The launcher removes inherited author variables and host
Git config. Without a broker identity or explicit repository settings,
it has no author. Four real-Git cases reproduce the reported error on
the base revision.

**Thesis for a fix:** Put `Paperclip <noreply@paperclip.ing>` in a
private Git config for each operation. Git treats it as global config,
so repository and command settings can override it. The existing broker
environment still takes precedence. Do not store a person's identity in
shared checkout config, where it could remain after steering or
revocation. The default grants no GitHub access.

## What Changed

- Write the default author to the existing private operation directory
with mode `0600`. Remove it when the operation exits.
- Point managed Git at this file while keeping host global/system
config, credentials and SSH identity excluded.
- Add real-Git regressions for missing, absent, unavailable and rejected
access. Preserve explicit overrides, identity changes and revocation
controls.
- Verify two shell commits through normal launcher staging and a commit
after anonymous heartbeat cleanup. Check saved files, commit metadata
and temporary config cleanup.
- Update identity documentation and the two run map recipes.

## Verification

- Red: the four new local-commit cases fail on the base launcher with
`Author identity unknown`.
- Green: all 80 focused tests pass in one run across
`github-launcher.test.ts`, `github-launcher-environment.test.ts`,
`heartbeat-github-launchers.test.ts` and `native-github-access.test.ts`
(23.31 seconds).
- A controlled filesystem failure first made the candidate discard valid
managed credentials. After the correction, the same real launcher
process retains them. Default-author file failure does not change
operation-directory readiness.
- Review found a second failure: a partial write can leave invalid Git
config. The real-process test failed before the repair with `fatal: bad
config line` from `git status`. The launcher now selects the default
file only after the write succeeds. Both write-failure cases retain
managed credentials, allow status, require identity when no default is
available, and save a commit with explicit identity. The test reads the
stored commit metadata with real Git.
- Real shells, generated launchers and Git create and inspect saved
commits. Sandbox transport and broker replies use controlled fixtures.
No live cloud rollout or GitHub write acceptance is claimed.
- The isolated test process excludes inherited task launcher paths, Git
config and broker authentication variables. An initial run exposed those
test-environment inputs; the isolated replay passed. The heartbeat
assertion now checks the private default file and its cleanup instead of
expecting `/dev/null`.
- Adapter-utils typecheck and build pass. Map validation and `git diff
--check` pass.
- Conflict repair on 2026-10-29: merged master
`fa5e70faf47ef2ca20bf2daf95b7503c94469b89` into the PR branch. Resolved
19 conflict paths by retaining master for unrelated files. All seven Git
identity files remain byte-for-byte equal to the reviewed fix. The final
diff is seven files, 145 additions and 15 deletions.
- At head `c501e3f28369d404c91082b89188dc150c9362a9`, all 80 focused
tests pass again (37.53 seconds). Adapter-utils typecheck and build
pass. Map validation and migration checks pass. The PR diff passes `git
diff --check`.
- Full workspace typecheck and build were attempted again. Both stop
because this sandbox has no Rust `cargo`. The duplicate full local
Vitest run was stopped after about five minutes without a final result
while full CI was running. The earlier full local attempt was stopped
without a final result. No local full-suite pass is claimed.
- GitHub now reports the PR as mergeable. All current-head checks have
completed. [Full CI run
40878973679](https://github.com/paperclipai/paperclip/actions/runs/40878973679)
passes, including typecheck, build, Rust, test shards and all eight E2E
shards. The exact-head check inventory has 54 successes, one neutral
Paperclip Review and two Storybook skips. Snyk passes. Greptile and
Paperclip each reviewed `c501e3f28369d404c91082b89188dc150c9362a9` at
5/5 with no actionable findings. All review threads are resolved. GitHub
reports the PR as clean and mergeable.
- The prior CI run had one failed E2E test: `a lost creation response
requires reload and cannot blindly replay commands`. Its YAML parser
received page text that starts with `Skip to Main Content`. The new run
passes E2E shard 7/8 and the full E2E gate. The prior failure remains
part of this record. A passing run does not establish the earlier
failure's cause. This change does not alter that test or the agent
configuration UI.

## Risks

- Commits without explicit or managed identity now use the generic
Paperclip author. Operators can still set repository or command
identity.
- `git config --global` writes to temporary operation config and does
not persist. The documentation states this behavior.
- If the private config directory cannot be prepared, commits still need
explicit identity. Existing redacted diagnostics and credential
isolation remain.
- One small synchronous file write is added to a directory the launcher
already creates. No new subprocess, network request, persistent identity
or authority cache is added.

> 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, based on GPT-6, with reasoning, shell execution and tool
use. The runtime does not expose the exact model ID or context window
size to this 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-29 08:56:12 -05:00
Nicky LeachandPaperclip 00331e0254 Connect native Runner admission to plugin-owned workspaces (#16978)
## Thinking Path

> - Paperclip manages work for AI agents.
> - A plugin can own an execution environment and its files.
> - #17029 supplies the task driver and bounded setup verification.
> - Heartbeat still prepares workspace files on Core and expects a shell
transport.
> - This PR gives the selected plugin ownership of workspace preparation
and native task execution.
> - Core keeps admission, tool authority, and recovery checks.

## Linked Issues or Issue Description

**Subsystem affected**

Heartbeat workspace admission and native Runner execution.

**Problem or motivation**

A task-only plugin cannot run through Core filesystem staging or shell
execution. Native admission and recovery must use the same lease and
workspace ownership.

**Proposed solution**

Use the task driver from #17029. Resolve workspace and project
identities without Core file operations. Connect native dispatch and
restart recovery to that driver.

**Alternatives considered**

Keep workspace admission and dispatch in one change. A workspace change
alone would leave dispatch on the previous transport. An execution
change alone would leave Core staging files for the wrong host.

## What Changed

- Enable plugin environment selection for native workspace sessions.
- Resolve workspace metadata and authorized project references without
Core checkout, copy, or provision operations.
- Preserve agent-default and primary workspace policies. Use the pinned
workspace provider for plugin-backed project storage. Reject unsupported
Core service commands and cross-owner reuse.
- Connect native execution to the prepared task driver. Keep assigned
MCP authority on Core.
- Save exact checkpoint identity, transfer selected account refreshes,
and require termination proof before replacement or cleanup.
- Complete managed plugin workspaces through their storage provider. Use
the same check after a restart. Keep branch validation and leave
task-owned files on the plugin.
- Include default instruction content in runtime synchronization when no
instruction directory exists.
- Defer new-agent Copilot verification until the saved-agent lifecycle
can acquire the agent-owned computer.

## Verification

- Rebased onto `origin/master` at `898144f4b3`. Master advanced once
during verification. Both manual conflicts joined import blocks: one in
`native-session-executor.ts`, and one in
`heartbeat/runtime-execution.ts`. The seven patches retain their logic.
The first rebased head passed repository typecheck, build, all 862
focused execution/finalizer cases, and all CI gates. The latest head
`93e5a72892` passes server typecheck and all 880 focused execution,
finalization, and execution-control notification tests. All 54 CI/review
checks pass, with two expected skips. Greptile ran automatically and
scored this head 5/5 with no unresolved findings. No new review or Apex
request was made for these import-only resolutions.
- The successful CI workflow initially left three stale job-check
reports. Targeted policy and summary reruns cleared the reports. No
source change or test fix was needed.
- Repository typecheck and build pass: `pnpm -r typecheck` and `pnpm
build`.
- Focused native execution and restart tests pass: 795 cases. The
account and harness suites pass: 159 cases. Workspace admission passes:
48 cases. The SDK task lifecycle, workspace API, runtime-hook, and
instruction checks also pass.
- The heartbeat integration case uses the merged workspace provider
contract and reaches native dispatch with no project files on Core. The
new-agent case confirms that creation leaves the agent in `preparing`
until lifecycle verification.
- Map validation and the Copilot source-identity check pass. No Runner
implementation or source-identity bump is in this PR.
- The first rebased workspace run found two stale path assertions and a
recovery guard that did not recognize task-owned targets. These are
fixed. The storage integration fixture also needed its persisted source
binding; the production authorization check stays in place.
- Two prior review findings required rejection of Core service startup
and cross-owner workspace reuse. The existing admission fixtures now
check both failures. The installed-account authentication finding was
fixed in merged #17029.
- Tests reuse the current fixtures. No new provider test matrix or live
Runner process is added for these fixes.
- The prior head `2db273ec12` passed CI on its first attempt and reached
standard Greptile 5/5. Apex scored 4/5 and found local-folder checks
during plugin completion and recovery.
- The completion fix reproduces both failures. It passes all 110 cases
in the runtime execution, workspace finalizer, and workspace provider
integration suites. Server typecheck and map checks pass. CI passed on
`62d8335b4e` on its first attempt, and standard Greptile scored 5/5. The
next Apex review scored 4/5. It found that a failed plugin inspection
could abort restart recovery.
- The restart fix records a failed workspace receipt and uses the
current run retry policy. A regression first reproduced the aborted
pass. It now checks retry state, retained results, and completion of
another workspace in the same pass. All 68 finalizer cases and server
typecheck pass. CI passed on `7d4016323b` on its first attempt. Standard
Greptile and Apex both scored 5/5. Apex left one P2 about lost failure
codes.
- The diagnostic fix keeps the known branch and owner mismatch codes in
receipts and run recovery state. It gives repair guidance for those
codes and keeps unknown plugin errors generic. All 96 affected finalizer
cases and server typecheck pass. Before the rebase, required CI passed
on `c2fa61c6c8` on its first attempt. Standard Greptile and Apex both
scored 5/5 on that head, with no unresolved findings. The new head
retains these reviewed patches.
- The earlier Superagent scan on `c2fa61c6c8` was inconclusive after a
pinned merge-base checkout failed or timed out. The current scan on
`93e5a72892` passes.
- The exact rebased head passes all 48 workspace cases and server
typecheck. The first local test launch could not bind PostgreSQL inside
the sandbox. The authorized local-server run passed.
- The prior head reached standard Greptile 5/5. Its first CI run hit an
unchanged Runner cleanup timeout and an external build-runner shutdown.
Both jobs passed on the single retry. The timeout cause is not
established.
- A local regression check found that a retained Core workspace could
also enter a plugin task without file transfer. Core now rejects this
move before provider work. The existing ledger test covers both
directions and confirms that the source files stay intact.
- The first active standard review scored 4/5. It found that a fresh
plugin retry could enter Core Git preparation. This mode now uses the
existing unsupported-storage error before workspace resolution or
acquisition. Both unsupported-mode cases pass, and server typecheck
passes.
- No live Cloud or provider validation was run.

## Risks

- The prerequisite #17029 is merged. This PR targets master.
- Native plugin tasks support per-turn workspace sessions. Core service
commands and fresh retries that import retained files remain
unsupported. A workspace storage plugin can use the modes and strategies
it declares.
- Core-managed credentials support Codex and direct Claude accounts.
Other managed credential routes fail closed. Other harnesses can use an
installed account.
- A provider must preserve its prepared configuration and report process
termination correctly. Uncertain termination keeps the task pending
cleanup.
- Cloud deployment and live provider validation remain separate.

## Model Used

OpenAI Codex, GPT-6. The exact model ID and context window are not
exposed in this session. Tool use includes code editing, local tests,
and the GitHub CLI.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-21 08:22:48 -07:00
DottaandPaperclip 3d78e3a4ec fix(runner): keep warm sessions alive with managed GitHub access (#13815)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - The native Runner keeps a live provider process between task turns.
> - Managed GitHub access used a token tied to one run.
> - A new run forced Paperclip to replace that process to replace its
token.
> - This PR gives the session a stable credential transport and binds
each operation to the active run.
> - The agent can keep its process while Paperclip checks current
identity and grants.

## Linked Issues or Issue Description

Follow-up to #13738. Related credential-rotation work: #11770 and #8208
use process replacement for other adapter credentials; this change
applies to managed GitHub access in the native Runner.

**What happened?**

A configured GitHub connection forced a warm native provider process to
close at each new run. The saved conversation survived, but the live
process did not.

**Expected behavior**

Keep the warm provider process. Resolve GitHub access for the current
run when each command starts. Deny access while idle or after the run
ends.

**Steps to reproduce**

1. Configure managed GitHub access for a native Runner agent with a warm
session.
2. Complete a turn, then send another message to the same task.
3. Observe the provider process close with the reason `warm native
session configuration changed`.

**Paperclip version or commit**

Reproduced on master `8326e33ad`. Rebased onto `e3d8fb087` before
submission.

**Deployment mode**

Local and remote native execution, including the sandbox callback
bridge.

## What Changed

- Move configured native GitHub transport and launcher ownership from
the run to the provider session.
- Bind the broker only after the executor acquires session ownership.
Clear that binding when the run exits.
- Keep the shared live-run, identity, grant, and trust-policy checks for
each credential request.
- Reject wrong scopes, idle requests, and credential responses that
arrive after their run binding changes.
- Retire transport and launcher files with the provider session. Keep
anonymous commands available if bridge startup fails.
- Add red/green executor tests, real subprocess and callback-bridge
tests, and database checks. Update the runtime documentation.

## Verification

- Before the fix, both new local and remote warm-session reuse tests
failed.
- After the fix, 435 targeted tests passed across the executor, broker,
launcher, token, and database suites.
- A real long-lived test process kept the same PID and original
environment across two runs, including through the production callback
bridge on local test processes.
- Server typecheck and TypeScript compilation passed.
- Full workspace typecheck and build passed. Server typecheck passed
again after the review fix.
- The fallback-logging regression failed before the fix; all 9 broker
tests pass afterward.
- The exact chat sidebar browser scenario passed locally. The initial CI
timeout showed failed Vite module downloads; all eight browser shards
pass on the latest commit.
- All 53 latest-head checks passed, including the full CI test matrix
and security checks (two unrelated conditional checks skipped).
- The duplicate full local test run was stopped after CI passed; it is
not claimed as a completed local pass. Targeted local tests, workspace
typecheck/build, and the browser scenario passed.
- Greptile reviewed the latest commit at 5/5 with no unresolved
findings.
- No fresh paid provider or Daytona campaign has run for this change.

## Risks

- The broker now lives as long as the provider session. Tests cover idle
denial, late cleanup, late responses, shutdown, and failed startup.
- Its in-memory authority does not survive a controller restart.
Existing checkpoint and process-recovery rules still apply.
- Raw GitHub credentials remain confined to individual command
processes. The session transport token cannot select a different task,
agent, company, or run.
- No database migration or public API change.

## Model Used

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

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-09-22 13:07:13 -05:00
DottaandPaperclip 0f5fafe16b fix(runner): preserve task replies and warm process continuity (#13738)
## Thinking Path

> - Paperclip lets people manage AI agents and their work.
> - The Runner connects provider sessions to task state, replies, and
delegated work.
> - Full-stack tests found lost final replies, rejected helper calls
that stopped the parent, and unnecessary process restarts.
> - A completed child could also receive a new assignment wake that the
scheduler then cancelled.
> - This change fixes those boundaries and gives agents clearer teammate
instructions.
> - The tests retain strict completion and process-continuity
requirements.

## Linked Issues or Issue Description

**What happened?**

A generated attachment comment could suppress an agent's final reply. A
known Codex helper could stop its parent when it requested a Paperclip
tool. Native Daytona processes restarted between turns because Paperclip
minted an unused GitHub broker token. Reassigning a completed child
queued a run that immediately cancelled. Revision instructions also
allowed agents to do work assigned to a named teammate themselves.

**Expected behavior**

Keep the final reply. Reject helper tool requests without borrowing
parent authority or stopping the parent. Keep an unconfigured sandbox
process alive between turns. Treat assignment-only changes to completed
tasks as metadata changes. Preserve explicit teammate assignments during
revisions.

**Steps to reproduce**

Run the retained Runner E2E cases for file handoff, teammate reuse,
Daytona warm continuity, and Legacy Claude interview/plan acceptance.
The focused regression tests reproduce the reply, helper,
process-lifetime, and assignment-wake defects without provider calls.

**Paperclip version or commit**

The live lifetime and completion campaign used `db3857807`. This PR
replays the changes on master `c65fc9e3c`. See Verification for the
limits of that evidence.

**Deployment mode**

Isolated local instances and native Runner sessions in Daytona
sandboxes.

Related work: #13546 handles a different queued-run issue after an
issue-lock compare-and-set failure. This PR prevents the unnecessary
assignment wake earlier. #13410 covers retained user services; this PR
covers the provider process. No duplicate fix was found.

## What Changed

- Exclude generated deliverable-binding comments from final-reply
deduplication. Preserve the attachment and explicit user-facing replies.
- Reject Paperclip tool and input requests from known Codex helper
threads without terminating the parent. Keep unknown-thread rejection
intact.
- Explain how to hire or reuse a persistent teammate and preserve named
delegation on revisions. Update generated protocol fixtures.
- Use stable, token-free GitHub wrappers for unconfigured native
sandboxes. Preserve credential isolation, configured-account rotation,
and cleanup after partial staging failures.
- Do not queue assignment-only wakes for done or cancelled tasks. Keep
explicit reopening behavior.
- Make warm-continuity fixtures create real review cards. Read the
persisted final response selected by production presentation logic.
Missing selected evidence still fails.

## Verification

- Before rebase: 560 focused route, native-executor, and launcher tests
passed. The new regressions were reproduced before their fixes.
- Live E2E: Legacy Claude interview/plan acceptance passed 3/3
repetitions. Daytona warm continuity passed 2/3 full repetitions. Each
successful run retained one process and provider session for all three
turns.
- The remaining Daytona repetition stopped after a same-URL browser
reload left the page blank. Both completed turns retained the same
process. Its failed verdict remains unchanged; this PR does not claim
the blank-page cause is fixed.
- Reports:
https://pages.paperclip.ing/runner-e2e-lifetime-race-20260920/investigation.html
and
https://pages.paperclip.ing/runner-e2e-behavior-followups-20260919-results/investigation.html
- Post-rebase `pnpm build` and `pnpm -r typecheck` passed. All 414
Runner E2E harness unit tests and its typecheck passed. Codex protocol
tests: 88 passed, 2 ignored.
- Latest-head CI: 55 successful checks and 2 intentional skips.
Greptile: 5/5 with no review threads. The unchanged workspace exposure
tests hit a fixed-port collision on the first CI attempt; their local
suite passed (25 tests, 3 platform skips), and the CI shard passed on
one retry.
- The duplicate local `pnpm test:run` was stopped after the full hosted
general and serialized test shards passed. It did not finish locally and
is not counted as a local full-suite pass.

## Risks

Configured GitHub accounts retain run-scoped credential rotation and can
still restart warm processes. That limitation requires a separate
design. Known provider helpers cannot use Paperclip coordination tools
directly; they must return findings to the parent. The delegation prompt
is an instruction, not an enforced guarantee; Codex Mini hiring/reuse
failures remain open. No schema or workflow changes are included.

## Model Used

OpenAI GPT-6 through Codex, with repository tools, code execution, and
parallel coding agents. The exact deployment suffix and context-window
size are not exposed in this session.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-09-21 08:56:35 -05:00