## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip runs agents through adapters, including built-in local and
gateway-style adapters.
> - Hermes gateway users need to connect Paperclip to an already-running
Hermes API server.
> - The gateway setup flow was missing clear non-local adapter
configuration fields and accepted fewer URL shapes than operators
naturally paste from Hermes.
> - It also surfaced sparse diagnostics when the gateway was unreachable
or when Paperclip generated onboarding prompts for gateway agents.
> - This pull request tightens Hermes gateway configuration, URL
normalization, diagnostics, and onboarding defaults.
> - The benefit is that Hermes gateway setup is easier to complete and
easier to debug without affecting unrelated adapters.
## Linked Issues or Issue Description
No public issue found for this exact follow-up. Related prior/in-flight
Hermes work:
- Refs #2363
- Refs #4359
- Refs #6473
Problem statement:
- **Type:** Adapter follow-up / setup reliability
- **Adapter:** `hermes_gateway`
- **Motivation:** Operators configure `hermes_gateway` against a running
Hermes API server, but the UI and onboarding flow did not expose enough
gateway-specific configuration or diagnostics.
- **Expected behavior:** Paperclip should render the gateway fields,
normalize common Hermes dashboard/API URL inputs, preserve sensible
gateway onboarding defaults, and report reachability failures with
actionable detail.
- **Deployment mode:** Built-in adapter package in the Paperclip
monorepo.
## What Changed
- Added UI config fields for non-local Hermes gateway settings,
including tests for rendering and field behavior.
- Accepted Hermes dashboard URLs by normalizing them to gateway API URLs
for execution.
- Improved gateway reachability and run URL diagnostics.
- Updated Hermes gateway onboarding text/default behavior so join
prompts preserve gateway configuration.
- Added focused server, UI, and adapter tests for the gateway
configuration and onboarding paths.
## Verification
- `pnpm install --frozen-lockfile --prefer-offline`
- `pnpm --filter @paperclipai/hermes-paperclip-adapter exec vitest run
src/gateway/server/execute.test.ts` — 20 passed
- `pnpm exec vitest run
server/src/__tests__/invite-accept-gateway-defaults.test.ts
server/src/__tests__/invite-onboarding-text.test.ts
ui/src/adapters/hermes-gateway/config-fields.test.tsx
ui/src/components/AgentConfigForm.render.test.tsx
ui/src/lib/agent-onboarding-prompt.test.ts` — 23 passed across 5 files
- Confirmed the PR diff excludes `pnpm-lock.yaml` and
`.github/workflows`.
## Risks
Low to moderate risk. The changes are scoped to Hermes gateway
configuration/onboarding and generic non-local adapter field rendering.
The main risk is rejecting an unusual Hermes URL shape that should be
accepted; the normalization tests cover dashboard and API URL variants
added here.
> 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-based coding agent in a tool-enabled local CLI
environment, with shell/GitHub/Paperclip API access. Exact runtime model
identifier was 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>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The dev/server UI exposes a `/api/health` endpoint and a lower-left
account drawer, but nothing surfaces *which* build the running instance
is on or when it last restarted
> - When iterating on a local dev instance it is hard to tell whether
the server you're looking at has actually restarted onto your latest
commit, or how stale the running process is
> - Developers need a lightweight, opt-in way to confirm the running
instance's identity without digging through logs or shelling into the
host
> - This pull request adds an experimental "Server Info Debug View"
setting that surfaces the running instance's last-restart time and
current commit as read-only rows in the account drawer
> - The benefit is a quick, in-UI sanity check of what the live server
is actually running, behind an experimental flag so it ships zero cost
to users who don't opt in
## Linked Issues or Issue Description
No public GitHub issue exists. Describing the underlying request inline
following the feature request template:
**Problem or motivation:**
When working against a local Paperclip dev instance there is no in-UI
way to confirm what the running server is — its current commit or when
it last restarted. You have to check logs or the host shell to know
whether the process picked up your latest build.
**Proposed solution:**
An opt-in experimental setting ("Server Info Debug View") that, once
enabled, renders a small read-only "Server" section at the bottom of the
lower-left account drawer showing **Last restarted** (the server process
start time) and **Running commit** (the current git HEAD short SHA +
subject).
**Alternatives considered:**
A separate top-right pill/overlay (like the work-life-balance plugin).
The account drawer was chosen to reuse existing menu-row styling and
avoid adding new always-present chrome.
**Roadmap alignment:**
Small, self-contained developer-experience aid gated behind an
experimental flag; does not overlap planned core roadmap work.
## What Changed
- Added `server/src/server-info.ts`: captures a `serverInfo` snapshot
once at boot — process start time and current git commit (SHA +
subject). Git is read via `execFileSync` with SHA validation and a
timeout.
- `/api/health` exposes the `serverInfo` snapshot, but only on
full-details health responses (board/agent in authenticated mode, or
local-trusted dev).
- Gated the UI surface behind a new `enableServerInfoDebugView`
experimental setting, wired through the shared instance type, validator,
settings normalizer, and OpenAPI schema.
- UI: added `SidebarServerInfo` rendering the read-only rows in the
account drawer (`BreadcrumbBar` / `SidebarAccountMenu`), plus the
experimental settings toggle and a typed `health` API client.
- Moved `ServerGitInfo` / `ServerInfoSnapshot` into
`@paperclipai/shared` so the server and UI share one definition instead
of duplicating it.
- Added unit tests for the server-info snapshot, health route exposure,
validator/normalizer, settings routes, the experimental settings page,
and the sidebar component.
## Verification
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/health.test.ts src/__tests__/server-info.test.ts
src/__tests__/instance-settings-service.test.ts
src/__tests__/instance-settings-routes.test.ts` — 32 passed
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/SidebarServerInfo.test.tsx
src/pages/InstanceExperimentalSettings.test.tsx` — 9 passed
- `tsc --noEmit` on both `@paperclipai/server` and `@paperclipai/ui` —
clean
- Manual: enable **Settings → Experimental → Server Info Debug View**,
refresh the UI, open the lower-left account drawer — a "Server" section
shows Last restarted and Running commit.
## Risks
- Low risk. The UI surface is fully opt-in via an experimental flag and
defaults off.
- The `serverInfo` field on `/api/health` is access-controlled to
full-details responses only (board/agent in authenticated mode, or
local-trusted dev) — never anonymous authenticated callers — so the git
SHA is not broadly exposed.
- The only new server work is a one-time git read at boot, guarded with
SHA validation and a timeout; failures degrade gracefully (the git block
reports `available: false` rather than throwing).
## Model Used
Claude — `claude-opus-4` (Anthropic), extended thinking with 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
instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent adapters are the boundary between the control plane and the
runtimes that actually do work.
> - Hermes support needs to be available as first-class local and
gateway adapters while still preserving the adapter-manager override
path for external packages.
> - The adapter work touches runtime execution, UI adapter metadata,
onboarding prompts, scoped credentials, release packaging, and smoke
coverage, so the handoff needs concrete verification rather than only
unit tests.
> - This pull request adds built-in Hermes local and Hermes gateway
support, keeps external adapter overrides compatible, and
documents/tests the gateway flow end to end.
> - The benefit is that operators can hire Hermes-backed agents without
a manual plugin install, while self-hosted installs can still
override/shadow the built-ins through Adapter manager packages.
## Linked Issues or Issue Description
No public GitHub issue exists for this exact Hermes built-in adapter,
gateway onboarding, and release-source work.
Problem description:
- Hermes local and gateway adapters need a public, reviewable source
path in the monorepo so package artifacts and built-in adapter behavior
match the application source.
- Operators need built-in `hermes_local` and `hermes_gateway` adapter
choices without losing the ability to install external Hermes packages
as overrides.
- Gateway onboarding needs secure defaults for API server URLs, API
keys, and generated agent setup text.
- Hermes-originated task bridge credentials need narrower API-key scope
configuration.
- Related public PRs found during duplicate search include #3027, #2363,
#7544, #7950, #8095, and #8543.
## What Changed
- Added the unified Hermes adapter package with local and gateway
server/UI/CLI exports, config schemas, transcript parsing, model
detection, and package metadata.
- Registered `hermes_local` and `hermes_gateway` as built-in adapters
across shared constants, server registries, CLI packaging, and UI
adapter registries.
- Kept the external adapter override path compatible so installed Hermes
packages can shadow built-ins and restore the built-in parser when
disabled.
- Added Hermes gateway onboarding docs, board-operator docs, Docker
smoke assets, and shell smoke harnesses for join/e2e validation.
- Added scoped task-bridge API-key support, authorization checks,
issue-origin handling, and tests for Hermes-created Paperclip tasks.
- Hardened gateway transport and redaction behavior for API keys,
headers, session data, and smoke diagnostics.
- Updated release packaging/bootstrap checks for the Hermes packages
while leaving `pnpm-lock.yaml` out of the PR per repository policy.
## Verification
Targeted local verification recorded before PR handoff:
- `pnpm --filter @paperclipai/hermes-paperclip-adapter exec vitest run
src/gateway/server/execute.test.ts` — 14/14 passed.
- `pnpm test:hermes-gateway-smoke` — 6/6 passed.
- Hermes package typecheck/build checks passed.
- Focused server/UI adapter tests passed — 31/31.
- Release helper Node tests passed — 18/18.
- `git diff --check origin/master..HEAD` passed.
Fresh Docker E2E smoke evidence:
- Ran `pnpm smoke:hermes-gateway-e2e` on 2026-06-26 with a fresh state
directory and fresh Docker container against a live Paperclip dev
server.
- Hermes direct execution reached `completed`.
- Hermes stop/cancel path reached `cancelled`.
- Hermes gateway created a Paperclip task, Paperclip ran the Hermes
agent, and the task reached `done` with the expected marker response.
- Temporary board auth keys, token files, smoke state, and Docker
containers were cleaned up after the run.
PR checks on head `b5eae40ce`:
- GitHub Actions passed: `policy`, `review`, `Typecheck + Release
Registry`, all general test shards, all serialized server shards,
`Build`, `Canary Dry Run`, `e2e`, and aggregate `verify`.
- External checks passed: Snyk and Socket Project Report.
- External Socket Pull Request Alerts remained pending after the
first-party CI matrix completed.
## Risks
- Medium risk: this spans adapter registration, package publishing,
gateway execution, onboarding docs, API-key scoping, and UI adapter
metadata.
- Migration risk is low: the scope-config migration adds a nullable
column and does not rewrite existing keys.
- Gateway execution depends on operator-provided Hermes API
configuration; the smoke covers the Docker gateway path but real
deployments may differ by network/auth setup.
- Direct Greptile review on the latest expanded diff is file-count
limited, although the commitperclip review gate passed.
> 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 coding agent, tool use enabled in a local repository
workspace. 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] Commitperclip review gate is green; direct Greptile review is
file-count limited on the latest expanded diff
- [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
> - Agent runs stream their transcripts through per-adapter stdout
parsers into the chat/run transcript UI
(`ui/src/adapters/transcript.ts`)
> - The Cursor CLI (local) streams assistant text as many small `text`
events (often a token or word each), and the parser emitted one
assistant entry per event and trimmed each
> - As a result the chat rendered one bubble per token ("every line a
new token") and dropped inter-token whitespace, making Cursor runs hard
to read
> - The render layer already coalesces consecutive `delta` entries
(`appendTranscriptEntry`), but the Cursor parser never tagged streamed
text as a delta
> - This pull request tags streamed `text` as a delta (without trimming)
so the existing render-time coalescer merges them into one assistant
block, while a `tool_call`/`tool_result` between deltas still breaks the
run
> - The benefit is readable Cursor transcripts with correct spacing and
preserved tool boundaries, with no change to the canonical event stream
(raw view unaffected)
## Linked Issues or Issue Description
No existing public issue — describing the bug inline (per
`.github/ISSUE_TEMPLATE/bug_report.yml`):
**What happened**
In the chat/run transcript, Cursor (local) assistant messages render as
one bubble per token/word, and inter-token spaces are dropped, making
the transcript unreadable. Root cause:
`packages/adapters/cursor-local/src/ui/parse-stdout.ts` (`type: "text"`
branch) emitted `{ kind: "assistant" }` per streamed `text` event
without `delta: true` and trimmed each, so the render-time coalescer
(`ui/src/adapters/transcript.ts`) never merged them and whitespace was
lost.
**Expected behavior**
Streamed assistant text should render as a single contiguous prose
block, with tool calls preserved as boundaries between blocks.
**Steps to reproduce**
1. Run a Cursor (local) agent that streams a multi-word assistant
message.
2. Open the run transcript in the chat UI.
3. Observe each streamed token/word rendered as its own bubble, with
inter-token spaces missing.
**Paperclip version**
Reproduced on current `master` (cutover base `e68188c43`).
**Deployment mode**
Self-hosted, `cursor_local` adapter.
## What Changed
- `packages/adapters/cursor-local/src/ui/parse-stdout.ts`: tag streamed
`text` events as `{ kind: "assistant", delta: true }` and stop trimming,
so the existing `appendTranscriptEntry` coalescer merges consecutive
deltas into one block.
- `ui/src/adapters/cursor-coalescing.test.ts` (new): dual-shape golden
fixtures (Cursor local + cloud) exercising the full render-time
projection via `buildTranscript`.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/adapters/cursor-coalescing.test.ts src/adapters/transcript.test.ts`
→ **10/10 pass**.
- `pnpm --filter @paperclipai/ui --filter
@paperclipai/adapter-cursor-local typecheck` → **green**.
- The golden fixtures assert the run `text → tool_call → tool_result →
text → consolidated final` renders as exactly **two prose blocks with
the tool between them**, **no duplication** of the consolidated final,
and **inter-token whitespace preserved** across coalesced deltas.
## Risks
- **Low risk.** Pure classification at parse time; the canonical event
stream and the raw view are unchanged — only the "nice" render-time
projection changes. The coalescing logic (`appendTranscriptEntry`) is
pre-existing and already covered by tests. No schema, migration, or
behavioral change outside transcript rendering.
## Model Used
- **Claude Opus 4.8** (Anthropic), extended/high reasoning mode, driven
via the Cursor agent with tool use + code execution. Diagnosis and
fixtures grounded in the repo's actual parser/render 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 (none found)
- [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 references)
- [x] My branch name describes the change
(`fix/cursor-transcript-coalescing`) 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 (N/A —
no documented behavior changes)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending CI 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: Sebastian Heyneman <sebastian@joinnova.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The skills catalog package publishes bundled and optional skills for
installs and downstream runtime consumers.
> - Published package consumers depend on both generated catalog
manifests and the source skill files being present in the npm artifact.
> - Prior fixes improved published package resolution, but the package
contents themselves did not have a smoke test guarding against
regressions.
> - This pull request adds an npm-pack artifact test for the skills
catalog package and hardens the test cleanup path.
> - The benefit is that packaging regressions are caught before release
instead of after users install a broken catalog package.
## Linked Issues or Issue Description
Bug description:
The skills catalog npm artifact needs to include the generated catalog
manifests plus bundled and optional skill files. Without a package-level
smoke test, a future change to `files`, build output, or catalog paths
could publish an artifact that installs successfully but cannot serve
catalog consumers correctly.
Expected behavior:
The package artifact produced by `npm pack` includes
`dist/generated/catalog.json`, `generated/catalog.json`, representative
bundled and optional skill `SKILL.md` files, and `package.json`.
Actual risk before this PR:
Package content regressions could ship without a focused local test
detecting the missing files.
## What Changed
- Added a Vitest smoke test for `@paperclipai/skills-catalog` that runs
`npm pack --json` and verifies required artifact paths.
- Added a build fallback inside the test when
`dist/generated/catalog.json` is absent before packing.
- Packs into registered temporary directories and recursively removes
them after each test run so generated `.tgz` files do not leak when
parsing or assertions fail.
- Allows enough time for the smoke test to exercise the build fallback
path on fresh CI runners.
## Verification
- `pnpm --filter @paperclipai/skills-catalog test`
- `pnpm --filter @paperclipai/skills-catalog clean && pnpm --filter
@paperclipai/skills-catalog test`
- Searched for duplicate/related PRs; existing PR #8327 is already
merged and this PR adds regression coverage around the package artifact.
- Checked `ROADMAP.md`; no overlapping roadmap entry for this packaging
test.
- Confirmed the branch diff does not touch `pnpm-lock.yaml` or
`.github/workflows`.
## Risks
Low risk. This is test-only coverage for package contents. The main
practical risk is a slightly slower skills catalog test run because it
invokes `npm pack` and may build the package manifest when `dist` is
absent.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex coding agent with shell and GitHub CLI tool use.
Context window 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 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
Reviewed by CTO for PAP-12039. Documentation/catalog-only change with regenerated manifest; focused catalog verification, CI, security scans, and Greptile are green.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The pipeline subsystem models repeatable work as items moving
through stages, with agent automation, review gates, blockers, drift
notices, and linked work.
> - Operators need this to be usable as one coherent workflow surface,
not just as backend primitives or disconnected route experiments.
> - The branch now carries the pipeline data model, service/routes,
CLI/tutorial path, aggregation feeds, operator UI, stage automation
controls, liveness/retry handling, and follow-up polish that make the
primitive reviewable end to end.
> - This pull request is the single review target for that pipeline
workflow primitive stack.
> - The benefit is that reviewers can evaluate the full operator
experience and server contract together against `master`.
## Linked Issues or Issue Description
No public GitHub issue exists for this work. The underlying feature
request is described inline.
### Problem or motivation
Paperclip needs a first-class way to model multi-stage agent/company
workflows where upstream items can spawn downstream work, request
review, carry fields across pipelines, surface drift, retry automation,
and show operators where work is blocked or active. Without a unified
pipeline primitive, these workflows spread across ad hoc issues,
routines, and comments, making the state hard to inspect or operate.
### Proposed solution
Add the pipeline workflow primitive stack: database schema and
migrations, shared validators/types, server services and REST routes,
aggregation and liveness helpers, CLI/tutorial smoke support, and the
React operator UI for pipeline lists, boards, item detail,
review/learnings views, settings, stage automation, secrets, carry-over
fields, and retry/recovery flows.
### Alternatives considered
- Keep workflows as loosely linked issues and routines: rejected because
operators need a single board/detail/settings surface for repeated
workflow patterns.
- Ship backend primitives first and defer UI: rejected for this branch
because the operator experience is the main way to validate the
primitive.
- Add a narrower one-off content workflow: rejected because the same
primitives are useful across future company processes.
## What Changed
- Added and evolved pipeline schema, migrations, shared contracts,
server services, REST routes, route tests, and CLI/tutorial smoke
support.
- Added pipeline aggregation, health/liveness, drift acknowledgment,
blocker/carry-over, automation retry, stage automation environment, and
permission recovery behavior.
- Added the operator UI for pipeline index/board/item
detail/settings/review/learnings flows, including stage secrets,
automation controls, markdown/item descriptions, linked issue assets,
liveness banners, and source automation metadata.
- Refactored issue document frame rendering through the shared
`DocumentFrameHeader` component to keep document controls consistent
with the pipeline document surfaces.
- Kept this PR as the single base-branch review target for the current
pipeline branch.
## Verification
Current branch refresh:
- `pnpm vitest run server/src/__tests__/pipelines-service.test.ts` — 31
passed
- `pnpm vitest run server/src/__tests__/pipelines-routes.test.ts` — 19
passed
- `pnpm --filter ./server typecheck` — passed
- `pnpm --filter ./ui typecheck` — passed
- Verified Pipelines remains gated by `enablePipelines === true`:
sidebar item is hidden unless the flag is enabled, direct pipeline
routes redirect to `/dashboard` when disabled, and the Experimental
settings UI still has no Pipelines toggle.
- GitHub status checks on `df071c710646de625131064c3fb6588b5e97964a` —
all complete with no failing conclusions, including Actions, Socket,
Superagent/Security, and Greptile Review
- Greptile summary on `df071c710646de625131064c3fb6588b5e97964a` —
Confidence Score 5/5
- GitHub review-thread sweep — 0 unresolved Greptile threads
Previously recorded during branch development:
- Server pipeline service/route and aggregation tests
- Shared validator tests
- UI pipeline page/settings/item-detail/learnings/liveness tests
- Pipeline tutorial smoke path
## Risks
- High review surface: this is a large feature branch spanning database,
shared contracts, server behavior, CLI/docs, and UI.
- Migration ordering and schema compatibility need reviewer attention
because this branch has been kept current across multiple `master`
syncs.
- GitHub still reports merge state `BLOCKED` because the PR is awaiting
normal human review/branch-protection completion; all current status
checks are green.
- Branch-name checklist exception: this PR uses the pre-existing
requested branch name, which predates the current public-branch naming
rule. The PR title/body avoid internal issue references.
> 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 repository tool use,
shell execution, git/GitHub CLI operations, and local verification
commands. Earlier commits in this branch were assisted by Paperclip
agents and other AI coding agents as recorded in commit authorship.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [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.
> - Scheduled routines can prompt agents with variables that are filled
in at dispatch time.
> - Existing routine variable handling supported plain text-like values,
but date inputs need a structured contract so routines can pass
consistent date values.
> - Operators also need date variables to be easy to configure and
override from the routine UI.
> - This pull request adds a date variable type across shared
validation, server dispatch, and UI editing/run dialogs.
> - The benefit is that routine authors can define date inputs once and
agents receive validated ISO-style date values when routines run.
## Linked Issues or Issue Description
Refs #219
Feature request:
- Problem/motivation: Scheduled routines need first-class, typed date
variables so operators can configure dates without relying on free-form
text conventions.
- Proposed solution: Add an `x-date` routine variable type with shared
parsing/validation, server dispatch support, and UI date-picker controls
in routine variable editors and run dialogs.
- Alternatives considered: Continue treating dates as plain text, but
that leaves validation and formatting to individual operators and
agents.
- Roadmap alignment: This is a focused improvement to the completed
Scheduled Routines milestone and does not duplicate an active roadmap
item.
Related PR search:
- Searched existing PRs/issues for `routine date picker`, `date
variables`, and `scheduled routine date variable`; no direct duplicate
PR was found.
## What Changed
- Added the shared `x-date` routine variable contract, parsing,
defaults, and validation coverage.
- Extended routine dispatch to validate and pass date variable values.
- Added date input controls to the routine variable editor and routine
run variables dialog.
- Added focused tests for shared validation, server dispatch, and the UI
date controls.
## Verification
- `git diff --check public/master...HEAD`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
packages/shared/src/routine-variables.test.ts
packages/shared/src/validators/routine.test.ts
server/src/__tests__/routines-service.test.ts
ui/src/components/RoutineRunVariablesDialog.test.tsx
ui/src/components/RoutineVariablesEditor.test.tsx`
- 5 test files passed
- 68 tests passed
## Risks
Low to medium risk. This adds a new routine variable type across
shared/server/UI paths, so the main risk is compatibility with existing
routine variable payloads. The change keeps existing variable types
intact and adds targeted validation tests for the new date behavior.
> 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 terminal, git, GitHub
CLI, and local test execution capabilities.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The execution workspace subsystem powers project and workspace views
by listing runtime state, branch metadata, issue links, and status
summaries.
> - The existing workspace views relied on broad list data that can grow
expensive as a company accumulates many workspaces and linked issues.
> - That makes the Workspaces page and project workspace cards slower
than necessary because the UI does not always need the full workspace
detail payload up front.
> - This pull request adds a bounded overview contract for workspace
listings and moves the relevant UI surfaces to that cheaper path.
> - The benefit is faster workspace loading while preserving detail
fetches for pages that actually need full workspace data.
## Linked Issues or Issue Description
No public GitHub issue exists for this change. Inline bug report follows
the repository bug template.
### What happened?
Workspace index-style screens can load too much execution workspace
detail before the user asks for it. Several UI surfaces used fuller
workspace data paths for summary displays, which can make workspace
loading slower as workspace history grows.
### Expected behavior
Overview screens should request a bounded summary payload, while detail
screens should keep using the full workspace detail endpoint.
### Steps to reproduce
1. Run Paperclip from source with enough execution workspace history to
make workspace lists non-trivial.
2. Open the Workspaces page or a project workspace summary card.
3. Observe that summary UI needs only bounded workspace metadata but can
depend on broader workspace payloads.
### Paperclip version or commit
`master` at the time this branch was prepared.
### Deployment mode
Local dev (`pnpm dev`)
## What Changed
- Added shared types, validators, and path constants for bounded
execution workspace overviews.
- Added server service and route support for overview queries with
bounded linked issue/runtime metadata.
- Updated workspace overview UI API calls, query keys, breadcrumbs,
quicklooks, close dialogs, project summaries, and detail links to
consume the cheaper overview shape where appropriate.
- Added regression coverage for the new server route/service behavior
and the UI overview consumers.
- Registered the new workspace overview route in the generated OpenAPI
spec.
- Kept overview totals aligned with the project join and preserved
project slug links in workspace headers.
## Verification
- `pnpm exec vitest run
server/src/__tests__/execution-workspaces-service.test.ts
server/src/__tests__/execution-workspaces-routes.test.ts
ui/src/api/execution-workspaces.test.ts
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/pages/Workspaces.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck && pnpm --filter @paperclipai/ui
typecheck`
- `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts
server/src/__tests__/execution-workspaces-routes.test.ts
server/src/__tests__/execution-workspaces-service.test.ts`
- `pnpm test:run:serialized -- --shard-index 1 --shard-count 4`
## Risks
Low to medium risk. The change introduces a new overview contract across
shared/server/ui layers, so the main risk is a mismatch between summary
and detail payload expectations. The added route/service/UI tests cover
the intended split, and full detail pages continue using the detail
path.
> 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-based coding agent with repository tool use and
local command execution. Exact served model identifier and context
window were 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>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The `claude_local` adapter is the control-plane surface that turns
Paperclip agent settings into concrete Claude CLI invocations.
> - Issue #3777 reports that selecting Claude Haiku 4.5 from the
Paperclip UI causes Claude Code to request `claude-haiku-4-5-20251001`,
which some enterprise-scoped keys are not allowed to access.
> - In the current adapter model list, the direct Anthropic 4.5 entries
are version-pinned IDs, unlike the 4.6 entries which already use the
stable short aliases.
> - This pull request switches the direct 4.5 `claude_local` model
options to the short aliases Claude Code account access expects, while
leaving Bedrock-native IDs unchanged in the Bedrock-specific model list.
> - The benefit is that selecting Claude Sonnet 4.5 or Claude Haiku 4.5
from Paperclip no longer forces the CLI onto a more restrictive
versioned direct model name.
## What Changed
- Updated the direct `claude_local` adapter model list to use
`claude-sonnet-4-5` instead of `claude-sonnet-4-5-20250929`.
- Updated the direct `claude_local` adapter model list to use
`claude-haiku-4-5` instead of `claude-haiku-4-5-20251001`.
- Left the Bedrock-specific model IDs in `src/server/models.ts`
unchanged so AWS Bedrock routing still uses region-qualified native IDs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/adapter-claude-local typecheck`
- Confirmed the only source change is
`packages/adapters/claude-local/src/index.ts`.
## Risks
- Low risk. This only changes the direct `claude_local` model IDs
advertised by Paperclip for the two 4.5 options.
- Bedrock behavior is unchanged because the Bedrock-native identifiers
are defined separately and were not modified in this PR.
- Existing 4.6 model options are untouched.
## Model Used
- OpenAI Codex on a GPT-5-class coding model with terminal tool use and
local code execution in this workspace.
## 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 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
Fixes#3777
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents and board users operate inside a company-scoped control plane
where permissions decide which mutating actions they can perform
> - Company skills are part of the reusable agent-company setup surface,
but skill mutation had been coupled to broader agent-creation authority
> - That coupling meant importing or managing skills required a
permission that also implies hiring power, which is broader than the
operation needs
> - Paperclip already has a grant-based permission vocabulary, so skill
mutation should be authorized through a dedicated `skills:create`
capability while preserving existing default behavior for trusted agents
> - This pull request adds the skill creation permission contract,
enforces it on company skill mutations, exposes it in agent permission
management, and documents the changed CLI/API expectations
> - The benefit is a narrower, auditable permission path for skill
import/create/update/delete flows without forcing agents to receive
broader agent-creation authority
## Linked Issues or Issue Description
No public issue is linked.
Problem: company skill mutation APIs were effectively tied to broader
agent creation authority. This PR splits skill mutation authorization
onto the public `skills:create` permission while keeping existing
default skill creation behavior for agents unless explicitly disabled.
Related public PR found during duplicate search: #5330. That PR uses an
older `canManageSkills` shape; this PR implements the `skills:create`
grant path instead.
## What Changed
- Added `skills:create` to shared permission constants and agent
permission types/validators as `canCreateSkills`.
- Backfilled default human/member role grants for `skills:create`.
- Updated company skill mutation routes to require board/user or agent
access to `skills:create`, while preserving legacy/default agent
behavior through `canCreateSkills` unless explicitly disabled.
- Updated agent permission update handling, UI permission controls,
duplicate-agent payloads, plugin SDK fixtures, and agent detail API
surfaces for `canCreateSkills`.
- Added regression coverage for skill route authorization, permission
schema/default behavior, invite grants, omitted permission updates, and
duplicate-agent payloads.
- Updated CLI and Paperclip skill documentation for the new skill
creation permission.
## Verification
- `pnpm exec vitest run
server/src/__tests__/agent-permissions-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/company-skills-routes.test.ts
server/src/__tests__/invite-join-grants.test.ts
ui/src/lib/duplicate-agent-payload.test.ts` — 5 files, 90 tests passed.
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck && pnpm --filter @paperclipai/ui
typecheck` — passed.
- `pnpm test:run ...changed files...` was attempted first, but the
stable wrapper rejects explicit file arguments; direct Vitest was used
for the same targeted files.
## Risks
- Moderate authorization risk: this changes the gate for company skill
mutations, so the tests cover board grant checks, agent explicit grant
checks, legacy default allowance, and explicit denial.
- Migration/backfill risk is low: the migration only grants
`skills:create` to existing human roles that already need broad
management capability.
- UI/API compatibility risk is low: `canCreateSkills` remains default-on
for full agent permissions, and the update validator preserves omitted
values so unrelated permission edits do not re-enable disabled skill
creation.
> 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 coding agent with terminal/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 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.
> - Sandboxed agents can do useful work in a remote workspace, then
export that result back to a host review checkout.
> - A restore can leave the host checkout's index stale even when the
sandbox content and committed state are correct.
> - That drift makes reviewers see misleading missing-file or
dirty-state results after sandbox execution.
> - This pull request repairs the host index refresh path and surfaces a
clean/dirty restore signal during finalization.
> - The benefit is more reliable review checkouts and clearer sandbox
finalization status.
## Linked Issues or Issue Description
Refs #248
No exact public GitHub issue was found for this restore/index
consistency fix. The underlying bug is that a sandbox restore can update
host working-tree content without leaving the host git index aligned, so
local review tooling may report stale deletions or misleading dirtiness.
This PR keeps the restore/export path consistent and reports the
resulting clean/dirty state.
GitHub search performed for related or duplicate work: `sandbox runtime
status`, `sandbox restore index`, and `runtime progress`. No direct
duplicate PR was found.
## What Changed
- Refreshed git workspace sync/index handling after sandbox
restore/export operations.
- Added finalization status that reports whether the restored host
checkout is clean or dirty.
- Preserved sandbox-managed runtime progress callbacks around restore
and finalization.
- Added adapter-utils tests covering the host index drift repair and
clean/dirty finalization signal.
## Verification
- Local PII scan before push: high-confidence secret patterns, internal
issue links, local user paths, and private URL patterns checked across
all three split diffs; no real secrets or internal links found.
- `git diff --check
feat/sandbox-runtime-status..fix/sandbox-restore-index-sync`
- `pnpm exec vitest run
packages/adapter-utils/src/sandbox-managed-runtime.test.ts` — 1 file, 10
tests passed.
- `pnpm run typecheck` passed on `fix/sandbox-restore-index-sync`.
- `pnpm run build` passed on `fix/sandbox-restore-index-sync`; Vite
reported existing CSS `::highlight` and chunk-size warnings.
- `pnpm run test:run` was attempted on `fix/sandbox-restore-index-sync`;
it failed in two unrelated broad-suite tests. One depends on this host's
Git default branch behavior, and one depends on local Claude
model-discovery environment. The changed focused suite above passes.
## Risks
- Git index refresh behavior needs to remain conservative so it does not
hide real uncommitted user changes.
- The clean/dirty finalization signal is diagnostic; it should not by
itself mark a sandbox run successful or failed.
- This PR is stacked on the runtime-status branch so finalization
progress can reuse the same callback path.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 via Codex coding agent, with shell/tool execution in a
local worktree. Exact context-window metadata is 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 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.
> - Sandboxed agent runs can spend meaningful time preparing a remote
workspace before the agent transcript shows useful output.
> - Operators need short, current progress text for those setup phases,
but that text should not become durable run history.
> - The existing live-run websocket path already carries run updates to
the UI, so the backend can reuse that channel instead of adding polling.
> - This pull request adds an ephemeral runtime-progress contract, a
process-local status store, and heartbeat integration for
sandbox-managed runs.
> - The benefit is a clearer active-run experience without database
migrations or persistent progress rows.
## Linked Issues or Issue Description
Refs #248
No exact public GitHub issue was found for this status-message plumbing.
The underlying problem is that active sandboxed runs currently have
setup phases, such as workspace sync and restore, where the operator
cannot see concise current progress through the live run state. This PR
addresses that gap for the backend/runtime layer while keeping progress
messages ephemeral.
GitHub search performed for related or duplicate work: `sandbox runtime
status`, `sandbox restore index`, and `runtime progress`. No direct
duplicate PR was found.
## What Changed
- Added shared runtime-progress types and the `heartbeat.run.progress`
live event type.
- Added a process-local heartbeat run runtime-status store with TTL,
bounded/redacted messages, and terminal cleanup.
- Threaded runtime progress callbacks through heartbeat execution and
active/live run serialization.
- Emitted sandbox-managed runtime phase updates for sync, adapter
startup, restore/export, and finalization paths.
- Added backend and adapter-utils tests for ephemeral status behavior,
terminal cleanup, live serialization, and sandbox progress callbacks.
## Verification
- `pnpm install --frozen-lockfile`
- Local PII scan before push: high-confidence secret patterns, internal
issue links, local user paths, and private URL patterns checked across
all three split diffs; no real secrets or internal links found. The only
secret-like text is an intentional fake test fixture (`sk-test-secret`).
- `git diff --check origin/master..feat/sandbox-runtime-status`
- `pnpm exec vitest run
server/src/services/heartbeat-run-runtime-status.test.ts
server/src/__tests__/heartbeat-runtime-state.test.ts
server/src/__tests__/agent-live-run-routes.test.ts
packages/adapter-utils/src/sandbox-managed-runtime.test.ts` — 4 files,
23 tests passed.
- `pnpm run typecheck` passed on both top stacks that include this
branch: `feat/sandbox-status-ui` and `fix/sandbox-restore-index-sync`.
- `pnpm run build` passed on both top stacks that include this branch;
Vite reported existing CSS `::highlight` and chunk-size warnings.
- `pnpm run test:run` was attempted on `fix/sandbox-restore-index-sync`;
it failed in two unrelated broad-suite tests. One depends on this host's
Git default branch behavior, and one depends on local Claude
model-discovery environment. The changed focused suites above pass.
## Risks
- Runtime progress is process-local by design, so status disappears
after TTL, terminal cleanup, or server restart.
- Clients that do not consume `heartbeat.run.progress` simply keep
existing behavior.
- Message redaction is intentionally generic; overly specific phase
details should stay out of runtime-progress payloads.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 via Codex coding agent, with shell/tool execution in a
local worktree. Exact context-window metadata is 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 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 CTO <cto@paperclip.local>
Co-authored-by: Paperclip CTO <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Local adapters can run agents against sandboxed execution targets by
syncing the workspace and selected runtime assets into the sandbox.
> - The Codex local adapter includes a managed Codex home asset so
sandboxed Codex runs can use the expected auth, config, skills, and
session state.
> - That asset follows symlinks, which is useful for real Codex home
content but unsafe for transient launcher directories.
> - Transient `tmp` and `.tmp` directories can contain symlinks to large
host binaries, so the sandbox archive can inline large executable
targets instead of just the small home directory content.
> - This pull request excludes transient Codex home directories from the
sandbox home asset while preserving the required Codex home files.
> - The benefit is a much smaller and more predictable sandbox setup
upload without changing the runtime files Codex actually needs.
## Linked Issues or Issue Description
No public issue was found for this exact sandbox archive-size bug.
Bug description:
- What happened: sandboxed `codex_local` runs sync the managed Codex
home as a `home` asset with `followSymlinks` enabled. If transient Codex
home dirs such as `tmp` or `.tmp` contain symlinks to a large host
binary, the archive can inline that binary and make `Syncing home to
sandbox` much larger than the managed home directory itself.
- Expected behavior: sandbox setup should include the Codex home files
needed for auth, config, skills, and session continuity, but should not
archive transient launcher scratch directories.
- Reproduction shape: create a managed Codex home with normal
auth/config/skills files and a `tmp/arg0` or `.tmp` symlink to a large
host executable, then start a sandboxed `codex_local` run. The home
asset archive grows by the symlink target size.
- Version/commit: observed on local `master` before this change.
- Related public context: #5028 covers a different managed Codex home
reliability issue around stale auth files; this PR addresses sandbox
archive bloat from transient symlink targets.
## What Changed
- Excluded `tmp` and `.tmp` from the Codex `home` asset that is uploaded
for sandboxed runs.
- Added regression coverage proving transient symlinked home dirs are
excluded from the tar while required auth/config/skills files remain
included.
- Kept `followSymlinks` behavior for the rest of the Codex home asset so
existing non-transient symlink behavior is preserved.
## Verification
- `git diff --check`
- Local PII/secret pattern scan over the committed diff
- `pnpm exec vitest run
packages/adapter-utils/src/sandbox-managed-runtime.test.ts`
- `pnpm --filter @paperclipai/adapter-utils typecheck`
- `pnpm --filter @paperclipai/adapter-codex-local typecheck`
## Risks
Low risk. The exclusion is limited to transient Codex home scratch
directories, and the regression test verifies the files needed in the
sandbox are still archived. The main compatibility risk is if a user
intentionally placed required persistent Codex state under `tmp` or
`.tmp`; those paths are treated as volatile scratch space by this
change.
## Model Used
OpenAI Codex coding agent based on GPT-5, with shell, git, and GitHub
CLI tool use. Exact hosted model build and context-window size were not
exposed in the local adapter runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [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>
Fixes#5997, fixes#4081, fixes#4723, fixes#6625, fixes#3923
Refs #6606 — this PR removes the rejected root `paperclip` field, but
#6606 also requires the protocol v3→v4 bump, which is out of scope here;
referencing rather than closing it.
## Thinking Path
> - Paperclip is the control plane that wakes and coordinates agent
workers across company-scoped execution flows.
> - The `openclaw_gateway` adapter is part of that wake path, so its
outbound payload contract has to match the gateway's validated `agent`
schema.
> - `master` currently reintroduces a previously fixed regression by
sending a top-level `paperclip` property in `agentParams` (see #3923,
which reverts the original fix in #626).
> - The gateway rejects unknown root params, which means OpenClaw wakes
fail before the remote agent can start work.
> - The actual wake context already rides in the generated `message`, so
the extra root property is both redundant and harmful.
> - This pull request removes that leaked root property, adds a focused
regression test around param construction, and updates affected server
expectations/docs to the supported contract.
> - The benefit is that OpenClaw Gateway agents wake successfully again
without losing inline wake context.
## What Changed
- Removed the top-level `paperclip` field from OpenClaw Gateway
`agentParams` and extracted `buildAgentParams()` so the contract is easy
to test.
- Added a package-level regression test that proves
`payloadTemplate.paperclip` is stripped while explicit
`agentId`/`timeout` behavior stays intact.
- Updated server tests that inspect OpenClaw Gateway payloads to assert
wake data is delivered in `message` instead of a rejected root field.
- Updated the adapter configuration docs to state that wake context is
embedded in the generated message text, not sent as a top-level param.
### Rebase onto current `master` (conflict resolution)
This branch was opened against an older `master`; re-merging current
`master` required:
- Resolving conflicts in `execute.ts` — `master` hoisted
`configuredAgentId` and moved the agentId/timeout precedence inline;
this PR keeps the `buildAgentParams()` extraction that strips the
gateway-rejected root `paperclip`.
- Updating tests `master` added **after** this branch's base that assert
the old root-`paperclip` contract. These suites use the OpenClaw gateway
adapter purely as a delivery harness (`adapterType: "openclaw_gateway"`
+ a mock gateway) and observe wake content via the gateway payload, so
dropping the root field requires them to read wake context from
`message` instead:
- `server/src/__tests__/heartbeat-comment-wake-batching.test.ts`
- `server/src/__tests__/low-trust-red-team-routes.test.ts` (redaction
guarantees preserved — sanitized body + `expectNoCanary` on the raw
canary)
- Replaced brittle JSON-substring assertions (flagged by Greptile) with
a shared `parseWakePayloadFromMessage()` helper + `toMatchObject`,
robust to serialization/key-order changes.
The strict contract is confirmed upstream: OpenClaw's
`AgentParamsSchema` is `Type.Object(..., { additionalProperties: false
})` with no `paperclip` field, so a root `paperclip` is rejected
(`invalid agent params: at root: unexpected property 'paperclip'`).
## Verification
- `pnpm --filter @paperclipai/adapter-openclaw-gateway typecheck` —
clean
- `pnpm --filter @paperclipai/server typecheck` — clean
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/openclaw-gateway-adapter.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/low-trust-red-team-routes.test.ts` — 26 passed (7 +
11 + 8)
- `packages/adapters/openclaw-gateway/src/server/execute.test.ts` — 6
passed (run via a local temp vitest config because the root
`vitest.config.ts` does not include this package)
## Risks
- Low risk: this narrows the outbound payload to the gateway-supported
contract and keeps wake context in the already-supported `message`
channel.
- Any downstream consumer that incorrectly depended on a top-level
`paperclip` field from the gateway mock payloads would need to follow
the supported `message` contract instead.
> 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 CLI coding agent via API authored the original change;
exact underlying model ID and context window were not exposed in that
environment.
- Rebase/conflict resolution and the test-assertion migration were done
with Claude Code (Claude Opus 4.8, 1M context).
## 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 the
related issues above
- [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
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: serenakeyitan via breeze-runner <serenakeyitan@users.noreply.github.com>
Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
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
> - Agents run their work inside sandboxes, provisioned through
pluggable sandbox-provider plugins; the Daytona plugin is one of them
> - Daytona bills storage against an org-wide disk quota, and a
*stopped* sandbox still counts against that quota — only an *archived*
sandbox is moved to cold storage and stops counting
> - The Daytona plugin created sandboxes without supplying any
auto-stop/auto-archive/auto-delete intervals, so Daytona fell back to
its own defaults (archive after 7 days, never auto-delete)
> - When in-product cleanup fails or never runs (crashed runs, failed
lease destroys, orphaned probes), stopped sandboxes then sit for a week
at a few GiB each until the org storage quota fills and blocks all
workers
> - This pull request makes the Daytona plugin apply quota-safe defaults
on create (auto-stop 15m, auto-archive 60m, auto-delete 7d) so every
sandbox eventually leaves the disk quota on its own, even when our own
cleanup fails
> - The benefit is that the storage quota no longer fills from
leaked/idle sandboxes, while operators keep full control to override the
intervals per environment
## Linked Issues or Issue Description
No public GitHub issue. Describing in-PR (bug report):
### What happened
Running many agents in Daytona sandboxes eventually exhausted the org's
storage quota, and Daytona returned an out-of-disk error that blocked
all sandbox creation. Root cause: a *stopped* Daytona sandbox still
consumes disk quota; only an *archived* sandbox is moved to cold storage
and frees it. The plugin created sandboxes without specifying
auto-stop/auto-archive/auto-delete intervals, so Daytona used its
built-in defaults (auto-archive after 7 days, auto-delete disabled). Any
sandbox that our own cleanup failed to remove — or that was orphaned by
a crashed run — therefore lingered for up to a week, accumulating until
the quota filled.
### Expected behavior
Idle/leaked sandboxes should leave the storage quota automatically
within a short window, without relying solely on in-product cleanup
succeeding.
### Steps to reproduce
1. Configure the Daytona sandbox provider with no explicit
auto-stop/auto-archive/auto-delete intervals.
2. Run many agent sandboxes over time (or leak some via crashed/failed
runs that skip in-product cleanup).
3. Observe stopped-but-not-archived sandboxes accumulating against the
org's 30 GiB storage quota until Daytona returns an out-of-disk error
and blocks new sandbox creation.
### Deployment mode
Self-hosted Paperclip using the Daytona sandbox-provider plugin.
## What Changed
- `daytona/src/plugin.ts`: `parseDriverConfig` now defaults
`autoStopInterval` → 15 min, `autoArchiveInterval` → 60 min,
`autoDeleteInterval` → 7 days when the value is unset. Explicit
per-environment values (including `0` and `-1`) are preserved and passed
through unchanged.
- `daytona/src/manifest.ts`: documents the new defaults and the quota
rationale on each field, and sets the manifest `default` so the values
surface in the environment configuration screen where operators can
override them.
- `daytona/src/plugin.test.ts`: adds tests covering the new defaults and
that explicit values / disabling sentinels (`0`, `-1`) are respected.
## Verification
```
pnpm --filter @paperclipai/plugin-daytona test
```
- 24 tests pass, including the new default-behavior tests.
- Confirmed an explicit `autoArchiveInterval: 0` / `autoDeleteInterval:
-1` in config is still forwarded unchanged (defaults only apply when the
field is absent).
## Risks
Low risk. Behavior change is limited to sandboxes created with no
explicit interval config: they now auto-stop/archive/delete on
Daytona-managed timers instead of Daytona's longer built-in defaults.
Auto-archive is reversible (resuming an archived sandbox restores it).
The only persistent action is auto-delete, which is a 7-day backstop for
sandboxes nobody resumes, matching prior intent. Any environment that
needs long-lived warm sandboxes can override the intervals (including
disabling them with `0`/`-1`).
## Model Used
Claude Opus (claude-opus-4-8), via the Paperclip agent harness with tool
use. Extended reasoning 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 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
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Remote sandbox providers are part of the execution environment layer
that lets agents run outside the local host.
> - The Daytona sandbox-provider plugin exposes CPU, memory, disk, and
GPU settings so operators can request larger execution sandboxes.
> - The plugin was forwarding resource settings through snapshot/default
creation, but Daytona rejects resource overrides on that path.
> - Image-backed Daytona creation does support resource settings, so
Paperclip should only send those values when an image is configured.
> - This pull request makes the Daytona contract explicit in validation
and runtime acquisition.
> - The benefit is that operators get a clear Paperclip error for
unsupported snapshot/default resource configs, while image-backed
sandboxes still receive the requested allocation.
## Linked Issues or Issue Description
No public GitHub issue exists.
### What happened?
Daytona sandbox environments can be configured with CPU/memory/disk/GPU
resource settings, but snapshot/default Daytona sandbox creation rejects
those resource overrides. Paperclip could pass unsupported resource
fields through to Daytona and surface an opaque provider error.
### Expected behavior
Paperclip should only send resource fields on Daytona creation paths
that support them, and it should reject unsupported resource
configurations before creating a sandbox.
### Steps to reproduce
1. Configure a Daytona sandbox environment with resource values such as
`cpu: 4` and `memory: 4`.
2. Leave the environment on snapshot/default creation by not setting an
image.
3. Acquire a Daytona sandbox lease.
4. Observe Daytona reject the resource override on the snapshot/default
path.
### Paperclip version or commit
Reproduced against the current Daytona sandbox-provider plugin behavior
before this PR. This PR fixes the contract in the plugin code and tests.
### Deployment mode
Local authenticated Paperclip instance using the Daytona sandbox
provider.
Additional scope:
- Daytona provider only.
- No schema changes.
- No non-Daytona provider changes.
Related search performed:
- `gh search issues "Daytona resources snapshot
repo:paperclipai/paperclip" --limit 10`
- `gh search prs "Daytona resources snapshot repo:paperclipai/paperclip"
--limit 10`
## What Changed
- Restrict Daytona `resources` create params to image-backed sandbox
creation.
- Add validation/runtime guard for resource settings without an image.
- Keep image-backed resource metadata and reusable-lease sentinel
behavior covered by tests.
- Update Daytona plugin tests for image-backed resources and
snapshot/default rejection.
## Verification
- `pnpm -C packages/plugins/sandbox-providers/daytona test`
- `pnpm -C packages/plugins/sandbox-providers/daytona build`
- Manual Daytona SDK probe in a local authenticated instance:
image-backed creation with `daytonaio/sandbox:0.8.0` returned a 4 CPU /
4 GiB sandbox and deleted cleanly.
- Greptile Review: 5/5, no inline comments.
## Risks
- Existing Daytona environments that set resource values while relying
on snapshot/default creation will now fail validation/acquisition with a
clear error instead of calling Daytona and failing there.
- Operators who need custom resource sizes should use image-backed
creation or a provider-side resource-sized snapshot workflow.
- Low migration risk: no database schema changes and no changes to
non-Daytona providers.
> 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 coding agent. Exact context window is not exposed in
this environment. Tool-enabled workflow with shell, GitHub CLI, database
inspection, and local test/build execution.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Claude <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI, plugin examples, and plugin SDK all share the same
React dependency graph.
> - Dependabot PR #8471 raised `react` and `@types/react`, but left
`react-dom` and `@types/react-dom` on older 19.x ranges.
> - That let dependency resolution install incompatible React runtime
versions, which makes React refuse to run and breaks UI-oriented test
suites.
> - Current PR policy keeps `pnpm-lock.yaml` owned by CI for
non-dependabot authors, so this PR updates manifests and lets CI
regenerate the lockfile artifact.
> - This pull request replaces #8471 with a complete React toolchain
alignment across the package manifests it touched.
> - The benefit is a single React 19.2.7 runtime graph that keeps
dependency consumers from deduping onto a stale `react-dom` patch.
## Linked Issues or Issue Description
Refs #8471
This PR replaces #8471, which updated `react` and `@types/react` but
left `react-dom` and `@types/react-dom` behind. The resulting dependency
graph can install mismatched `react` and `react-dom` versions, producing
React's incompatible-version runtime error in UI tests.
## What Changed
- Aligned `react-dom` package ranges to `^19.2.7` anywhere the React
dependency set is present.
- Aligned `@types/react-dom` package ranges to the 19.2 line.
- Kept the `react@^19.2.7` and `@types/react@^19.2.17` bumps from #8471.
- Added root pnpm overrides for `react` and `react-dom` so peer-only
consumers resolve to the same 19.2.7 runtime instead of a stale patch.
- Left `pnpm-lock.yaml` out of the PR, per the PR workflow policy; CI
regenerates and shares the lockfile artifact when manifests change.
## Verification
- `cd ui && NODE_ENV=test npx vitest run` passed locally before handoff:
243 files / 1739 tests.
- `NODE_ENV=development pnpm install --lockfile-only --ignore-scripts
--no-frozen-lockfile` passed locally on the rebased branch.
- Confirmed regenerated `pnpm-lock.yaml` resolves `react` and
`react-dom` to 19.2.7 and contains no `react@19.2.4` /
`react-dom@19.2.4` entries.
- GitHub Actions are green on this PR, including `policy`, `Typecheck +
Release Registry`, all general test shards, `Build`, `e2e`, and
`verify`.
- Greptile completed at 5/5 with zero unresolved threads.
## Risks
Low risk. This is a dependency-version alignment only, but React
dependency changes can expose package-manager resolution issues. The
root overrides intentionally constrain the React runtime pair to the
same patch version to avoid that class of failure.
## Model Used
OpenAI Codex based on GPT-5, using command-line tool execution and
GitHub CLI operations in a Paperclip-managed workspace.
## 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>
[//]: # (dependabot-start)
⚠️ **Dependabot is rebasing this PR** ⚠️
Rebasing might not happen immediately, so don't worry if this takes some
time.
Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.
---
[//]: # (dependabot-end)
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.28.0 to 0.28.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/evanw/esbuild/releases">esbuild's
releases</a>.</em></p>
<blockquote>
<h2>v0.28.1</h2>
<ul>
<li>
<p>Disallow <code>\</code> in local development server HTTP requests (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p>
<p>This release fixes a security issue where HTTP requests to esbuild's
local development server could traverse outside of the serve directory
on Windows using a <code>\</code> backslash character. It happened due
to the use of Go's <code>path.Clean()</code> function, which only
handles Unix-style <code>/</code> characters. HTTP requests with paths
containing <code>\</code> are no longer allowed.</p>
<p>Thanks to <a
href="https://github.com/dellalibera"><code>@dellalibera</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Add integrity checks to the Deno API (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p>
<p>The previous release of esbuild added integrity checks to esbuild's
npm install script. This release also adds integrity checks to esbuild's
Deno install script. Now esbuild's Deno API will also fail with an error
if the downloaded esbuild binary contains something other than the
expected content.</p>
<p>Note that esbuild's Deno API installs from
<code>registry.npmjs.org</code> by default, but allows the
<code>NPM_CONFIG_REGISTRY</code> environment variable to override this
with a custom package registry. This change means that the esbuild
executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the
expected content.</p>
<p>Thanks to <a
href="https://github.com/sondt99"><code>@sondt99</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Avoid inlining <code>using</code> and <code>await using</code>
declarations (<a
href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p>
<p>Previously esbuild's minifier sometimes incorrectly inlined
<code>using</code> and <code>await using</code> declarations into
subsequent uses of that declaration, which then fails to dispose of the
resource correctly. This bug happened because inlining was done for
<code>let</code> and <code>const</code> declarations by avoiding doing
it for <code>var</code> declarations, which no longer worked when more
declaration types were added. Here's an example:</p>
<pre lang="js"><code>// Original code
{
using x = new Resource()
x.activate()
}
<p>// Old output (with --minify)<br />
new Resource().activate();</p>
<p>// New output (with --minify)<br />
{using e=new Resource;e.activate()}<br />
</code></pre></p>
</li>
<li>
<p>Fix module evaluation when an error is thrown (<a
href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>,
<a
href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p>
<p>If an error is thrown during module evaluation, esbuild previously
didn't preserve the state of the module for subsequent module
references. This was observable if <code>import()</code> or
<code>require()</code> is used to import a module multiple times. The
thrown error is supposed to be thrown by every call to
<code>import()</code> or <code>require()</code>, not just the first.
With this release, esbuild will now throw the same error every time you
call <code>import()</code> or <code>require()</code> on a module that
throws during its evaluation.</p>
</li>
<li>
<p>Fix some edge cases around the <code>new</code> operator (<a
href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p>
<p>Previously esbuild incorrectly printed certain edge cases involving
complex expressions inside the target of a <code>new</code> expression
(specifically an optional chain and/or a tagged template literal). The
generated code for the <code>new</code> target was not correctly wrapped
with parentheses, and either contained a syntax error or had different
semantics. These edge cases have been fixed so that they now correctly
wrap the <code>new</code> target in parentheses. Here is an example of
some affected code:</p>
<pre lang="js"><code>// Original code
new (foo()`bar`)()
new (foo()?.bar)()
<p>// Old output<br />
new foo()<code>bar</code>();<br />
new (foo())?.bar();</p>
<p></code></pre></p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/evanw/esbuild/blob/main/CHANGELOG.md">esbuild's
changelog</a>.</em></p>
<blockquote>
<h2>0.28.1</h2>
<ul>
<li>
<p>Disallow <code>\</code> in local development server HTTP requests (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p>
<p>This release fixes a security issue where HTTP requests to esbuild's
local development server could traverse outside of the serve directory
on Windows using a <code>\</code> backslash character. It happened due
to the use of Go's <code>path.Clean()</code> function, which only
handles Unix-style <code>/</code> characters. HTTP requests with paths
containing <code>\</code> are no longer allowed.</p>
<p>Thanks to <a
href="https://github.com/dellalibera"><code>@dellalibera</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Add integrity checks to the Deno API (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p>
<p>The previous release of esbuild added integrity checks to esbuild's
npm install script. This release also adds integrity checks to esbuild's
Deno install script. Now esbuild's Deno API will also fail with an error
if the downloaded esbuild binary contains something other than the
expected content.</p>
<p>Note that esbuild's Deno API installs from
<code>registry.npmjs.org</code> by default, but allows the
<code>NPM_CONFIG_REGISTRY</code> environment variable to override this
with a custom package registry. This change means that the esbuild
executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the
expected content.</p>
<p>Thanks to <a
href="https://github.com/sondt99"><code>@sondt99</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Avoid inlining <code>using</code> and <code>await using</code>
declarations (<a
href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p>
<p>Previously esbuild's minifier sometimes incorrectly inlined
<code>using</code> and <code>await using</code> declarations into
subsequent uses of that declaration, which then fails to dispose of the
resource correctly. This bug happened because inlining was done for
<code>let</code> and <code>const</code> declarations by avoiding doing
it for <code>var</code> declarations, which no longer worked when more
declaration types were added. Here's an example:</p>
<pre lang="js"><code>// Original code
{
using x = new Resource()
x.activate()
}
<p>// Old output (with --minify)<br />
new Resource().activate();</p>
<p>// New output (with --minify)<br />
{using e=new Resource;e.activate()}<br />
</code></pre></p>
</li>
<li>
<p>Fix module evaluation when an error is thrown (<a
href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>,
<a
href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p>
<p>If an error is thrown during module evaluation, esbuild previously
didn't preserve the state of the module for subsequent module
references. This was observable if <code>import()</code> or
<code>require()</code> is used to import a module multiple times. The
thrown error is supposed to be thrown by every call to
<code>import()</code> or <code>require()</code>, not just the first.
With this release, esbuild will now throw the same error every time you
call <code>import()</code> or <code>require()</code> on a module that
throws during its evaluation.</p>
</li>
<li>
<p>Fix some edge cases around the <code>new</code> operator (<a
href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p>
<p>Previously esbuild incorrectly printed certain edge cases involving
complex expressions inside the target of a <code>new</code> expression
(specifically an optional chain and/or a tagged template literal). The
generated code for the <code>new</code> target was not correctly wrapped
with parentheses, and either contained a syntax error or had different
semantics. These edge cases have been fixed so that they now correctly
wrap the <code>new</code> target in parentheses. Here is an example of
some affected code:</p>
<pre lang="js"><code>// Original code
new (foo()`bar`)()
new (foo()?.bar)()
<p>// Old output<br />
new foo()<code>bar</code>();<br />
new (foo())?.bar();<br />
</code></pre></p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="bb9db84c02"><code>bb9db84</code></a>
publish 0.28.1 to npm</li>
<li><a
href="9ff053e53b"><code>9ff053e</code></a>
security: add integrity checks to the Deno API</li>
<li><a
href="0a9bf2135b"><code>0a9bf21</code></a>
enforce non-negative size in gzip parser</li>
<li><a
href="e2a1a71320"><code>e2a1a71</code></a>
security: forbid <code>\\</code> in local dev server requests</li>
<li><a
href="83a2cbfc35"><code>83a2cbf</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>:
don't inline <code>using</code> declarations</li>
<li><a
href="308ad745d8"><code>308ad74</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4471">#4471</a>:
renaming of nested <code>var</code> declarations</li>
<li><a
href="f013f5f99a"><code>f013f5f</code></a>
fix some typos</li>
<li><a
href="aafd6e48b1"><code>aafd6e4</code></a>
chore: fix some minor issues in comments (<a
href="https://redirect.github.com/evanw/esbuild/issues/4462">#4462</a>)</li>
<li><a
href="15300c30b5"><code>15300c3</code></a>
follow up: cjs evaluation fixes</li>
<li><a
href="1bda0c31d7"><code>1bda0c3</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>,
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4467">#4467</a>:
esm evaluation fixes</li>
<li>Additional commits viewable in <a
href="https://github.com/evanw/esbuild/compare/v0.28.0...v0.28.1">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>
Bumps [@cursor/sdk](https://github.com/cursor/cursor) from 1.0.18 to
1.0.19.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/cursor/cursor/commits">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>
Bumps [@codemirror/view](https://github.com/codemirror/view) from
6.39.15 to 6.43.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/codemirror/view/blob/main/CHANGELOG.md">@codemirror/view's
changelog</a>.</em></p>
<blockquote>
<h2>6.41.0 (2026-04-01)</h2>
<h3>Bug fixes</h3>
<p>Fix an issue where <code>EditorView.posAtCoords</code> could
incorrectly return a position near a higher element on the line, in
mixed-font-size lines.</p>
<p>Expand the workaround for the Webkit bug that causes nonexistent
selections to stay visible to be active on non-Safari Webkit
browsers.</p>
<h3>New features</h3>
<p>The new <code>EditorView.cursorScrollMargin</code> facet can now be
used to configure the extra space used when scrolling the cursor into
view.</p>
<h2>6.40.0 (2026-03-12)</h2>
<h3>Bug fixes</h3>
<p>Fix a bug that caused Shift-Enter/Backspace/Delete on iOS to lose the
shift modifier when delivered to key event handlers.</p>
<p>Fix an issue where <code>EditorView.moveVertically</code> could move
to the wrong place in wrapped lines with a large line height.</p>
<p>Make sure the selection head associativity is properly set for mouse
selections made with shift held down.</p>
<h3>New features</h3>
<p><code>WidgetType.updateDOM</code> is now called with the previous
widget value as third argument.</p>
<h2>6.39.17 (2026-03-10)</h2>
<h3>Bug fixes</h3>
<p>Improve touch tap-selection on line wrapping boundaries.</p>
<p>Make <code>drawSelection</code> draw our own selection handles on
iOS.</p>
<p>Fix an issue where <code>posAtCoords</code>, when querying line
wrapping points, got confused by extra empty client rectangles produced
by Safari.</p>
<h2>6.39.16 (2026-03-02)</h2>
<h3>Bug fixes</h3>
<p>Perform scroll stabilization on the document or wrapping scrollable
elements, when the user scrolls the editor.</p>
<p>Fix an issue where changing decorations right before a composition
could end up corrupting the visible DOM.</p>
<p>Fix an issue where some types of text input over a selection would be
read as happening in wrong position.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/codemirror/view/commits">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-agent
companies.
> - The control plane coordinates issues, workspaces, documents,
routines, and board review flows across company-scoped data.
> - The local source branch contained related schema, service, and UI
changes for workspace issue scoping and document/routine annotations.
> - These changes need to move together because db schema, shared types,
server services, and UI consumers form one contract.
> - This pull request extracts the migration-bearing control-plane work
from the source branch onto `origin/master`.
> - The benefit is a standalone branch with deterministic migration
order and focused review for the highest-risk part of the split.
## Linked Issues or Issue Description
No GitHub issue exists for this branch split. Internal source task:
[PAP-11234](/PAP/issues/PAP-11234).
Problem/motivation:
- Workspace operations need explicit issue scoping so readiness and
blocker handling can be derived correctly.
- Document annotations need reliable live updates, save failure
surfacing, normalized activity keys, and better comment panel behavior.
- Routine descriptions need the same annotation contract as issue
documents so operators can discuss and edit routine text without
special-case infrastructure.
Proposed solution:
- Add the workspace-operation `issueId` migration and readiness scoping.
- Add routine document/annotation schema, shared types, services,
routes, and UI editing support.
- Keep the related migrations in one PR so the renumbered `0106` and
`0107` migrations land in a deterministic order after current `master`.
Alternatives considered:
- Split migrations into separate PRs, rejected because that would create
migration-numbering conflicts and make each branch less standalone.
- Merge this with UI polish, rejected because this branch needs deeper
server/db review.
Roadmap alignment:
- Checked `ROADMAP.md`; the roadmap mentions future recurring routine
capabilities generally, but no duplicate implementation PR for these
annotation/workspace changes was found.
## What Changed
- Added `0106_workspace_operations_issue_id.sql` and
`0107_routine_description_annotations.sql`, plus schema exports.
- Scoped workspace readiness to blocker issues and attached workspace
operation issue ids.
- Scoped issue-thread interaction accept finalization to the source run.
- Added routine document annotation contracts across
db/shared/server/UI.
- Improved document annotation live updates, activity-key normalization,
save failure surfacing, and comment panel behavior.
- Added issue workspace property controls and compact
blocked-by/quick-control UI updates.
- Added focused server and UI regression tests for the new contracts.
## Verification
- `CI=true NODE_ENV=development pnpm install --frozen-lockfile
--prefer-offline`
- `NODE_ENV=test pnpm exec vitest
server/src/__tests__/document-annotation-routes.test.ts
server/src/__tests__/issue-thread-interactions-service.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/routine-document-annotation-routes.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/workspace-runtime.test.ts
ui/src/components/IssueDocumentAnnotations.test.tsx
ui/src/components/IssueProperties.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/context/LiveUpdatesProvider.test.ts --run` — 10 files, 273 tests
passed.
- `NODE_ENV=test pnpm -r --filter @paperclipai/db --filter
@paperclipai/shared --filter @paperclipai/server --filter
@paperclipai/ui typecheck` — passed, including db migration numbering
check.
## Risks
- Migration-bearing PR; merge this branch before any later PR that adds
migrations with higher numbers.
- Cross-layer contract risk across db/shared/server/ui, mitigated with
targeted tests and affected-package typecheck.
- Review should pay special attention to company scoping in new
routine/document annotation paths.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex via Paperclip `codex_local` / CodexCoder, GPT-5-class
coding model with tool use and shell execution. Exact runtime snapshot
and context-window setting were not exposed by the Paperclip run
context.
## 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 available from the run context)
- [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 (N/A per source task: do not add screenshots/images unless
specifically part of the work)
- [x] I have updated relevant documentation to reflect my changes (N/A;
no public docs changed)
- [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
> - This change touches several already-shipped subsystems —
document-comment annotations, the selected-agent Conference Room chat
surface, routine description annotations, workspace-operation tracking,
and the board polling/inbox UI
> - A batch of incremental fixes and two small backend additions had
accumulated on a local mainline and were deployed to a live instance,
but never landed upstream — so each `origin/master` sync kept
re-diverging
> - Leaving them un-upstreamed means the same delta has to be re-merged
on every sync and risks being lost or silently reverted
> - This pull request rebases that delta cleanly on top of current
`origin/master` (preserving recent upstream work such as reusable
sandbox leases and the relation-list collapse controls) and brings it up
for review
> - The benefit is that mainline and the deployed instance converge, and
these fixes/additions get normal review + CI + Greptile coverage
## Linked Issues or Issue Description
No single GitHub issue tracks this; it is a bundle of bug fixes and two
small feature additions. Following the issue-template fields:
**Bug fixes (what was wrong → what this does):**
- Document comments rendered out of document order, didn't live-update
across clients, swallowed save failures, and lost the markdown text
selection on re-render. Now: doc-order sort, live updates, surfaced
save-failure state, stable selection across re-renders.
- The board polling hot path returned oversized payloads on every poll.
Now: an opt-in `summary` projection trims the heartbeat-run list
payload.
- Assorted UI fixes: sidebar nav peek/streamlining edge cases, markdown
file-viewer re-mount/line-height issues on iOS Safari, inbox badge/skill
deep-link tab selection, and `⌘.` work-mode cycling on iOS.
**Feature additions:**
- `workspace_operations.issue_id` — associate a workspace operation with
the issue that triggered it (new migration `0106`, schema, service,
shared type).
- Routine **description annotations** — comment threads on a routine's
description document, mirroring issue document annotations (new
migration `0107`, `routine_documents` schema, routes/service,
editable-sections UI).
- Selected-agent **Conference Room chat** surface wiring and live
issue-thread updates.
**Related PR:** #8229 (`feat(control-plane): add annotation and
workspace controls`, draft) covers overlapping
annotation/workspace-control territory — flagging it so a reviewer can
reconcile the two rather than double-merging.
## What Changed
- `feat(workspace)`: `workspace_operations.issue_id` migration +
schema/service/type; issue workspace property controls reconciled with
upstream's evolved "Service" row.
- `feat(routines)`: routine description annotations —
`routine_documents` schema, migration `0107`, routes/service,
`editable-sections` UI.
- `fix(document-comments)`: doc-order sort, live updates, save-failure
surfacing, stable markdown selection (+ storybook story, rerender test).
- `feat(chat)`: selected-agent Conference Room chat surface and live
issue-thread updates (`LiveUpdatesProvider`, `issue-chat-messages`,
interactions service).
- `perf(board)`: opt-in `summary` projection for the board
polling/heartbeat-run list payload, plus assorted sidebar / file-viewer
/ inbox / IssueProperties UI fixes.
Organized into 5 logical commits. Migrations are numbered incrementally
after upstream's latest (`0105`) — `0106` then `0107`, no journal
collision.
## Verification
- Built by 3-way merging the deployed delta onto current
`origin/master`; the only merge conflict (`IssueProperties.test.tsx`,
two adjacent test blocks) was resolved in favor of upstream's evolved
"green service link above the workspace row" layout, which matches the
merged component's rendered output.
- Confirmed recent upstream work is preserved post-merge: reusable
sandbox lease teardown (`#8513`), the IssueProperties relation-list
collapse controls, and the sidebar streamlined-nav default.
- Confirmed the net diff vs `origin/master` is exactly the intended
feature delta (65 files) and that overlapping server files (`issues.ts`,
`agents.ts`, `heartbeat.ts`) only add feature code without disturbing
upstream logic.
- This delta is already running on a live deployed instance.
- Full typecheck/test suite + Greptile to run in CI (see checklist).
## Risks
- Two new migrations (`0106`, `0107`). Both are additive (new table /
new nullable column) and ordered after upstream's `0105`; no data
backfill, low risk. If another migration-bearing PR merges first,
renumber before merge.
- Largest blast radius is in the merged overlapping UI/service files;
covered by the existing test suites for those files plus CI.
- Overlaps thematically with draft PR #8229 — reviewers should reconcile
rather than merge both blindly.
## Model Used
Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, with tool use
(git, shell). Agentic coding workflow.
## 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)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to coordinate
AI agents, issues, approvals, comments, and work products.
> - The involved subsystem is issue context: markdown links, issue
properties, related work, lists, filters, inbox/sidebar status, and
plugin-provided external context.
> - The gap is that URLs to external systems currently remain mostly
plain links, so humans and agents must manually open them to understand
status, identity, and liveness.
> - This matters because external work objects such as GitHub issues and
pull requests are part of the operational state of a Paperclip company.
> - The implementation keeps core provider-neutral: shared contracts,
storage, sync, routes, and UI surfaces live in core while providers can
contribute detection and status resolution.
> - This pull request adds the external object reference foundation,
GitHub provider support, issue-surface rendering, filters,
sidebar/list/inbox signals, and test/story coverage.
> - The benefit is that linked external work becomes inspectable
Paperclip context without hardcoding every provider directly into the
UI.
## Linked Issues or Issue Description
No public GitHub issue exists for this work.
Feature request:
- Problem: URLs in Paperclip issues, comments, documents, and related
surfaces do not expose provider status or object identity inline.
- Proposed behavior: detect supported external object URLs, persist
normalized references, refresh provider status, and render concise
status-aware links across issue surfaces.
- Users affected: board users, agents, and maintainers who triage issues
containing external work links.
- Acceptance: external object references are company-scoped,
provider-extensible, visible in key issue surfaces, filterable where
relevant, and covered by focused shared/server/UI tests.
Related PR search:
- No open duplicate PRs found for `external object references`.
- Closed related prior attempt: #4556.
## What Changed
- Added shared external-object contracts, validators, status/liveness
helpers, and plugin protocol declarations.
- Added database schema and additive migrations for external objects,
source mentions, and display metadata.
- Added server services/routes for detecting, syncing, summarizing,
refreshing, and resolving external objects across issues, documents,
comments, projects, and plugins.
- Added a GitHub external-object provider plus plugin SDK authoring
docs.
- Wired UI presentation across markdown links, comments, issue chat,
documents, properties, related work, issue rows, filters, inbox/sidebar
badges, and Storybook stories.
- Rebasing cleanup: moved the branch onto current `master`, repaired
stale worktree provision config, hardened environment-sensitive
tests/mocks, and removed committed screenshot artifacts from the PR
branch to keep the reviewable file set below tool limits.
## Verification
- `pnpm exec vitest run packages/shared/src/external-objects.test.ts
server/src/__tests__/external-object-routes.test.ts
server/src/__tests__/external-objects-service.test.ts
ui/src/components/ExternalObjectPill.test.tsx
ui/src/lib/external-objects.test.ts` passed after rebasing: 5 files, 56
tests.
- Historical branch verification before this PR creation included `pnpm
test:run`, `pnpm -r typecheck`, and `pnpm build`; this PR body does not
claim those were rerun after the final rebase.
## Risks
- Medium: this adds a new cross-surface sync path on
issue/document/comment writes. The implementation uses safe sync
wrappers so external-object failures warn instead of blocking core
mutations.
- Medium: the migrations introduce new tables and indexes. They are
additive and company-scoped.
- Medium: provider-specific URL parsing can miss or misclassify edge
cases. Shared canonicalization tests and provider tests cover current
GitHub shapes.
- Low: UI badge/filter behavior could add visual noise for object-heavy
issues; component tests and Storybook stories cover the intended
surfaces.
> Roadmap checked: `ROADMAP.md` references the plugin system as the
current extension path and does not list a duplicate core feature.
Related long-range docs discuss external references, work products,
preview URLs, and plugin extension points; this PR implements the scoped
external-object reference foundation.
## Model Used
OpenAI Codex, GPT-5 coding-agent runtime, with shell and GitHub CLI tool
use. Reasoning mode: medium. Exact deployed runtime model ID and context
window were not exposed in the 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 board UI has an experimental streamlined left navigation mode
that changes how projects and agents appear in the sidebar.
> - Today that mode is opt-in, so users keep seeing the classic
navigation unless they find and enable the experiment.
> - The requested product behavior is to make streamlined navigation the
default experience while keeping an explicit experiments opt-out.
> - This pull request flips the shared/server default, updates UI
consumers to treat only explicit `false` as classic mode, and covers
both the default-on path and opt-out behavior in tests.
> - The benefit is that new and legacy settings get the streamlined
sidebar by default without removing the classic-sidebar escape hatch.
## Linked Issues or Issue Description
Refs #7645
Related: #8430 takes the broader route of removing the classic sidebar.
This PR intentionally keeps the opt-out path.
## What Changed
- Default `enableStreamlinedLeftNavigation` to `true` in shared
validation and server-side normalization.
- Preserve explicit stored `false` as the experiments opt-out for the
classic sidebar.
- Render the sidebar and experimental settings toggle as streamlined-on
unless the setting is explicitly `false`.
- Add regression coverage for loading/default streamlined sidebar
behavior and the opt-out patch from the experiments page.
- Remove internal issue identifiers from newly touched source comments
before publishing.
## Verification
- `git diff --check origin/master...HEAD` — passed.
- `pnpm -r typecheck` — passed.
- `pnpm exec vitest run
server/src/__tests__/instance-settings-service.test.ts
ui/src/components/Sidebar.test.tsx
ui/src/pages/InstanceExperimentalSettings.test.tsx` — passed, 3 files /
27 tests.
- `pnpm build` — passed, with existing Vite/CSS/chunk-size warnings.
- `pnpm test:run` — failed in unrelated
`server/src/__tests__/workspace-runtime.test.ts`: the test `auto-detects
the default branch via symbolic-ref when origin/HEAD is set` creates a
temp repo on `main` then runs `git push -u origin main master`; `master`
does not exist in that temp repo. Summary: 1 failed, 214 passed, 1780
tests passed, 1 skipped.
## Risks
Low-to-medium behavioral risk: the default sidebar changes for users who
never explicitly set the experiment. Explicit `false` remains respected,
so users can still opt out via experimental settings.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 via Codex local adapter, with tool use and code execution.
Exact context window was not surfaced in 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 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 planning skills turn approved plans into executable issue
graphs for agents
> - The executor starts assigned tasks that have no open blockers
> - Parent/child nesting gives structure, but it does not create
execution dependencies
> - Planning guidance that does not make this distinction can let
dependent tasks run too early
> - This pull request clarifies that hard dependencies must be encoded
as `blockedByIssueIds`
> - The benefit is that agents converting plans into tasks have a
clearer checklist for creating and verifying runnable task graphs
## Linked Issues or Issue Description
No public GitHub issue was found for this documentation update.
Problem:
- The planning guidance could be read as if hierarchy, phase order, or
prose blocker notes were enough to sequence execution.
- In Paperclip, only explicit blockers prevent an otherwise-ready
assigned task from starting.
Expected behavior:
- Skill guidance should tell planners to encode each hard dependency as
`blockedByIssueIds` on the dependent issue.
- It should also tell planners to verify the created issue graph before
closing the source planning issue.
## What Changed
- Clarified that parent/child issue nesting is structural and not an
execution blocker.
- Added guidance to build a compact task matrix with task, owner,
initial status, and blockers when converting accepted plans into tasks.
- Added verification guidance to re-fetch or otherwise inspect created
issues before marking the source planning issue done.
- Updated the bundled task-planning skill handoff text to point at the
same dependency expectations without duplicating the full companion
skill text.
## Verification
- Ran `git diff --check origin/master...HEAD` locally.
- Rebased cleanly onto the latest `master` from `paperclipai/paperclip`
before pushing.
- Greptile completed at 5/5 on the latest head; the previous P2 wording
thread was fixed and resolved.
- GitHub PR checks are green on latest head
`2b07db9aef22ca32c65fa66fcca2db86c5b01220`, including policy,
commitperclip review, typecheck/release registry, general test shards,
build, serialized server suites, e2e, canary dry run, verify, Socket,
security-review, Snyk, and Greptile.
- No additional local unit test command was run because this is a
documentation/skill-text-only change and the remote PR suite covered the
repository gates.
## Risks
Low risk. This changes skill guidance only; it does not alter runtime
behavior, database schema, migrations, workflows, or UI code.
> 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-based coding agent in the Paperclip local adapter
runtime. Exact backend model ID and context window were not exposed by
the runtime. Used shell, git, GitHub CLI, and the GitHub connector for
repository inspection and PR creation.
## 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
- [ ] 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
- [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
> - Sandbox and SSH runtimes need to preserve agent work across isolated
execution environments
> - Git-backed workspaces were being copied mostly as filesystem
archives, which breaks when `.git` points outside the mounted workspace
and makes sandbox agents unable to publish their own branches
> - Large ignored dependency trees could also be swept into the sandbox
overlay, causing multi-GB transfers and max-string failures in some
sandbox clients
> - This pull request makes sandbox runtime setup use a git-backed HEAD
sync plus a small dirty/untracked overlay, and bounds sandbox file
transfers so large archives do not need one huge string
> - The benefit is that sandbox agents can commit and push from a usable
git checkout without uploading dependency trees such as `node_modules`
## Linked Issues or Issue Description
Refs #8395
No public duplicate issue or PR was found after searches for `sandbox
git workspace`, `git push sandbox`, and `node_modules sandbox upload`.
Bug report:
- What happened: sandbox-backed agent workspaces could receive a `.git`
file that pointed at host-only git state, leaving the sandbox unable to
run normal git workflows. The sandbox overlay upload could also include
ignored dependency directories, creating very large transfers.
- Expected behavior: sandbox and remote runtimes should prepare a usable
git-backed workspace, copy only the necessary workspace overlay, and
restore git history plus file changes without depending on a host-only
`.git` path.
- Steps to reproduce:
1. Run an agent in a sandbox-backed workspace whose local git checkout
is a worktree.
2. Ask the agent to complete a GitHub workflow that requires commit/push
access.
3. Observe that git operations can fail inside the sandbox, and ignored
dependency trees can be uploaded as part of the workspace overlay.
- Paperclip version or commit: reproduced against `master` before this
PR, base `7aa212296eb1`.
- Deployment mode: local dev / sandbox-backed runtime.
- Installation method: built from source.
- Agent adapters involved: local adapters using shared adapter-utils
runtime preparation.
- Database mode: not database-related.
- Access context: agent runtime.
- Local verification environment: Node.js v25.6.1, pnpm 9.15.4, macOS
arm64.
- Privacy checklist: all pasted output was reviewed for secrets, private
hostnames, local usernames, and internal instance links.
## What Changed
- Added a GitHub workflow push preflight so agent runs can detect
missing push credentials when a workflow explicitly needs GitHub
publishing.
- Added shared git workspace sync helpers for shallow HEAD import/export
and dirty/untracked overlay tracking.
- Updated sandbox managed runtime setup to use git history plus a
selected overlay instead of uploading the full local workspace for
git-backed workspaces.
- Bounded sandbox archive upload/download paths so large payloads stream
or chunk instead of materializing one oversized string.
- Excluded `.git` and ignored dependency trees from sandbox upload,
download, and restore baselines while preserving local ignored
directories during sync-back.
- Added focused tests for git workspace sync, sandbox overlay selection,
transfer chunking, and heartbeat push-preflight behavior.
## Verification
- `pnpm exec vitest run
packages/adapter-utils/src/git-workspace-sync.test.ts
packages/adapter-utils/src/sandbox-managed-runtime.test.ts
packages/adapter-utils/src/command-managed-runtime.test.ts
server/src/__tests__/heartbeat-project-env.test.ts
server/src/__tests__/heartbeat-workspace-session.test.ts`
- `pnpm --filter @paperclipai/adapter-utils typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm build`
- Public-hygiene scan of the PR diff and commit messages for internal
issue ids, local paths, private hostnames, and obvious token patterns.
## Risks
- Medium risk: this changes sandbox runtime synchronization semantics
for git-backed workspaces, especially around dirty tracked files,
untracked files, deleted paths, and ignored files.
- The main mitigation is focused test coverage for upload contents,
restore exclusions, and git round-trip behavior.
- The SSH runtime keeps the current bundle-based implementation from
`master`; this PR only aligns shared excludes and sandbox behavior with
that model.
## Model Used
OpenAI Codex, GPT-5-based coding agent, tool-enabled shell/git/GitHub
workflow, with code 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] 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
> - Local adapters (Claude, Codex, Cursor, Gemini, Grok, OpenCode, Pi,
ACPX) ship bundled "skills" — opinionated Markdown prompt bundles
materialized into the agent's runtime
> - One of those bundled skills, `paperclip-dev`, existed to let agents
develop Paperclip itself; it has now moved to its own external repo and
no longer belongs in the core tree
> - The adapter skill model also carried a `required` / `requiredReason`
attribute plus a `paperclip_required` `AdapterSkillOrigin` variant, all
of which only existed to mark bundled skills as non-optional in the UI
and adapter sync logic
> - With `paperclip-dev` gone, no bundled skill is "required" anymore,
and the type / runtime surface for `required` is dead weight — but it is
computed at request time and never persisted, so a clean removal is safe
(no compatibility shim needed)
> - This pull request deletes `skills/paperclip-dev/` and removes every
trace of the `required` / `requiredReason` field and the
`paperclip_required` origin across shared types, validators,
adapter-utils, all eight local adapters, server routes, the
company-skills service, the UI, the storybook fixtures, and the test
suite
> - The benefit is a smaller, simpler adapter-skill surface: one origin
(`company_managed`) for managed bundled skills,
`resolvePaperclipDesiredSkillNames` collapses to "just the configured
desired set", and the AgentDetail skills tab no longer renders a
"Required by Paperclip" section that no longer applies
## Linked Issues or Issue Description
<!-- No existing public GitHub issue; describing the underlying work
inline (feature_request template fields). -->
**Summary**
Remove the bundled `paperclip-dev` skill (now maintained in its own
external repo) and retire the `required` / `requiredReason` skill
attribute and the `paperclip_required` skill origin, which only existed
to support it.
**Problem or motivation**
`paperclip-dev` is the only bundled skill that was ever marked
"required". Now that it lives in a separate repository, shipping it
inside the core tree is wrong, and the entire `required` surface (a type
field, a validator field, a synthesized `paperclip_required` origin, UI
"Required by Paperclip" section, and required-skill merging in the
desired-skills calculation) becomes dead weight. The `required` value is
computed at request time and never persisted, so it can be removed
cleanly without a migration or compatibility shim.
**Proposed solution**
Delete `skills/paperclip-dev/`, drop the `required` / `requiredReason`
fields and `paperclip_required` origin everywhere they are produced or
consumed, collapse managed-skill origin to a single `company_managed`
value, and simplify `resolvePaperclipDesiredSkillNames` to return only
the configured desired set.
**Alternatives considered**
Keeping the `required` attribute as a no-op for forward compatibility —
rejected because it is request-time only (nothing persists it), so
leaving it in place is pure dead surface area with no callers.
**Roadmap alignment**
Internal cleanup / dead-code removal that simplifies the adapter-skill
surface; it does not introduce or duplicate any planned core feature in
ROADMAP.md.
## What Changed
- Deleted bundled `skills/paperclip-dev/` (moved to a separate repo).
- Dropped `required`, `requiredReason`, and the `paperclip_required`
origin from `packages/shared/src/types/adapter-skills.ts`,
`packages/shared/src/validators/adapter-skills.ts`, and
`packages/adapter-utils/src/types.ts`.
- In `packages/adapter-utils/src/server-utils.ts`: removed
`readSkillRequired()`; dropped `required`/`requiredReason` from
`listPaperclipSkillEntries()`,
`normalizeConfiguredPaperclipRuntimeSkills()`,
`buildPersistentSkillSnapshot()`, and `PaperclipSkillEntry`; collapsed
`buildManagedSkillOrigin()` to always return `company_managed`;
simplified `resolvePaperclipDesiredSkillNames()` to return only the
configured desired set (signature preserved so adapter call sites are
untouched).
- Walked all eight local adapters (`acpx-local`, `claude-local`,
`codex-local`, `cursor-local`, `gemini-local`, `grok-local`,
`opencode-local`, `pi-local`) and removed every remaining
`requiredReason` / `paperclip_required` reference.
- `server/src/services/company-skills.ts`: dropped the `required =
sourceKind === "paperclip_bundled"` synthesis when listing runtime skill
entries.
- `server/src/routes/agents.ts`: removed required-skill merging from the
desired-skills calculation in the persist-config path and the
unsupported-snapshot path (keeping the current version-aware
`desiredSkillEntries` structure).
- `ui/src/pages/AgentDetail.tsx`: dropped required-based filters, the
required tooltip, and the entire "Required by Paperclip" section from
the agent skills tab; storybook fixtures in
`ui/storybook/stories/acpx-local.stories.tsx` cleaned up to match.
- Tests: deleted the `required: false` case in
`paperclip-skill-utils.test.ts` and the "keeps required bundled skills
installed" case in every `*-local-skill-sync.test.ts`;
`acpx-local-execute.test.ts`, `cursor-local-execute.test.ts`,
`cursor-local-skill-sync.test.ts`, `agent-skills-routes.test.ts`, and
`packages/adapter-utils/src/server-utils.test.ts` were updated to drop
removed fields and map `origin: "paperclip_required"` →
`"company_managed"`.
- `server/src/adapters/registry.ts`: two `as unknown as
ServerAdapterModule["..."]` casts on `hermesListSkills` /
`hermesSyncSkills` (matching the existing `executeHermesLocal` pattern).
`hermes-paperclip-adapter@0.2.0` still depends on the published
`@paperclipai/adapter-utils` which keeps the retired
`paperclip_required` variant; the cast bridges the
workspace-vs-published type mismatch at the registry seam and can drop
once hermes upgrades.
## Verification
Run from the workspace root:
```sh
grep -rn "skills/paperclip-dev" .
grep -rn "paperclip_required" --include="*.ts" --include="*.tsx" .
grep -rn "requiredReason" --include="*.ts" --include="*.tsx" .
pnpm -w typecheck
pnpm --filter @paperclipai/server exec vitest run paperclip-skill-utils
pnpm --filter @paperclipai/server exec vitest run skill-sync
```
The first three greps return only the explanatory comment in
`server/src/adapters/registry.ts` (no live `paperclip_required` /
`requiredReason` usage) and zero `skills/paperclip-dev` source hits.
Locally:
- `pnpm -w typecheck` → all packages this PR touches pass
(adapter-utils, shared, server, ui, cli, and the
cursor/gemini/opencode/pi adapters).
- Affected vitest suites pass: `paperclip-skill-utils`, `server-utils`,
all eight `*-local-skill-sync`, `agent-skills-routes`, and the
`acpx`/`cursor`/`pi` execute suites.
## Risks
- Behavioral shift in the agent skills UI: the "Required by Paperclip"
section disappears. No bundled skill is required anymore, so this only
affects environments that previously surfaced `paperclip-dev` as a
forced-on row; those installs will see the skill move into the regular
"company-managed" list (and be uninstalled on next sync unless
explicitly listed as desired).
- Existing agents may still have the string `"paperclip-dev"` in their
persisted `desiredSkills`. That entry is inert (no source for it to
install from); a one-time DB cleanup is out of scope. Low risk.
- Hermes adapter type bridge: two casts in `registry.ts` paper over a
type-only divergence between the workspace `@paperclipai/adapter-utils`
and the published version still pinned by
`hermes-paperclip-adapter@0.2.0`. Runtime behavior is unaffected because
the retired `paperclip_required` value is no longer produced by anything
in this tree. The casts can be removed once hermes upgrades its
dependency.
## Model Used
- Provider: Anthropic
- Model: Claude Opus 4.7 (`claude-opus-4-7`)
- Capability: agent tool use via Paperclip's `claude_local` adapter
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The `codex_local` adapter runs `codex exec` as a child process and
streams JSONL events from its stdout
> - NEE-79 caught a real-world `codex_local` orphan: codex sat in
`read()` on stdin for 1h+, no JSON events emitted past startup, no LLM
call in flight. The adapter had no inactivity timer; the only safety net
was the platform-level 1h silent-run detector
> - This is precisely the failure shape NEE-80 scoped: an
adapter-detected fault that should be killed *by the adapter* and
surfaced as `failed`, not waited out for an hour
> - This pull request adds an output-inactivity watchdog inside the
codex-local adapter that resets on every parsed JSONL event from stdout,
kills the child via SIGTERM → 5s grace → SIGKILL when it fires, and
resolves the run with a structured watchdog failure
> - The benefit is that NEE-79-class hangs shorter than 1h stop reaching
the platform-level safety net — they fail fast and visibly at the
adapter layer, with diagnostic logs that don't require host shell access
## Linked Issues or Issue Description
No existing GitHub issue covers this; describing the underlying bug
in-PR per the bug report template
(`.github/ISSUE_TEMPLATE/bug_report.yml`):
- **What happened?** A `codex_local` run sat in `read()` on stdin for
over an hour: codex emitted its startup JSONL events, then nothing — no
further events, no LLM call in flight, no exit. The adapter kept the
child alive indefinitely; the run was only reaped by the platform-level
1h silent-run safety net, an hour after it had effectively died.
- **Expected behavior:** The adapter should detect that its child has
stopped producing output long before the platform-level safety net, kill
it, and surface the run as `failed` with a diagnostic that explains what
happened.
- **Steps to reproduce:** Run any `codex_local` issue where `codex exec`
hangs after startup (e.g. codex blocks reading stdin and never emits
another JSONL event). Observe the run stays alive until the 1h platform
safety net fires.
- **Paperclip version or commit:** reproduced on master prior to this
branch.
- **Agent adapter(s) involved:** codex_local.
Related PRs found while searching for duplicates (none implement an
event-aware inactivity watchdog inside `codex_local`):
- #4004 — generic idle + wall watchdogs for `runChildProcess` in
adapter-utils; complementary, operates below the JSONL parse layer and
is not codex-event-aware
- #4742 — fail-fast on codex-local *startup* hang; this PR covers the
post-startup hang class
- #6861 — zombie-run termination for codex-local; reaping after exit,
not inactivity detection
- #7811 — the analogous output-idle timeout for grok-local
## What Changed
- `packages/adapters/codex-local/src/server/watchdog.ts` *(new)* — pure
watchdog primitive with injectable timers/clock:
`resolveCodexInactivityTimeout`, `createCodexInactivityWatchdog`,
`formatWatchdogErrorMessage`. Default `7 * 60_000` ms; honors `null` as
the disabled escape hatch
- `packages/adapters/codex-local/src/server/execute.ts` — `runAttempt`
now wraps `onSpawn` to capture `pid`/`processGroupId`, feeds stdout
chunks through `noteStdoutChunk`, and on watchdog fire sends SIGTERM to
the process group, schedules SIGKILL after 5s, and returns an
`AdapterExecutionResult` with `exitCode: null`, `signal:
SIGTERM|SIGKILL`, `errorMessage: "watchdog: no codex output for {N}m
{S}s"`, `errorCode: "codex_output_inactivity_watchdog"`. With
`errorMessage` set and `timedOut: false`, `heartbeat.ts:5860` maps the
run to `outcome === "failed"` (not `cancelled`)
- `packages/adapters/codex-local/src/index.ts` — `agentConfigurationDoc`
documents `outputInactivityTimeoutMs` (number ms; `null` disables;
non-positive falls back to default with a warning log at spawn)
- `packages/adapters/codex-local/src/server/watchdog.test.ts` *(new)* —
13 tests covering acceptance criteria 2 and 3 plus supporting cases:
fires after silence, no-fire across 12× (threshold − 1s) cycles,
multi-event chunks, non-JSON ignoring, single-fire idempotency,
formatter shape, full resolution table, `null` → disabled
-
`packages/adapters/codex-local/src/server/watchdog.integration.test.ts`
*(new)* — real Node subprocess that prints one JSONL event then sleeps;
`runChildProcess` reaps it within `threshold + 6s`; signal is SIGTERM or
SIGKILL; `parsedEventCount === 1` (acceptance criteria 1 and 4)
## Verification
```
$ pnpm --filter @paperclipai/adapter-codex-local typecheck
> tsc --noEmit
# clean
$ pnpm --filter @paperclipai/adapter-codex-local exec vitest run
✓ src/server/quota-spawn-error.test.ts (1 test)
✓ src/server/codex-home.test.ts (3 tests)
✓ src/server/codex-args.test.ts (3 tests)
✓ src/server/watchdog.test.ts (13 tests)
✓ src/server/parse.test.ts (9 tests)
✓ src/server/execute.remote.test.ts (4 tests)
✓ src/ui/parse-stdout.test.ts (3 tests)
✓ src/ui/build-config.test.ts (1 test)
✓ src/server/watchdog.integration.test.ts (1 test)
Test Files 9 passed (9)
Tests 38 passed (38)
```
Acceptance criteria check:
1. ✅ Simulated child emits one event then sleeps → killed at threshold;
result `errorMessage` matches `watchdog: no codex output for {N}m {S}s`
(`watchdog.integration.test.ts`)
2. ✅ Child emits events every (threshold − 1s) → not killed
(`watchdog.test.ts: "does not fire when events arrive every (threshold -
1s)"`)
3. ✅ `outputInactivityTimeoutMs: null` disables the watchdog
(`resolveCodexInactivityTimeout` returns `disabled`; `execute.ts` skips
watchdog construction and logs a startup warning)
4. ✅ Real subprocess reaped well within `threshold + 6s` — 250 ms
threshold, 290 ms wall clock in CI
5. ✅ Adapter-level fault → `outcome === "failed"` per
`heartbeat.ts:5860`. NEE-79-class hangs <1h get caught at the adapter,
not the platform-level safety net
Post-rebase verification (head `2a8703fab`, rebased onto master
`69a368ed5`):
```
$ pnpm --filter @paperclipai/adapter-codex-local typecheck # clean
$ pnpm --filter @paperclipai/adapter-codex-local exec vitest run
Test Files 11 passed (11)
Tests 64 passed (64)
```
## Risks
- Low risk. Behind a default-on watchdog that only fires after 7m of
zero parsed JSON events. Operators can disable it with
`outputInactivityTimeoutMs: null` for known-slow tasks
- The kill path reuses the same `process.kill(-pgid, signal)` pattern
that `runChildProcess` already uses for its terminal-result cleanup, so
signal semantics match the existing code path
- `timedOut: false` is preserved on watchdog fire — the platform-level
timeout outcome is unchanged, only the `failed`-vs-success
classification flips. No behavioral shift for already-failing runs
- Sandbox/SSH execution targets: the watchdog fires and emits the
structured log, but the kill is best-effort because remote pids aren't
owned by this process. The platform-level 1h safety net still applies.
Out of scope for NEE-81 by design
## Model Used
- Provider: Anthropic Claude
- Model: claude-opus-4-7
- Mode: Claude Code (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
- [ ] 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
(`agentConfigurationDoc` for `outputInactivityTimeoutMs`)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (re-running on the rebased head)
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(P2 addressed; review threads resolved)
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Neeraj Kumar Singh <b.nirajkumarsingh@hotmail.com>
Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Environments are configured through a JSON-schema-driven form
(`JsonSchemaForm`), and each sandbox provider (here, Daytona) supplies a
manifest describing its fields
> - In the Daytona environment config form, the "memory" field rendered
as a free-text input; on save the value round-tripped to `0`, producing
a server-side "number must be greater than zero" error even when the
user typed a valid number like `2`
> - Rather than patch the free-text input, memory is now a true dropdown
of the sandbox sizes Daytona actually supports, so an invalid value can
no longer be typed or coerced
> - The dropdown leads with a blank "None" row that is selected by
default (meaning "not configured — use Daytona's defaults"); `0` is
never offered because it is not a valid configuration
> - The benefit is that operators pick a valid memory size from a
constrained list, it saves correctly as an integer, and leaving it unset
cleanly omits the field instead of submitting `0`
## Linked Issues or Issue Description
No public GitHub issue exists; describing the bug inline per the bug
report template.
### What happened?
In the Daytona environment configuration form, the memory field is a
free-text input labelled "gigabytes of RAM". Entering a value such as
`2` and saving coerced the value to `0`, and the server rejected the
save with "the number must be greater than zero". There was no way to
enter a valid memory size through the form.
### Expected behavior
Selecting a valid memory size (e.g. `2`) keeps that value and saves
cleanly as an integer. Leaving memory unset is valid and submits no
value (Daytona defaults apply). `0` is never selectable.
### Steps to reproduce
1. Open the environment configuration form for a Daytona sandbox
provider.
2. In the memory field, type `2`.
3. Click Save.
4. Observe the value becomes `0` and the form errors with "the number
must be greater than zero".
## What Changed
- Daytona manifest: `memory` is now an `enum` of the supported sandbox
sizes `[1, 2, 4, 8]` (GiB). It stays optional, so "not configured"
remains valid. `0` is not in the list.
- `JsonSchemaForm` `EnumField`: optional enums now render a leading
blank **None** row that is selected by default when no value is set,
letting the user express "not configured" and clear a previous selection
(Radix `Select` forbids an empty-string item value, so the unset state
maps to a sentinel that translates back to `undefined`).
- `JsonSchemaForm` `EnumField`: when every enum option is numeric, the
selected value is coerced back to a number on change so the payload
keeps the schema's integer type (a stringified `"2"` would otherwise
fail server-side integer validation — this is what fixes the original
bug for the dropdown path).
- Tests: added `EnumField` coverage in `JsonSchemaForm.test.tsx` (blank
row present, no `0`, blank selected by default, numeric coercion, blank
→ unset) and Daytona manifest coverage in the plugin test (memory enum
is `[1,2,4,8]`, excludes `0`, stays optional).
## Verification
- `cd ui && npx vitest run src/components/JsonSchemaForm.test.tsx
src/pages/CompanyEnvironments.test.tsx` → 16/16 passing (14 + 2).
- `vitest run` in `packages/plugins/sandbox-providers/daytona` → 19/19
passing (incl. 3 new manifest tests).
- `cd ui && npx tsc --noEmit` → clean.
- Behavior: the memory field now renders as a dropdown showing **None /
1 / 2 / 4 / 8**, with **None** selected by default. Picking `2` stores
the integer `2`; picking **None** clears the field so it is omitted from
the payload (no `0`, no "must be greater than zero" error).
## Risks
- Low risk. The blank-row + numeric-coercion changes live in
`EnumField`. The blank row is only added for **optional** enums
(required enums are unaffected); numeric coercion only triggers when
every option is numeric, so existing string enums (`egressMode`,
`backend`, `sessionStrategy`) are unchanged. The manifest change is
scoped to the Daytona provider.
## Model Used
Claude (Anthropic), Opus-class model, used via the Paperclip agent
workflow (author + reviewer agents) 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 (none found)
- [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
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have 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
> - The `codex_local` adapter isolates local Codex runs by assigning
managed `CODEX_HOME` state per company and per agent
> - PR #8272 tightened that isolation, but it left a gap: once
`CODEX_HOME` became explicit, the adapter treated it like a user-managed
override and skipped auth seeding
> - That meant newly isolated agents could launch with no usable
`auth.json`, hit OpenAI unauthenticated, and fail with `401 Missing
bearer`
> - Users who had already persisted one of those broken managed homes
could remain stranded even after config changes unless Paperclip
repaired the home itself
> - This pull request teaches Paperclip to seed managed homes correctly,
backfill already-stranded managed homes on startup, and reject
credential-less managed homes before they reach the provider
> - The benefit is that affected managed `codex_local` agents recover
automatically after upgrade and restart, without manual `CODEX_HOME`
surgery
## Linked Issues or Issue Description
- Fixes#497
- Refs #5028
- Related PR: #8272
- Related PR: #8399
## What Changed
- Distinguished Paperclip-managed `CODEX_HOME` paths from genuine
external overrides and always seeded auth into managed homes, even when
`CODEX_HOME` is explicit in config.
- Wrote API-key-backed `auth.json` files for managed homes when
`OPENAI_API_KEY` is configured, otherwise symlinked the shared Codex
auth for subscription/OAuth flows.
- Added a startup reconciliation pass that backfills already-isolated
managed homes created by the broken release so upgrade plus restart
repairs stranded agents automatically.
- Preserved previously resolved API-key auth when the stored
`OPENAI_API_KEY` binding is secret-backed and startup cannot resolve the
secret value directly.
- Hardened the managed-home preflight to require a credential-bearing
`auth.json`, not just file presence, and documented the recovery
behavior.
- Added regression tests covering managed-home seeding, fail-fast
behavior, and server-side startup reconciliation.
## Verification
```bash
pnpm exec vitest run packages/adapters/codex-local/src/server/codex-home.test.ts packages/adapters/codex-local/src/server/execute.auth.test.ts server/src/__tests__/codex-auth-reconciliation.test.ts server/src/__tests__/codex-local-execute.test.ts server/src/__tests__/server-startup-feedback-export.test.ts
pnpm --filter @paperclipai/adapter-codex-local typecheck
pnpm --filter @paperclipai/server typecheck
pnpm check:tokens
pnpm exec vitest run server/src/__tests__/server-startup-feedback-export.test.ts
```
GitHub Actions `PR` workflow is green on latest head `e5b1e08d4`,
including policy, typecheck, test shards, build, e2e, serialized server
suites, and canary dry run. Greptile Review is green on latest head with
0 comments added. No UI changes.
## Risks
- Startup reconciliation now mutates persisted managed Codex homes at
boot. Risk is low because it only touches Paperclip-managed
company/agent home paths and no-ops when a home already has usable auth.
- Genuine external `CODEX_HOME` overrides remain intentionally
self-managed, so those users still own repair steps inside their custom
home.
- Hosts with neither shared Codex auth nor an explicit per-agent API key
now fail earlier with a clearer adapter error instead of surfacing a
downstream `401`, which changes timing but not capability.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in a local Codex session; exact
served model ID/context window were not exposed to the session. Tool use
and code execution were 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 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
- [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 claude-local adapter launches the Claude CLI inside execution
environments (local, SSH, and ephemeral sandboxes such as Daytona)
> - Newer adapter code passes `--effort` to the CLI, but the Claude
binary baked into some sandbox images is older and rejects it with
`error: unknown option '--effort'`, so every run in those environments
fails
> - This needs addressing because the failure is environment-dependent
and silent from the operator's perspective — the run just dies with a
CLI usage error
> - This pull request probes the in-sandbox CLI for `--effort` support
once, caches the result per environment, and strips the flag (with a
warning) when the CLI does not support it
> - The benefit is that sandboxes with older Claude CLIs keep working
instead of failing, with negligible probe overhead because the
capability check is cached and reused across ephemeral leases
## Linked Issues or Issue Description
No public GitHub issue exists. Describing the bug inline following the
bug report template:
### What happened?
Runs using the `claude-local` adapter inside certain sandbox/execution
environments fail with `error: unknown option '--effort'`. The adapter
unconditionally appends `--effort` to the Claude CLI invocation, but the
Claude CLI version present in some sandbox base images predates that
flag, so the process exits with a usage error and the run dies.
### Expected behavior
The adapter should detect that the target environment's Claude CLI does
not support `--effort` and degrade gracefully — drop the flag and emit a
warning — rather than failing the run.
### Steps to reproduce
1. Configure an execution environment (e.g. a sandbox image) whose
bundled Claude CLI is old enough to predate the `--effort` option.
2. Run any claude-local task that resolves to an effort level (so
`--effort` is appended).
3. Observe the run fail immediately with `error: unknown option
'--effort'`.
### Paperclip version or commit
`master` at the time of this PR (branch forked from current `master`).
### Deployment mode
Self-hosted / local instance using execution environments (reproducible
with ephemeral sandbox providers such as Daytona where `reuseLease:
false`).
### Agent adapter(s) involved
Claude Code (`claude-local`).
## What Changed
- Add a CLI capability probe (`cli-capabilities.ts`) that runs the
target Claude binary's `--help` inside the execution environment to
detect `--effort` support.
- Strip `--effort` from the CLI args (emitting a warning) when the probe
reports the flag is unsupported; keep it otherwise.
- Cache probe results keyed by
`sandbox:providerKey:environmentId:command` (no lease id) so the probe
is reused across ephemeral leases — important for `reuseLease: false`
sandbox configs like Daytona, which would otherwise re-probe on every
run.
- Conservative fallback: if the probe itself can't run/parse, assume the
flag is supported (preserves prior behavior).
## Verification
- `node_modules/.bin/vitest run
src/__tests__/claude-local-execute.test.ts
src/__tests__/claude-local-adapter-environment.test.ts` → **2 files, 30
tests passed**.
- Regression test issues two `execute()` calls with distinct lease ids
and asserts the in-sandbox `--help` probe runs exactly once (cache reuse
across leases).
- Added tests covering: flag stripped when unsupported, flag retained
when supported, warning emitted, and conservative fallback when the
probe fails.
## Risks
Low risk. The change is additive and gated behind a probe with a
conservative default (assume supported on probe failure), so existing
environments that support `--effort` are unaffected. Worst case for an
environment where the probe is unreliable is the prior behavior (flag
passed through).
## Model Used
Claude Opus 4.8 (claude-opus-4-8), extended thinking, with tool use /
code execution 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 (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)
- [ ] 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 (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 control plane for running AI-agent companies, so
long-running remote work needs to stay observable to human operators.
> - Cloud / sandbox agents are an active roadmap area, and their
workspace sync path is part of the runtime substrate every remote coding
run depends on.
> - In the sandbox and SSH execution-target flows, Paperclip logged that
sync had started, then often went silent for the full transfer window.
> - That made large remote syncs feel stalled and also hid a real
performance problem in the command-managed sandbox upload path.
> - The first part of this pull request threads a throttled
progress-reporting surface through the adapter execution-target stack so
sync and restore work can emit meaningful updates.
> - The second part fixes the command-managed sandbox transport itself:
it removes the old serial 32KB append bottleneck, but also falls back
away from the single-stream path when a provider-backed sandbox runner
cannot surface mid-flight stdin progress.
> - The result is that sandbox and SSH transfers are both faster and
more observable, including the live Daytona-style sandbox case that
previously only emitted `0%` and `100%`.
## Linked Issues or Issue Description
No public GitHub issue exists for this bug, so it is described inline
below following the bug report template.
### What happened
- Remote sandbox and SSH workspace syncs could spend a long time
transferring data while only logging a start line (`Syncing workspace
and runtime assets to sandbox environment`) and, at best, a terminal
line.
- In the command-managed sandbox path, the original upload
implementation also paid a large performance cost by appending base64
data in many small sequential remote writes (thousands of serial 32KB
round-trips on a large workspace).
- After the initial transport rewrite, live provider-backed sandbox runs
still only emitted `0%` and `100%` because the single-stream stdin RPC
buffered progress until completion.
### Expected behavior
- Long-running sandbox and SSH syncs should periodically report how much
of the transfer is complete (a percentage and/or MB transferred) so an
operator can tell the run is healthy and making progress rather than
stuck.
- The main sandbox upload path should not be artificially slow.
- A transfer that fails partway should leave an explicit failure marker
in the log rather than a dangling intermediate percentage.
### Steps to reproduce
1. Run an agent against a sandbox (command-managed) or SSH
(remote-managed) execution target with a non-trivial workspace.
2. Watch the run log during the workspace/runtime asset sync phase.
3. Observe that the log shows the sync start line and then stays silent
for the full transfer (live provider-backed sandbox runs only show `0%`
then `100%`).
### Paperclip version or commit
- Branch `PAPA-825-provide-status-updates-when-syncing-sandboxes` off
`master`.
### Deployment mode
- Self-hosted / local instance using sandbox (command-managed) and SSH
(remote-managed) execution targets, including provider-backed sandbox
runners.
## What Changed
- Added shared throttled runtime progress reporting and threaded
`onProgress` through the adapter execution-target surface and adapter
`execute.ts` entrypoints.
- Added sync and restore progress reporting for the command-managed
sandbox path and the SSH/remote-managed path, including git
import/export progress where totals are known.
- Reworked command-managed sandbox transfer behavior so uploads use the
faster single-stream path when appropriate, but fall back to chunked
progress-emitting writes when the runner cannot expose mid-stream stdin
progress.
- Marked provider-backed environment sandbox runners as not supporting
single-stream stdin progress so live sandbox runs emit meaningful
intermediate updates instead of only `0%` and `100%`.
- Emit an explicit terminal failure marker (`failed at NN% (x/y MB)`)
when an SSH/tar transfer rejects, so a failed sync no longer leaves a
dangling intermediate percentage in the log.
- Run the SSH sync/restore size estimate (local directory walk / remote
`du` probe) concurrently with the transfer instead of awaiting it before
opening the pipe, so progress instrumentation no longer adds startup
latency proportional to workspace file count.
- Added and extended focused regression coverage for runtime progress
throttling and the new failure marker, command-managed sandbox
transfers, sandbox orchestration, SSH transfer progress, and environment
execution-target wiring.
## Verification
- `pnpm exec vitest run
packages/adapter-utils/src/runtime-progress.test.ts
packages/adapter-utils/src/ssh-fixture.test.ts
packages/adapter-utils/src/command-managed-runtime.test.ts
packages/adapter-utils/src/sandbox-managed-runtime.test.ts`
- `pnpm exec vitest run
packages/adapter-utils/src/command-managed-runtime.test.ts
server/src/__tests__/environment-execution-target.test.ts`
- `npx tsc --noEmit` for `packages/adapter-utils`
## Risks
- The provider-backed sandbox fallback now prefers chunked
command-managed writes when progress hooks are active, so
small-to-medium uploads may trade some raw throughput for observable
intermediate progress on runtimes that cannot surface true mid-stream
stdin progress.
- Progress percentages on tar-based transfers still depend on estimates
in some cases, so operators may briefly see MB-only lines before the
estimate resolves, then near-final clamping before the terminal `100%`
line.
- This PR changes shared execution-target behavior used by multiple
adapters, so regressions would most likely appear in remote runtime
setup/teardown flows rather than in a single adapter.
## Model Used
- Initial implementation: OpenAI GPT-5.4 via Codex local agent
(`codex_local`), high reasoning mode.
- Observability follow-ups (failure marker, concurrent size estimate,
added tests): Claude Opus 4.8 via Claude Code (`claude_local`).
## 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
- [x] 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
- [ ] 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 control plane for AI-agent companies, so execution
environment selection has to stay inspectable and predictable across
companies, agents, and runs.
> - The environment subsystem decides where an agent heartbeat actually
runs and how remote sandbox state is realized and restored.
> - That subsystem previously mixed company-scoped environment catalogs
with issue-level environment stamping, so a reassigned issue could keep
executing in the previous assignee's sandbox.
> - That behavior breaks the control-plane contract: changing the
assignee should change the executing agent/environment path unless there
is an explicit current override.
> - Fixing it cleanly required more than a narrow patch; the environment
model had to move to instance scope with a single inherited default and
per-agent override semantics.
> - This pull request rewires the schema, server/API surface, runtime
resolution, and UI around that model, then adds regression coverage for
cross-company inheritance and per-agent isolation.
> - The benefit is that environment choice now follows the approved
instance/agent configuration path instead of stale issue state, while
shared environments only need to be configured once per instance.
## Linked Issues or Issue Description
- No directly matching public GitHub issue or PR was found while
searching for this refactor.
### What happened?
Reassigning work between agents with different execution environments
could keep running in the previous sandbox because environment choice
was stamped onto the issue and outranked the current assignee. The same
subsystem also forced environment catalogs to be duplicated per company
even though the underlying execution environments were instance-wide
resources.
### Expected behavior
Execution should resolve through the current instance and agent
configuration path, with one instance-scoped environment catalog, one
instance default, optional per-agent override, and no stale issue-level
environment authority surviving reassignment.
### Steps to reproduce
1. Configure two agents to use different execution environments.
2. Assign an issue to the first agent so the issue records execution
state in that environment.
3. Reassign the same issue to the second agent and run another
heartbeat.
4. Observe that the pre-fix runtime can still sync or execute in the
original sandbox instead of the second agent's environment.
### Paperclip version or commit
Current `master` before this PR.
### Deployment mode
Self-hosted server.
### Installation method
Built from source (`pnpm dev` / `pnpm build`).
### Agent adapter(s) involved
- Claude Code
- Not adapter-specific (core bug in environment authority / resolution)
### Database mode
External Postgres.
### Access context
Both board reassignment and agent heartbeats were involved.
## What Changed
- Moved environments and their default selection contract to instance
scope in DB/shared types, including the migration that dedupes legacy
per-company environments and seeds the instance local default.
- Reworked environment CRUD/auth flows to use instance-scoped APIs and
added route/service coverage for instance-level environment management.
- Changed runtime resolution to prefer `agent default -> instance
default -> built-in local`, removed issue-level environment stamping
from the active execution path, and isolated sandbox/plugin leases by
`(executionWorkspaceId, agentId)`.
- Added environment env-var runtime precedence so environment-provided
values act as the baseline for agent execution.
- Moved the environment UI into instance settings and updated agent
configuration surfaces to reflect inherit/override behavior.
- Added regression coverage for instance-default inheritance across
companies and for the new runtime resolution behavior.
- Fixed a rebase-only duplicate `enableTaskWatchdogs` flag regression in
instance settings types/validators/services so the branch typechecks
cleanly on current `master`.
- Updated stale server tests so CI matches the shipped instance-scoped
environment contract.
## Verification
- `git diff --check`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run
server/src/__tests__/environment-runtime-driver-contract.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/environment-routes.test.ts
server/src/__tests__/environment-instance-routes.test.ts
server/src/__tests__/execution-workspace-policy.test.ts
server/src/__tests__/heartbeat-plugin-environment.test.ts
server/src/__tests__/instance-settings-routes.test.ts`
## Risks
- The migration changes environment scope and dedupes existing rows, so
installs with unusual legacy environment combinations should be reviewed
carefully during upgrade.
- Remote execution behavior now depends on instance-default inheritance
semantics instead of issue-level stamping, so any remaining code paths
that still assume issue-scoped environment authority would surface as
follow-up bugs.
- This PR includes both server/runtime behavior and UI relocation, so
reviewers should watch for authorization edge cases around instance
settings and environment management.
> I checked [`ROADMAP.md`](ROADMAP.md). This work fits the existing
Cloud / Sandbox agents direction as a bug-fix/refactor to current
behavior, not a new parallel product surface.
## Model Used
- OpenAI Codex coding agent in this Paperclip/Codex session; GPT-5-class
tool-using model with code execution and shell access. The exact backend
model ID is not exposed to the session runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run through adapters; `@paperclipai/adapter-claude-local`
shells out to the Claude CLI and maps its result JSON into Paperclip's
run outcome (`errorCode` / `errorFamily`)
> - A Fable 5 policy refusal exits the CLI cleanly (`exitCode=0`,
`is_error=false`) with `stop_reason: "refusal"`, which the adapter
mapped to `errorCode: null`
> - Paperclip therefore recorded the run as a silent success, so the
agent's heartbeat stalled with no signal for operators and no hook to
retry or alert
> - This pull request adds a refusal detector to the adapter so a
refusal resolves to a distinct `errorCode: "claude_refusal"`
(`errorFamily: "model_refusal"`)
> - The benefit is that policy refusals become observable: the server
persists the code, operators can see it on the run, and downstream
retry/alert policy can key off it
## Linked Issues or Issue Description
No public GitHub issue exists. Describing the underlying bug in-PR,
following `.github/ISSUE_TEMPLATE/bug_report.yml`:
**What happened?**
When the Claude CLI returns `stop_reason: "refusal"` (a model policy
refusal), it still exits cleanly (`exitCode=0`, `is_error=false`).
`@paperclipai/adapter-claude-local` keyed refusal detection off the
failure flag during error-code resolution, so it returned `errorCode:
null` — a refused run was indistinguishable from a successful one,
recorded by Paperclip as a silent success with no signal to retry or
alert.
**Expected behavior**
A refusal should resolve to a distinct, non-null error code (`errorCode:
"claude_refusal"`, `errorFamily: "model_refusal"`) so the server can
surface it to operators and downstream policy can react.
**Steps to reproduce**
1. Run any agent on the `claude-local` adapter with a prompt the model
refuses on policy grounds.
2. The Claude CLI exits `0` with `is_error=false` and `stop_reason:
"refusal"`.
3. Observe the run resolves `errorCode: null` (pre-fix) instead of a
refusal-specific code.
**Paperclip version or commit**
`adapter-claude-local` v2026.609.0 (`dist/server/execute.js` error-code
resolution block); reproduced on `master`.
**Deployment mode**
Local / self-hosted.
**Agent adapter(s) involved**
`@paperclipai/adapter-claude-local` (Claude Code, local).
## What Changed
- **`packages/adapters/claude-local/src/server/parse.ts`** — new
`isClaudeRefusalResult()` helper, parallel to
`isClaudeMaxTurnsResult()`. Detects `stop_reason` / `stopReason` /
`error_code` / `errorCode` == `refusal` and `subtype` ==
`model_refusal`, case/whitespace tolerant.
- **`packages/adapters/claude-local/src/server/execute.ts`** — compute
the refusal flag independent of the `failed` flag (a refusal exits
cleanly, so keying off `failed` would miss it); resolve `errorCode:
"claude_refusal"` and `errorFamily: "model_refusal"`; surface
`stopReason: "refusal"` in `resultJson`.
- **`packages/adapter-utils/src/types.ts`** — widen
`AdapterExecutionErrorFamily` with `"model_refusal"`.
- **`packages/adapters/claude-local/src/server/index.ts`** — export the
new helper.
- **`packages/adapters/claude-local/src/server/parse.test.ts`** — 7 new
unit tests.
## Verification
```
npx vitest run packages/adapters/claude-local # 35 passed (7 new)
npx tsc --noEmit -p packages/adapter-utils # clean
npx tsc --noEmit -p packages/adapters/claude-local # clean
```
Manual: a result JSON with `stop_reason: "refusal"` on a clean exit now
resolves `errorCode: "claude_refusal"` and `errorFamily:
"model_refusal"`; non-refusal results are unaffected.
## Risks
Low risk. Additive only — it introduces a new error code on a path that
previously returned `null`; no existing error code or success path
changes. `claude_refusal` is deliberately **not** added to the
transient-upstream retry set: a refusal is deterministic, so retrying
the same prompt yields the same refusal. It surfaces as a distinct
non-transient code for operators; downstream retry/alert policy can key
off `claude_refusal` / `errorFamily: model_refusal` later.
## Model Used
Claude Opus 4.8 (`claude-opus-4-8`), extended-thinking mode, run via
Claude Code 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 (no related PRs found)
- [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
- [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 —
internal adapter error-code addition; no user-facing docs)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (this push re-runs the gates;
will confirm once green)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(prior run was 4/5 on a PR-description note now addressed; will confirm
on re-run)
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Full-Stack Engineer <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Local adapters turn Paperclip runs into unattended CLI invocations.
> - The `gemini_local` adapter depends on Gemini CLI behaving
non-interactively in headless worker sessions.
> - When Gemini CLI falls back to browser-based auth, the process can
stall at startup instead of producing stream-json output.
> - The adapter should make headless intent explicit and turn missing
auth into a classified, fast failure.
> - This pull request hardens the Gemini child-process environment and
parser classification around that failure mode.
> - The benefit is that operators get actionable `gemini_auth_required`
failures instead of silent hung runs.
## Linked Issues or Issue Description
No exact public issue exists for this runtime stall.
Related: #2344 covers a Gemini CLI adapter environment/auth probe
failure. This PR addresses a different runtime path: unattended
`gemini_local` executions that can stall when Gemini CLI attempts
interactive browser auth in a headless session.
Bug summary:
- Adapter: `gemini_local`
- Symptom: child Gemini CLI process starts but produces no stream-json
output when auth requires interactive browser flow
- Expected: unattended runs either produce stream-json output or fail
quickly with a classified auth error
- Actual: the run can hang at invocation until an external watchdog
kills it
- Scope: Gemini adapter process env, auth-required parsing, and adapter
docs/tests
Duplicate search performed:
- `gh pr list --state all --search "gemini headless invocation stall"`
found only this PR.
- `gh pr list --state all --search "gemini NO_BROWSER NO_COLOR"` found
only this PR.
- `gh issue list --state all --search "gemini headless authentication"`
found related issue #2344 but no exact duplicate.
## What Changed
- Set a headless-safe Gemini invocation env at the final child-process
boundary: `TERM=xterm-256color`, `COLORTERM=truecolor`, and
`NO_BROWSER=1`.
- Delete inherited `NO_COLOR` from the child env so Gemini CLI keeps
color-capable terminal behavior when deciding whether it can run
non-interactively.
- Classify Gemini `FatalAuthenticationError: Manual authorization is
required...` failures as `gemini_auth_required`.
- Update Gemini adapter docs to describe the non-interactive `--prompt`
path and headless env behavior.
- Add/extend focused parser and remote execution tests for the
auth-required and env invariants.
## Verification
- `pnpm exec vitest run
packages/adapters/gemini-local/src/server/parse.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts` — 23
tests passed.
- `pnpm --filter @paperclipai/adapter-gemini-local typecheck` — passed.
- Local Gemini CLI probe confirmed `NO_BROWSER=1` turns the browser-auth
stall into a fast auth error instead of an interactive wait.
## Risks
Low risk. The change is limited to `gemini_local` invocation env,
parsing, docs, and tests. It does not touch schemas, API routes,
persisted data, or other adapters.
Operational risk: environments that intentionally rely on `NO_COLOR`
being inherited by Gemini child processes will no longer pass that
variable through. That is intentional here because Gemini CLI
auth/headless behavior should not depend on inherited color suppression.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex via Codex CLI, with shell/file-editing tool use for
repository inspection, code edits, tests, and GitHub PR maintenance.
## 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
- [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
/cc @codex — please review.
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents coordinate through issue threads, so issue comments and
interactions are part of the task authorization surface.
> - Low-trust and boundary-limited agents can receive narrow
mention-scoped access, but that must not turn into broad same-company
issue-thread reads.
> - The comment creation path was narrowed to allow explicit mention
replies without granting mutation access.
> - The surrounding list/read routes still needed to enforce the same
`issue:read` boundary before returning thread data.
> - This pull request applies the issue read check to issue comment and
interaction listing routes, and locks that behavior with server
regressions.
> - The benefit is that narrow cross-agent collaboration remains
possible without exposing unrelated issue-thread history.
## Linked Issues or Issue Description
Bug fix:
- What happened: same-company agents outside an issue read boundary
could still hit issue-thread listing routes and receive thread data.
- Expected behavior: issue comments and issue-thread interactions should
only be listed after the actor is allowed to read the issue.
- Steps to reproduce: configure a peer agent denied by the issue read
boundary, then request `GET /api/issues/:id/comments` or the issue
interaction listing route.
- Paperclip version/commit: current `master` before this branch.
- Deployment mode: applies to server authorization in all modes.
Related public context: #7389, #7863, #8024.
## What Changed
- Added issue read enforcement before listing issue comments.
- Added issue read enforcement before listing issue-thread interactions.
- Added server regressions for denied peer-agent issue-thread access
while preserving mention-scoped collaboration behavior.
- Removed an avoidable per-mentioned-comment issue reload in
mention-grant authorization by passing the already-loaded issue assignee
through the helper.
- Documented cross-agent issue read/comment authorization behavior in
the Paperclip API reference.
- Added a resilient fallback for pinned external skills when GitHub tree
fetches are temporarily unavailable during catalog builds.
## Verification
- `pnpm vitest run
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/authorization-service.test.ts`
- 2 test files passed
- 84 tests passed
- Existing mocked recovery revalidation warnings were emitted by the
route suite and the command exited 0
- Greptile: 5/5 on commit `b73fc323f192dc44f88374c16865008d4348923b`; no
unresolved review threads.
- `pnpm vitest run packages/skills-catalog/src/catalog-builder.test.ts`
- 1 test file passed
- 6 tests passed
- CI: all visible PR checks are terminal green on commit
`b73fc323f192dc44f88374c16865008d4348923b`.
## Risks
Low risk. This tightens read authorization on issue-thread listing
routes; any caller that depended on same-company access without
`issue:read` will now receive 403 and must use an explicit grant or
valid issue read path.
## Model Used
OpenAI GPT-5 Codex, Codex coding agent environment, tool use and local
command execution enabled, reasoning mode active. Context window 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 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
- [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
- [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 task lifecycle and recovery subsystems decide when agent work is
still productive, stalled, or ready for review.
> - Existing recovery paths can observe stopped or incomplete work, but
there was no first-class per-task watchdog model with scoped review
permissions.
> - Watchdog follow-ups also need strict boundaries so
recovery/status-only runs cannot mutate approvals or perform deliverable
work.
> - This pull request adds the task watchdog data model, API/service
layer, scheduler/review flow, adapter wake context, UI configuration
surfaces, and docs.
> - The branch has been rebased onto current `paperclipai/paperclip`
`master`; the watchdog migration is now ordered after master's latest
migrations as `0104_issue_watchdogs`.
> - The benefit is a more explicit task-review loop that preserves
Paperclip's single-assignee and governance invariants while making
stalled work easier to route.
## Linked Issues or Issue Description
No linked GitHub issue. Paperclip task:
[PAP-11275](/PAP/issues/PAP-11275).
## Problem or motivation
Task recovery needs a first-class watchdog path that can inspect stopped
work and create scoped follow-ups without bypassing normal task
ownership. Board/UI users need a way to configure watchdogs on tasks and
see watchdog-related live work. Recovery/status-only runs must remain
limited to status reporting and must not create approvals, link
approvals, or submit approval comments.
## Proposed solution
Add a task-watchdog data model, scheduler/classifier, scoped mutation
guard, adapter wake context, API/UI configuration surfaces, and
documentation so watchdog agents can review stopped task subtrees under
explicit boundaries.
## Alternatives considered
Reuse the existing recovery-action flow only. That would keep
stopped-work detection implicit, make per-task watchdog assignment
harder to expose in the UI, and would not provide a durable
scoped-review issue for stalled task trees.
## Roadmap alignment
This is Paperclip control-plane lifecycle infrastructure for task
execution and recovery. I checked `ROADMAP.md`; this PR does not
duplicate an existing planned core item.
## What Changed
- Added issue watchdog schema, migration, shared contracts, validators,
CRUD API, and service support.
- Added task watchdog scheduler/classifier behavior, scoped mutation
enforcement, adapter wake context, and default watchdog mandate
guidance.
- Added UI surfaces for configuring watchdogs on new/existing tasks,
viewing watchdog activity, and exposing the experimental setting.
- Added docs for the user-facing task watchdog workflow and
implementation semantics.
- Gated new-task watchdog setup behind `enableTaskWatchdogs` and blocked
cheap status-only recovery runs from approval mutations.
- Rebased onto current `master` and renumbered the idempotent watchdog
migration from the branch-local `0102_issue_watchdogs` slot to
`0104_issue_watchdogs`.
- Addressed Greptile feedback by loading watchdog classifier input with
a recursive subtree query and centralizing the watchdog origin-kind
constant.
- Added and updated focused server/UI tests for watchdog routes,
scheduler/classifier behavior, scope boundaries, live task visibility,
settings, and new issue dialog behavior.
## Verification
- `pnpm vitest run server/src/__tests__/task-watchdogs-scheduler.test.ts
server/src/__tests__/task-watchdogs-classifier.test.ts`
- `pnpm vitest run
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts`
- `pnpm vitest run ui/src/components/NewIssueDialog.test.tsx`
- `pnpm --filter @paperclipai/server typecheck`
- `git diff --check`
- Verified the PR diff does not include `pnpm-lock.yaml` or
`.github/workflows`.
## Risks
- Medium risk: this introduces a new task lifecycle surface touching DB
schema, server routes/services, adapter wake context, and UI task
configuration.
- Watchdog scheduling behavior depends on the new experimental setting
and runtime context checks behaving consistently across local and
production agents.
- The watchdog migration is idempotent (`IF NOT EXISTS` /
duplicate-object guards) so users who tried the previous branch-local
migration number should not get duplicate-object failures.
- CI and the second Greptile pass are pending after the latest
review-fix push.
> 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 the Paperclip workspace. Exact
runtime model id and context window were not exposed to the agent; tool
use and local command execution were 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
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots — N/A per Paperclip task instruction: do not add
screenshots/images to this PR unless they are specifically part of the
work.
- [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>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Issue work mode controls how a task starts and how the conversation
composer frames the operator's intent.
> - Paperclip already supports standard agent execution and planning
mode, but there is no lightweight mode for asking a question without
immediately implying execution or plan drafting.
> - That gap makes low-commitment clarification workflows look like
normal task execution.
> - This pull request adds an explicit Ask mode and threads it through
shared contracts, server heartbeat context, and the issue composer UI.
> - The benefit is that operators can create or switch a task into a
question-oriented mode while preserving existing agent and planning
flows.
## Linked Issues or Issue Description
No public GitHub issue exists for this change. Inline feature request
follows the repository feature request template.
### Subsystem affected
Cross-cutting: `packages/shared`, `server/`, and `ui/`.
### Problem or motivation
Issue conversations currently distinguish standard agent work from
planning work, but question-first conversations do not have a clear
public mode in the shared contract or UI. Operators who want to ask an
agent a focused question have to use standard mode, which can imply
normal task execution, or planning mode, which asks for a plan rather
than an answer.
### Proposed solution
Add Ask as a first-class issue work mode. It should be selectable from
issue creation and issue chat, cycle alongside Standard and Planning
from the keyboard shortcut/menu, appear distinctly in composer styling,
and be included in heartbeat context so agents know to answer directly
instead of executing or drafting a plan.
### Alternatives considered
- Keep using standard mode for questions: rejected because it does not
communicate answer-only intent to the agent or the UI.
- Reuse planning mode for questions: rejected because planning mode asks
for a plan and is semantically different from asking a question.
- Add only local UI copy: rejected because the mode needs to be
represented in the shared contract and server heartbeat context to be
reliable.
### Roadmap alignment
This is a focused issue-workflow improvement. `ROADMAP.md` was checked
and no duplicate planned core work was found.
### Additional context
Related public searches performed before opening this PR:
- GitHub PR search for `"ask mode" repo:paperclipai/paperclip`
- GitHub issue search for `"ask mode" repo:paperclipai/paperclip`
- GitHub PR search for `"work mode" "ask" repo:paperclipai/paperclip`
No duplicate PR was found.
## What Changed
- Added `ask` to the shared issue work-mode contract and validation
coverage.
- Included issue work mode in heartbeat context summaries so agents can
see standard, planning, and ask state.
- Added Ask mode metadata, styling, composer tone handling, and
selection/cycling behavior in the issue chat/new issue UI.
- Updated focused tests for shared validators, heartbeat context, and
affected UI work-mode flows.
## Verification
- `NODE_ENV=test pnpm exec vitest run
ui/src/components/ChatComposer.test.tsx
ui/src/components/IssueChatThread.test.tsx
ui/src/components/NewIssueDialog.test.tsx
ui/src/lib/work-mode-meta.test.ts`
- `NODE_ENV=test pnpm exec vitest run
packages/shared/src/validators/issue.test.ts
server/src/__tests__/heartbeat-context-summary.test.ts
server/src/__tests__/issues-service.test.ts
ui/src/components/ChatComposer.test.tsx
ui/src/components/IssueChatThread.test.tsx
ui/src/components/NewIssueDialog.test.tsx
ui/src/lib/work-mode-meta.test.ts ui/src/pages/IssueDetail.test.tsx`
The broader targeted command passed 8 test files / 245 tests.
Visual reference for Standard/Planning/Ask composer states:
https://gist.github.com/cryppadotta/714d8590bac55500a65e7e16de5bb4b8
It emitted an expected warning from an existing server test fixture
about a missing run-log fixture while verifying derived issue comment
metadata.
## Risks
Low to moderate risk. This adds a new enum value that crosses shared,
server, and UI contracts. Existing standard and planning modes are
preserved, but any downstream code assuming only two non-terminal work
modes may need to handle `ask`.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex coding agent in Paperclip CodexCoder mode, with
shell, git, GitHub connector, and local test execution tools. Context
window and exact hosted model snapshot are not exposed in this runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`,
`feat/...`) 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
- [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 control plane people use to run and
supervise AI-agent companies.
> - The skills system is part of that core operator experience because
agents and humans both depend on the catalog-backed Skills Manager
surfaces.
> - In source checkouts, the server can find the catalog manifest and
bundled skill files through monorepo-relative paths, but published
installs do not preserve that layout.
> - That mismatch makes `GET /api/skills/catalog` fail in npm/pnpm
installs even though the catalog package itself is present.
> - The server therefore needs to resolve the catalog from the published
`@paperclipai/skills-catalog` package first, while still keeping a
monorepo fallback for local development.
> - This pull request makes the published package the primary resolution
path, uses the same resolved package root for bundled skill file reads,
and degrades the list route safely when the manifest is unavailable.
> - The benefit is that Skills Manager catalog reads behave correctly in
packaged installs instead of only in repo-local development layouts.
## Linked Issues or Issue Description
Fixes#8316
Refs #7281
Refs #7313
Refs #7350
Refs #7860
Refs #8223
Refs #8227
## What Changed
- Added `@paperclipai/skills-catalog` as a runtime dependency of
`@paperclipai/server`.
- Exported `./package.json` from `@paperclipai/skills-catalog` so the
server can resolve the published package root directly.
- Updated `server/src/services/skills-catalog.ts` to resolve the
manifest and package root from the published package first, with the
monorepo path retained only as a development fallback.
- Applied that resolved package root to bundled catalog file reads so
manifest lookup and skill-file reads use the same published layout.
- Added `listCatalogSkillsOrEmpty()` so `GET /api/skills/catalog`
returns `[]` and logs a warning when the manifest is unavailable instead
of surfacing a 500.
- Added targeted server tests for published-package resolution and
missing-manifest fallback handling.
## Verification
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/skills-catalog-service.test.ts
src/__tests__/company-skills-routes.test.ts`
- `pnpm --filter @paperclipai/server build`
- Packaging smoke:
- pack `@paperclipai/shared` and `@paperclipai/skills-catalog` from this
checkout
- mount those packed artifacts under `server/dist/node_modules`
- import `server/dist/services/skills-catalog.js`
- verify a bundled catalog `SKILL.md` resolves and reads successfully
from the packed package layout
## Risks
- Low risk: the change is narrowly scoped to catalog package resolution
and fallback behavior.
- The new `./package.json` export slightly broadens the catalog
package's public surface, so reviewers should confirm that is an
acceptable runtime contract.
- The empty-array fallback intentionally changes failure mode for a
missing manifest from `500` to a warning + empty payload, which is safer
for packaged installs but could hide packaging regressions if logs are
not monitored.
## Model Used
- OpenAI Codex via Paperclip `codex_local` (GPT-5-based coding agent;
exact backend model ID/context window not exposed in this harness), with
tool use, shell execution, git, and local test/build verification.
## 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
- [x] 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
- [ ] 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 control plane people use to create, configure, and
run AI agents for work.
> - This change sits in the server-side agent lifecycle and
secret-binding subsystem, where adapter config `env` entries can
reference company secrets.
> - An incident (while trying to configure a Novita sandbox) showed that
an agent can reach a broken runtime state if `adapterConfig.env`
contains `secret_ref` entries but the matching `company_secret_bindings`
rows are missing.
> - The immediate run-path guard and error-surfacing work made the
failure diagnosable, but they did not fully prevent new broken agents
from being created.
> - The risk came from create and approval flows being responsible for
remembering to sync bindings at each call site, which is easy to miss as
new flows are added.
> - This pull request moves the invariant into `agentService`
create/update/activate paths, keeps the existing hire-flow fix, and adds
regression coverage for create, update, and legacy pending-approval
recovery.
> - The benefit is that agent secret binding integrity is enforced
closer to the data mutation point, so future callers inherit the
protection automatically.
## Linked Issues or Issue Description
Refs #8309
### What happened?
A Paperclip agent could persist `adapterConfig.env` `secret_ref` entries
without matching agent-scoped `company_secret_bindings` rows. When that
happened, the config UI could still look configured, but the real run
path failed pre-dispatch because the secret was not actually bound to
that agent.
### Expected behavior
Every normal agent create, config-update, and pending-approval
activation flow should leave the agent with secret bindings that match
its persisted secret-ref env config.
### Steps to reproduce
1. Create or activate an agent through a flow that persists
`adapterConfig.env` secret refs without synchronizing
`company_secret_bindings`.
2. Observe that the config state can still appear populated.
3. Start a run for that agent.
4. Observe that pre-dispatch binding validation fails because the secret
reference exists but the agent binding does not.
### Deployment mode
Local dev (`pnpm dev`)
### Installation method
Built from source (`pnpm dev` / `pnpm build`)
### Agent adapter(s) involved
- Claude Code
- Not adapter-specific (core bug)
### Database mode
Embedded PGlite / embedded local dev database flow
### Access context
Board (human operator) created or approved the agent; agent runtime
later consumed the config.
### Additional context
This PR focuses on preventing new broken states from normal service
flows and on backfilling the covered legacy pending-approval activation
path.
## What Changed
- Kept the existing branch-local hire-flow fix that synchronized
bindings for route and approval paths.
- Moved the binding integrity invariant into `agentService.create()`,
`agentService.update()` when `adapterConfig` changes, and
`agentService.activatePendingApproval()`.
- Added `server/src/__tests__/agents-service-secret-bindings.test.ts`
covering create-time sync, update-time resync, and backfill for legacy
pending-approval agents.
- Removed now-redundant route-layer and approval-layer binding sync
calls once the service layer became authoritative.
- Simplified the affected unit tests so route/approval tests no longer
assert service-owned binding writes directly.
## Verification
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run
server/src/__tests__/agents-service-secret-bindings.test.ts
server/src/__tests__/approvals-service.test.ts
server/src/__tests__/agent-skills-routes.test.ts`
## Risks
- Low to medium risk.
- This changes where secret-binding synchronization is enforced, so any
unexpected caller that relied on upper-layer manual sync behavior could
behave differently.
- Agent create/update/activation flows now perform binding
synchronization consistently, which adds binding-table writes at those
mutation points.
- This PR does not retroactively scan and heal every already-broken
historical agent row; it prevents and backfills through the covered
service flows.
> 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 Codex class model via `codex_local`
- Session model family: GPT-5 Codex
- Tool-assisted coding with shell, git, HTTP, and local test execution
- Reasoning mode: medium interactive tool-use workflow
## 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
- [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 control plane teams use to manage AI
agents for work.
> - Company skills are imported from `SKILL.md` files and rely on YAML
frontmatter to describe what each skill does.
> - Multi-line descriptions commonly use YAML block scalars (`>` and
`|`), but the broken parser path behind #4989 reduced those descriptions
to a literal `>` or `|`.
> - The earliest contributor fix for that bug was PR #5046, so this
branch keeps that PR as the canonical merge target instead of replacing
it.
> - Follow-up work from #5071 and #8258 was then transplanted onto this
earlier branch so the final PR preserves contributor credit while still
shipping the strongest complete fix.
> - The resulting change fixes block-scalar parsing in the shared
frontmatter path, aligns server company-skill imports with that shared
parser, and prevents already-stale stored markers from rendering as junk
in the UI.
## Linked Issues or Issue Description
- Fixes#4989
- Refs #2863
- Refs #788
- Related superseded PRs: #5071, #8258
## What Changed
- Kept the original PR #5046 server-side company-skill fix and
regression coverage as the base branch history.
- Added the missing YAML chomping and indicator hardening explored
further in #5071.
- Moved frontmatter parsing to the shared parser path so
`packages/shared`, `packages/skills-catalog`, and server company-skill
imports stay aligned.
- Added UI summary sanitization and fallback handling so stale stored
`>` / `|` values no longer render as visible junk in company-skill
cards.
- Added regression coverage for shared frontmatter parsing,
skills-catalog parsing, company-skill imports, and stale-summary
fallback behavior.
## Verification
- Passed locally: `pnpm exec vitest run
packages/shared/src/frontmatter.test.ts
packages/skills-catalog/src/frontmatter.test.ts
server/src/__tests__/company-skills.test.ts
ui/src/lib/company-skill-summary.test.ts`
- Passed locally: `pnpm --filter @paperclipai/shared typecheck`
- Passed locally: `pnpm --filter @paperclipai/skills-catalog typecheck`
- Not fully runnable in this worktree: `pnpm --filter
@paperclipai/server typecheck` currently fails in `packages/plugins/sdk`
before reaching server code because local workspace `node_modules` type
deps are missing (`TS2688` for `node` / `react`).
- GitHub Actions / PR checks are rerunning on PR #5046 head
`005290b7557725abf748d00f36dd24ea0d919aba`.
## Risks
- Medium-low risk: the fix now touches shared parser code, server
company-skill imports, and UI fallback display rather than only the
server import path.
- The parser is still intentionally narrower than a full YAML
implementation; this change focuses on block-scalar correctness and the
stale-description rendering path relevant to #4989 / #2863.
- This branch intentionally supersedes narrower overlapping work from
#5071 and duplicate work from #8258 once the survivor PR is green.
> 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-based coding agent with local shell and
code-editing tools 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
- [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: Devin Foley <devin@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The `codex_local` adapter runs local Codex CLI processes and builds
their environment from persisted agent config plus host process env.
> - A host-level `OPENAI_API_KEY` or shared Codex auth home can silently
make new agents spend through shared credentials.
> - Existing agents can be repaired manually, but new and updated agents
need a persistent guard at the agent configuration boundary.
> - This pull request isolates new and updated `codex_local` agents with
per-agent `CODEX_HOME` and an empty `OPENAI_API_KEY` override.
> - The benefit is that future agent creation or adapter updates cannot
silently fall back to shared OpenAI credentials.
## Linked Issues or Issue Description
Paperclip work item: [ZOL-5477](/ZOL/issues/ZOL-5477).
No matching GitHub issue exists, so the bug is described inline
following `.github/ISSUE_TEMPLATE/bug_report.yml`.
**Pre-submission checklist**
- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I am on the latest `master` commit for this PR branch.
- [x] I have confirmed the error originates in Paperclip's `codex_local`
adapter configuration boundary, not in a provider outage.
**What happened?**
New or updated `codex_local` agents could inherit a host-level
`OPENAI_API_KEY` or use a shared Codex home when their adapter config
did not explicitly isolate those values. That made it possible for
future agents or manual adapter edits to silently fall back to shared
OpenAI credentials.
**Expected behavior**
Creating, hiring, or updating a `codex_local` agent should either
persist isolated per-agent configuration or reject unsafe shared Codex
home configuration with a clear 422 response. The guard must not print
secret values.
**Steps to reproduce**
1. Create or update a `codex_local` agent without an explicit
`adapterConfig.env.OPENAI_API_KEY` override.
2. Run it on a host where the Paperclip server process has
`OPENAI_API_KEY` set.
3. Observe that the adapter process can inherit the host key unless
Paperclip persists a blocking empty override.
4. Set `adapterConfig.env.CODEX_HOME` to a shared path such as
`~/.codex` or the company-level `codex-home`.
5. Observe that the old code allowed the shared auth home instead of
returning a validation error.
**Paperclip version or commit**
- Reproduced by inspection against `master` before this PR.
**Deployment mode**
- Local dev / self-hosted server with `codex_local` agents.
**Installation method**
- Built from source.
**Agent adapter(s) involved**
- Codex.
**Database mode**
- Not database-related.
**Access context**
- Board and agent configuration paths.
**Relevant logs or output**
- No secret-bearing logs included.
**Relevant config**
- Unsafe shape: missing `adapterConfig.env.OPENAI_API_KEY`, or shared
`adapterConfig.env.CODEX_HOME`.
- Fixed shape: per-agent `CODEX_HOME` plus empty `OPENAI_API_KEY`
override.
**Additional context**
Related PR search for `codex_local OPENAI_API_KEY CODEX_HOME` found:
- #3681 `fix: preserve managed Codex auth and repo-root env loading`
- #5621 `fix: copy worktree codex auth locally`
Those are adjacent auth-handling changes, but they do not add the agent
create/update guard implemented here.
**Privacy checklist**
- [x] I have reviewed all pasted output for PII, usernames, file paths,
API keys, tokens, company names, and redacted where necessary.
## What Changed
- Added a `codex_local` config guard in agent create, hire, and update
routes.
- The guard assigns `adapterConfig.env.CODEX_HOME` to
`companies/<companyId>/agents/<agentId>/codex-home` when missing.
- The guard persists `adapterConfig.env.OPENAI_API_KEY = ""` when
missing, preventing host env inheritance.
- Shared `CODEX_HOME` values for the company codex-home, host
`$CODEX_HOME`, or `~/.codex` now fail with a 422 error.
- Added route tests for create, hire, update, and rejected shared host
Codex home.
- Updated `codex_local` and development docs to describe the per-agent
home contract.
## Verification
- `pnpm exec vitest run
server/src/__tests__/agent-adapter-validation-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/agent-skills-routes.test.ts`
- `pnpm typecheck`
- `git diff --check upstream/master...HEAD`
- `gh pr list --repo paperclipai/paperclip --state all --search
"codex_local OPENAI_API_KEY CODEX_HOME" --limit 20 --json
number,title,state,url`
- `rg -n "codex|OPENAI_API_KEY|CODEX_HOME|adapter" ROADMAP.md` returned
no roadmap overlap.
## Risks
- Existing legacy `codex_local` agents with shared `CODEX_HOME` will get
a clear 422 when their adapter config is updated until the shared path
is replaced. This is intentional because silent fallback is the bug
being guarded.
- Low migration risk: no database migration and no secret values are
printed or persisted beyond the empty override.
## Model Used
- OpenAI GPT-5.5 Codex, Codex coding-agent session with repository 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
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Paperclip
- Issue: [ZOL-5477](/ZOL/issues/ZOL-5477)
- Owner: Разработчик (`6625498c-66c9-429f-b578-4463ddc3ba16`)
- Status: waiting reviewer
- Next action: merge after approval and green CI
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the control plane operators use to manage agent
execution environments, including plugin-declared sandbox providers.
> - The failing user path here was `Test draft` for an unsaved sandbox
environment using a schema field marked `format: "secret-ref"`.
> - Saved environments already resolve secret refs before provider use,
but the unsaved probe path was forwarding the selected secret UUID
directly to the provider, which made Novita draft probes fail.
> - Fixing that safely required a probe-only secret resolution path with
explicit actor authorization and audit context, because an unsaved draft
has no persisted environment binding to authorize against.
> - Once that was fixed, CI and review surfaced follow-up hardening
work: preserve actor source through the draft-probe path, prevent late
heartbeat finalization from overwriting already-terminal runs, avoid
duplicate successful-run handoff wakes for comment-driven runs, make SSH
git ref updates tolerate concurrent managed-runtime restores, and keep
the skills catalog build from failing on transient GitHub errors for
pinned references.
> - The result is that Novita draft probes now behave like saved
environments, the new secret access path is constrained and audited, and
the PR is green end-to-end with Greptile at 5/5.
## Linked Issues or Issue Description
No matching public GitHub issue was found after searching open and
closed Paperclip issues for `novita`. Related PR search found
[#8255](https://github.com/paperclipai/paperclip/pull/8255), but it
addresses Novita/dev-SDK linking rather than this draft probe bug.
Bug summary:
- What happened?
When a board user configured a sandbox environment backed by a
schema-driven plugin provider such as Novita, selecting an existing
company secret for `apiKey` and clicking `Test draft` failed because the
probe received the secret UUID instead of the resolved secret value.
- Expected behavior
`Test draft` should resolve secret-ref fields before calling the
provider probe, just like the saved runtime path does.
- Steps to reproduce
1. Open `Company Settings -> Environments`.
2. Create or edit a `Sandbox` environment using a provider with a
`format: "secret-ref"` field such as `Novita Agent Sandbox`.
3. Select an existing company secret for `apiKey`.
4. Click `Test draft`.
5. Observe the probe failure before this patch.
- Paperclip version or commit
Reproduced on a local `master` dev checkout; fixed and verified on
branch commit `ed982d0c0`.
- Deployment mode
Local dev (`pnpm dev`).
- Installation method
Built from source (`pnpm dev` / `pnpm build`).
- Agent adapter(s) involved
Not adapter-specific in the core bug path; affects schema-driven sandbox
provider plugins such as Novita.
- Database mode
Not database-related.
- Access context
Board (human operator).
- Node.js version
`v25.6.1`.
- Operating system
`macOS 15.7.4`.
- Relevant logs or output
The user-visible failure was `Novita sandbox probe failed` during `Test
draft`.
## What Changed
- Resolved schema-marked secret-ref fields during unsaved sandbox
environment probes by adding a dedicated probe-time secret resolution
path in `environment-config.ts`.
- Passed `companyId` plus the full authenticated actor context into the
draft probe normalization route so secret resolution stays
company-scoped, authorized, and auditable.
- Hardened ephemeral secret resolution so unsaved probes require
`secrets:read`, preserve the original actor source (`local_implicit`,
`agent_jwt`, etc.), and emit usable audit metadata.
- Added a conditional heartbeat run-status update so late adapter
completions cannot overwrite runs that were already cancelled or
otherwise terminal.
- Skipped successful-run handoff synthesis for comment-driven wakes,
which removes the extra wake/run that was breaking
`heartbeat-comment-wake-batching`.
- Retried managed-runtime SSH git ref updates on concurrent ref-lock
races instead of failing the restore path.
- Reused the previous skills-catalog manifest entry when a pinned GitHub
reference fails with a recoverable transient error during CI catalog
generation.
- Added focused regression coverage for the draft probe, ephemeral
secret access, heartbeat handoff behavior, SSH ref-lock races, and
catalog fallback behavior.
## Verification
- `pnpm vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm vitest run server/src/__tests__/secrets-service.test.ts`
- `pnpm vitest run
server/src/__tests__/heartbeat-comment-wake-batching.test.ts`
- `pnpm vitest run
server/src/services/recovery/successful-run-handoff.test.ts`
- `pnpm vitest run
server/src/__tests__/openclaw-gateway-adapter.test.ts`
- `pnpm exec vitest run packages/adapter-utils/src/ssh-fixture.test.ts
-t "merges concurrent remote commits through the managed runtime restore
path"`
- `pnpm exec vitest run
packages/skills-catalog/src/catalog-builder.test.ts`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/skills-catalog build`
- `pnpm --filter @paperclipai/adapter-utils build`
- `gh pr checks 8256`
- Manual/live validation: the same fix was cherry-picked into the
running local dev checkout and the user re-tested the Novita `Test
draft` flow successfully after the server restart.
## Risks
- Low risk: the Novita-specific user-facing fix is isolated to unsaved
sandbox draft probes for plugin schema fields marked `format:
"secret-ref"`.
- The new ephemeral secret resolution path is intentionally stricter
than the original broken behavior; regressions would most likely show up
as denied draft probes rather than accidental secret exposure.
- The heartbeat, SSH, and catalog changes are all defensive; if they
regress, they should affect test/CI orchestration paths rather than
persisted company data.
## Model Used
- OpenAI Codex Local (`codex_local` in Paperclip). The runtime does not
expose the exact backend model ID in agent metadata. GPT-5-class coding
model with shell/tool use, repository editing, test execution, GitHub
review handling, and issue-thread coordination.
## 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
- [ ] 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
> - Sandbox-provider plugins (cloudflare, daytona, e2b, exe-dev,
kubernetes, modal, novita) and `plugin-workspace-diff` are published as
standalone npm packages, but during local dev they need the in-repo
`@paperclipai/plugin-sdk` symlinked in
> - Each of these plugins shipped a `postinstall` lifecycle script that
traversed *out* of its own package directory (`node
../../../../scripts/link-plugin-dev-sdk.mjs`) to do that linking
> - The publishable manifest is built by a `prepack` whitelist that
drops the `scripts` field, so npm consumers don't see the postinstall
today — but that safety property depends entirely on `prepack` running
on every publish. A publish that skips lifecycle scripts would ship a
tarball whose postinstall escapes its package directory at consumer
install time
> - This pull request removes the escape-the-package-dir lifecycle
script from every plugin source manifest and moves the dev linking to a
single root-level postinstall that iterates the excluded plugin
directories itself
> - The benefit is that plugin tarballs can no longer carry an
install-time script that reaches outside their own directory, regardless
of whether `prepack` runs
## Linked Issues or Issue Description
This is a follow-up hardening change flagged during review of the Novita
sandbox provider PR (#7595).
**Problem (security):** Excluded plugin packages each carried
`"postinstall": "node ../../../../scripts/link-plugin-dev-sdk.mjs"`. The
relative path traverses outside the package root. Today the published
manifest is sanitized by a `prepack` whitelist that drops `scripts`, so
consumers are unaffected in the normal publish path. The risk is that
this is a defense-in-depth gap: if a publish ever skips lifecycle
scripts (e.g. `npm publish --ignore-scripts` is *not* used, or a tool
publishes the raw manifest), the tarball would ship a postinstall that
runs out-of-tree code at the consumer's install time.
## What Changed
- Added a single root `package.json` `postinstall`: `node
scripts/link-plugin-dev-sdk.mjs`.
- Rewrote `scripts/link-plugin-dev-sdk.mjs` to iterate the excluded
plugin directories itself (`packages/plugins/sandbox-providers/*` + the
orchestration smoke example) instead of relying on each plugin to invoke
it from its own cwd. Preserves both prior behaviors: leave a real
installed SDK dir alone, and skip when already correctly symlinked
(idempotent).
- Removed `scripts.postinstall` from all 7 sandbox-provider plugins
(cloudflare, daytona, e2b, exe-dev, kubernetes, modal, novita).
- Removed `scripts.postinstall` from `plugin-workspace-diff` (a pnpm
workspace member — pnpm already links the SDK, so the script was a no-op
there).
## Verification
- `node scripts/link-plugin-dev-sdk.mjs` from repo root: links the SDK
into the excluded plugins and reports skipped (already-linked) dirs;
re-running is idempotent.
- `grep -r "link-plugin-dev-sdk" packages/plugins/*/package.json
packages/plugins/sandbox-providers/*/package.json` returns no matches —
no plugin source manifest references the linker any longer.
- All affected `package.json` files re-validated as parseable JSON.
## Risks
Low risk. Dev-only tooling: the linker only runs at the repo root during
local install and only touches `node_modules/@paperclipai/plugin-sdk`
symlinks inside excluded plugin dirs. No change to published plugin
behavior or runtime code. Worst case if the root postinstall failed to
run, local dev of an excluded plugin would not find the SDK symlink —
easily re-run manually.
## Model Used
Claude Opus (claude-opus-4-8), extended reasoning, with tool use / code
execution in an agentic coding 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
- [x] I have either (a) linked existing issues 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 (added
`scripts/link-plugin-dev-sdk.test.js`, wired into
`test:release-registry`)
- [ ] If this change affects the UI, I have included before/after
screenshots (N/A — no UI change)
- [ ] I have updated relevant documentation to reflect my changes (N/A)
- [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 already separates agent adapters from execution
environments, so agents can run locally, over SSH, or through sandbox
providers.
> - Sandbox provider plugins let Paperclip add new cloud runtimes
without changing each agent adapter.
> - Novita Agent Sandbox is a cloud runtime for AI agent workloads with
isolated filesystems, command execution, templates, timeout controls,
and pause/resume behavior.
> - Paperclip currently has sandbox provider examples for Daytona and
Cloudflare, but not Novita.
> - This pull request adds a Novita sandbox provider plugin using the
existing provider-plugin lifecycle.
> - The benefit is that Paperclip users can run existing adapters such
as Codex, Claude, Gemini, OpenCode, Cursor, or ACPX inside Novita Agent
Sandbox environments.
## Linked Issues or Issue Description
Fixes#7596
## What Changed
- Added `packages/plugins/sandbox-providers/novita` as a standalone
sandbox provider plugin package.
- Registered provider key `novita` with `kind: "sandbox_provider"` and
`environment.drivers.register` capability.
- Implemented Novita environment lifecycle hooks: validate config,
probe, acquire lease, resume lease, release lease, destroy lease,
realize workspace, and execute commands.
- Added config support for `apiKey`, `domain`, `template`,
`requestedCwd`, `timeoutMs`, `requestTimeoutMs`, `secure`, `autoPause`,
and `reuseLease`.
- Added README documentation for setup, configuration, and lifecycle
behavior.
- Added tests for manifest shape, config parsing, safe shell command
wrapping, stdin delimiter safety, and env-key validation.
## Verification
From `packages/plugins/sandbox-providers/novita`:
- `pnpm typecheck`
- `pnpm test`
The tests avoid live Novita API calls and cover the provider's static
contract and command-wrapping behavior. Live end-to-end verification
requires a Paperclip instance with the plugin installed and a Novita API
key configured as either a Paperclip secret or `NOVITA_API_KEY` in the
worker environment.
## Risks
- This adds a new direct dependency on the Novita Sandbox JS SDK
(`novita-sandbox`). Socket/Snyk should review the package as part of
normal dependency checks.
- The implementation relies on Novita SDK command execution semantics;
live provider behavior should be verified with a real Novita sandbox
before marking the plugin production-ready.
- `reuseLease` maps Paperclip release behavior to Novita `betaPause()`.
If pause is unavailable for a selected template, the plugin falls back
to best-effort kill during release.
- Low migration risk for existing users because this is a new standalone
provider plugin and does not change existing adapters or built-in
providers.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex via Codex CLI, with repository file access, shell
command execution, GitHub CLI/API usage, and local TypeScript/Vitest
verification. Web and local documentation context were used for Novita
Sandbox SDK/API behavior.
## 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>
Co-authored-by: Devin Foley <devin@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The `server/services/environments.ts` module lazily provisions a
managed Kubernetes sandbox environment for each company on first
heartbeat. Idempotency relies on `ensureKubernetesEnvironment` returning
the single managed row per company.
> - The `(company_id, driver)` unique index in the `environments` schema
is partial on `driver='local'` only, so two concurrent callers (e.g.
simultaneous first heartbeats from a freshly synced tenant) can both
insert a `driver='sandbox'` row before either sees the other.
> - The function tried to converge after the race by re-reading, picking
the oldest managed row as winner, and deleting the loser. Under
autocommit + read-committed, each post-insert SELECT is a fresh snapshot
— A may not see B's row, B may not see A's, so both pick their own and
neither deletes. Two rows survive.
> - The same race fired in CI as `expected 2 to be 1` at
`environment-service.test.ts:293`, gating multiple unrelated PRs on
retries.
> - This pull request encodes the operator-level invariant ("at most one
Paperclip-managed sandbox row per company") at the DB layer with a
partial unique index, then switches `ensureKubernetesEnvironment` to
`INSERT … ON CONFLICT DO NOTHING` keyed on that index. Losers re-read
the surviving row.
> - The benefit is the race is impossible by construction — no
application-side convergence loop, no test flake, and any future
`ensureXyzSandboxEnvironment` that sets `managedByPaperclip=true`
inherits the invariant for free.
## Linked Issues or Issue Description
Paperclip issue: PAPA-783 — implement managed-sandbox dedup fix (phase 2
of the approved plan on the parent flake-investigation issue).
This is the phase-2 fix for the flaky `environmentService > deduplicates
concurrent managed Kubernetes environment creation` test introduced by
`4ad94d0bd` (PR #7938). Failing CI runs since then on at least PRs
#7595, #8233, #8215, #8212. The plan was reviewed and approved on the
parent issue before implementation.
Closely related (not duplicates):
- PR #7938 — introduced the test and the in-process convergence loop
being replaced here.
- PR #7595, PR #8233, PR #8215, PR #8212 — downstream PRs affected by
the flake; one of them will be rebased onto this fix as the acceptance
gate.
## What Changed
- `packages/db/src/schema/environments.ts`: added
`environments_company_managed_sandbox_idx`, a partial unique index on
`(company_id) WHERE driver='sandbox' AND
(metadata->>'managedByPaperclip')::boolean = true`. The umbrella
`managedByPaperclip` predicate covers any current or future
Paperclip-managed sandbox flavor without needing a new index per
provider.
- `packages/db/src/migrations/0102_managed_sandbox_dedup_index.sql`:
one-shot dedup `DELETE` keeping the oldest managed-sandbox row per
`company_id` (scoped to `driver='sandbox' AND managedByPaperclip=true`),
`RAISE NOTICE` if any duplicates were removed, then `CREATE UNIQUE INDEX
IF NOT EXISTS environments_company_managed_sandbox_idx`. `CONCURRENTLY`
is omitted because the codebase's migration runner wraps each file in a
transaction (see `applyPendingMigrationsManually`); the table holds 1–3
rows per company, so the short lock is acceptable and consistent with
every other migration in the repo.
- `server/src/services/environments.ts`: `ensureKubernetesEnvironment`
now uses `INSERT … ON CONFLICT DO NOTHING` keyed on the new index. On
conflict it re-reads the surviving managed-sandbox row and returns it.
Drops the post-insert convergence (re-read by `createdAt ASC, id ASC`,
delete the loser) and the trailing comment that flagged "until a partial
unique index is added via migration" as the proper long-term fix.
- Unused `asc` import removed from
`server/src/services/environments.ts`.
## Verification
Local (matches the success criteria in the issue body):
```
$ cd server
$ passes=0; for i in $(seq 1 20); do
pnpm vitest run src/__tests__/environment-service.test.ts -t "deduplicates concurrent" \
&& passes=$((passes+1)) || break
done; echo "$passes/20"
20/20
$ passes=0; for i in $(seq 1 10); do
pnpm vitest run src/__tests__/environment-service.test.ts \
&& passes=$((passes+1)) || break
done; echo "$passes/10"
10/10
```
Adversarial fan-out stress (temporarily bumped `Array.from({ length: 8
}, …)` to `length: 32` on both dedup tests; reverted before commit):
```
$ # both dedup tests fan-out-of-32, 10 iterations
10/10
```
Two ensure paths exist in the parent plan, but only
`ensureKubernetesEnvironment` is on `master`.
`ensureManagedSandboxEnvironment` (referenced by the approved plan as
commit `dce9a9622`) lives only on an unmerged feature branch, not
master. The plan's helper-extraction and symmetric dedup test for that
path are deferred to whichever PR lands the second ensure path — it
inherits the same DB invariant by setting `managedByPaperclip=true`.
Discrepancy was flagged on the issue thread before implementation.
Typecheck:
```
$ pnpm -C server typecheck
ok
```
## Risks
Low risk.
- **Migration safety.** `IF NOT EXISTS` on the index makes the migration
idempotent. The dedup `DELETE` is bounded to rows matching the
managed-sandbox predicate; in production this should be a no-op (no race
has been reported in the wild — only in CI). On dev/CI DBs that already
accumulated duplicates, the migration emits a `NOTICE` reporting the
count.
- **No `CONCURRENTLY`.** The migration runner wraps each `.sql` file in
a transaction, which is incompatible with `CREATE INDEX CONCURRENTLY`.
The `environments` table holds 1–3 rows per company and the row count is
bounded by company count; the short ACCESS EXCLUSIVE during `CREATE
UNIQUE INDEX` is acceptable here and matches every other index migration
in the repo.
- **Predicate scope.** The partial index predicate matches exactly the
rows that `ensureKubernetesEnvironment` writes (`driver='sandbox'` with
`metadata.managedByPaperclip=true`). Tenant-created sandbox envs (via
`svc.create`) do not set this marker and are not covered — no false
positives, no surprise constraint violations on unrelated inserts.
## Model Used
Claude (Anthropic), `claude-opus-4-7`. Tool use: code edit + bash +
filesystem search; no extended-thinking mode.
## 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
> - Routines let users templatize titles and instructions with
`{{name}}` placeholders that get prompted for at run time
> - A user reported that `{{pr_url}}` typed into a routine description
was never detected as a variable, while camelCase placeholders worked
> - Root cause: the routine description is edited via MDXEditor (WYSIWYG
markdown). On save its serializer (`mdast-util-to-markdown`) defensively
escapes intraword underscores, so a user-typed `{{pr_url}}` is stored as
`{{pr\_url}}`. The variable matcher only accepted `[A-Za-z0-9_]` inside
the placeholder, so the backslash broke the match and the variable was
silently dropped
> - This PR widens the matcher to tolerate `\_` and unescapes it back to
`_` on capture, in both extraction and interpolation, so a single name
is detected and resolved regardless of whether the source markdown was
hand-typed or round-tripped through a WYSIWYG editor
> - The benefit is that snake_case placeholders behave the same in the
routine UI and the executor, removing a silent failure mode
## Linked Issues or Issue Description
Refs PAPA-771 — "Fix example for variable_name". Initially scoped as a
docs-copy fix (change the example to camelCase), but investigation
showed the underlying bug was that the parser could not see the variable
when it was authored as `{{pr_url}}` in a WYSIWYG-edited description.
This PR addresses the bug directly so snake_case names work as users
expect. (Supersedes the now-closed PR #8054 — same commit, fresh branch
so the diff is visible.)
## What Changed
- `packages/shared/src/routine-variables.ts`:
- Widened the `ROUTINE_VARIABLE_MATCHER` regex to accept `\_` inside
placeholder names (in addition to `[A-Za-z0-9_]`)
- Added `unescapeRoutineVariableName` and applied it on capture in both
`extractRoutineVariableNames` and `interpolateRoutineTemplate`, so the
looked-up variable name is normalized regardless of the markdown escape
- `packages/shared/src/routine-variables.test.ts`:
- Added tests covering plain snake_case (`{{pr_url}}`), markdown-escaped
(`{{pr\_url}}`), multi-underscore (`{{pr\_url\_v2}}`),
sync-with-template, and interpolation
## Verification
- `pnpm --filter @paperclipai/shared exec vitest run routine-variables`
→ 13 passed (4 new), confirming both plain and escaped underscore
placeholders are detected and interpolated as the same variable name
- Reproduced the root cause out-of-band with `mdast-util-from-markdown`
+ `mdast-util-to-markdown` to confirm the editor's serializer is the
source of the `\_` escape
## Risks
- Low. Change is confined to `packages/shared/src/routine-variables.ts`;
the regex is strictly more permissive in a tightly bounded way (only
`\_` is newly allowed) and the captured name is normalized.
`isValidRoutineVariableName` is unchanged, so stored names are still
strict identifiers.
## Model Used
- Claude (Anthropic), `claude-opus-4-7` (Opus 4.7), 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 — only my own closed#8054 supersedes/duplicates this one.
Open PRs touching `routine-variables.ts` (#7184, #7186, #6993, #7187)
are all for unrelated concerns (server-side PATCH persistence, GH#6525)
- [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 — `routine-variables` suite
passes (13/13)
- [x] I have added or updated tests where applicable — 4 new tests
covering snake_case + escaped forms
- [ ] If this change affects the UI, I have included before/after
screenshots — no UI change in this PR
- [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 re-run after the
push for the Greptile blank-line nit
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
initial review was 5/5; the one P2 (missing blank line) is addressed in
the latest push
- [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
> - The plugin subsystem persists state into four tables
(`plugin_entities`, `plugin_job_runs`, `plugin_logs`,
`plugin_webhook_deliveries`) and those rows currently have no notion of
an owning tenant — so company-deletion doesn't cascade plugin state, and
operators have no way to query "what does this plugin own for company
X?"
> - The fix is one thin slice of tenant-isolation hygiene that doesn't
change any external API: add a nullable `company_id` FK with `ON DELETE
CASCADE` to the four plugin tables, index it, and scope the
`plugin_entities` external-id uniqueness per-tenant
> - The benefit is plugin-row tenant attribution + cascade cleanup, with
zero impact on single-tenant local-first deploys (`NULL` continues to
mean instance-scope)
> **Rebase note (scope narrowed):** This PR originally also hardened the
schedulers (`heartbeat.tickTimers` / `resumeQueuedRuns` /
`enqueueWakeup` and `routines.tickScheduledTriggers`) to skip archived
companies. That half has since landed on `master` via #7478 (`93206f73`,
"Stop archived companies from waking agents") with a stricter
implementation (`status != 'active'` plus a skipped-request audit row).
On rebase those changes were dropped as redundant — `heartbeat.ts` and
`routines.ts` are now identical to `master`, and the scheduler-specific
tests were removed. **This PR is now DB-only.**
## Linked Issues or Issue Description
No existing issue covers this directly — problem described in-PR:
- Four plugin tables (`plugin_entities`, `plugin_job_runs`,
`plugin_logs`, `plugin_webhook_deliveries`) persist rows with no notion
of an owning tenant.
- Company deletion therefore does not cascade plugin state, and
operators have no way to query "what does this plugin own for company
X?"
- One thin slice of tenant-isolation hygiene fixes this without changing
any external API: a nullable `company_id` FK with `ON DELETE CASCADE`,
an index, and per-tenant scoping of the `plugin_entities` external-id
uniqueness.
- Part of the multi-tenant hardening initiative alongside #3967
(cross-tenant 404 oracle) and #5864 (per-company JWT keys).
## What Changed
**Schema (`packages/db/src/schema/plugin_*.ts`):**
- Nullable `companyId` FK with `onDelete: "cascade"` added to
`plugin_entities`, `plugin_job_runs`, `plugin_logs`,
`plugin_webhook_deliveries`.
- A btree index on each new `company_id` column (`<table>_company_idx`).
- `plugin_entities_external_idx` rescoped from `(plugin_id, entity_type,
external_id)` to `(company_id, plugin_id, entity_type, external_id)` and
switched to `UNIQUE … NULLS NOT DISTINCT` so instance-scope rows
(`company_id IS NULL`) keep their dedup guarantee while tenants get
their own namespace.
**Migration:**
- `0095_plugin_company_id_tenant_isolation.sql` — 14 statements: 4
column adds + 4 FK constraints (`ON DELETE CASCADE`) + 4 indexes +
drop/recreate of the external-id unique constraint.
- Journal entry + regenerated `0095_snapshot.json`.
**Tests:**
- `server/src/__tests__/plugin-tenant-isolation.test.ts` — `NULL`
preserves backward compat; `CASCADE` on company delete across all four
tables; per-tenant external-id namespacing; NULL-NULL collision rejected
(`NULLS NOT DISTINCT`).
## Verification
- `pnpm --filter @paperclipai/db run check:migrations` — pass.
- `pnpm --filter @paperclipai/db typecheck` (`tsc`) — pass.
- `vitest run plugin-tenant-isolation` — **4/4 pass** (embedded Postgres
applies `0095` and exercises cascade + NULLS NOT DISTINCT).
## Notes
- **Clean snapshot, no drift.** The earlier revision of this PR shipped
a ~17.6k-line meta snapshot that was almost entirely pre-existing drift.
On rebase the migration was renumbered (the old `0090_brainy_darkhawk`
collided with `master`'s `0090_resource_memberships … 0094`) and
regenerated from the current `master` baseline via `drizzle-kit
generate`. The result is a 14-statement migration containing **only**
the plugin-table changes — no unrelated drift.
- **Backward-compatible.** `NULL company_id` continues to mean
instance-scope (cron jobs, public webhooks). No new env vars, no API
surface change. Single-tenant local-first deploys unaffected.
## Risks
- **Migration is additive and nullable** — `0095` adds nullable
`company_id` columns, FK constraints, and indexes; existing rows stay
valid (`NULL` keeps meaning instance-scope) and no backfill is required.
- **`ON DELETE CASCADE` is a behavioral change**: deleting a company now
removes its plugin rows across all four tables. Intended (it is the
point of the PR), but operators relying on plugin rows surviving company
deletion would be affected. Covered by the cascade tests.
- **Uniqueness semantics change on `plugin_entities`**: the external-id
constraint is rescoped per-tenant and switched to `UNIQUE … NULLS NOT
DISTINCT`, so two instance-scope rows (`company_id IS NULL`) with the
same external id are now rejected instead of coexisting. Covered by the
NULL-NULL collision test.
- **No API surface change, no new env vars.** Single-tenant local-first
deploys unaffected.
(Section added retroactively to match the PR template; distilled from
the What Changed / Notes sections above.)
## Model Used
Same authoring setup as #5864 (same series, same day): Claude Opus 4.7
(1M context), extended thinking mode. (Section added retroactively.)
## Checklist
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] Tests run locally and pass (plugin-tenant-isolation 4/4)
- [x] `check:migrations` + db typecheck pass
- [x] No UI changes
- [x] Migration carries only the intended changes (no snapshot drift)
- [x] Scheduler half dropped as superseded by #7478
Part of the multi-tenant hardening initiative — see also #3967
(cross-tenant 404 oracle) and #5864 (per-company JWT keys).
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The `openclaw_gateway` adapter is how operators wire Paperclip
agents to an OpenClaw gateway over WebSocket
> - The adapter UI previously only exposed a handful of config fields in
edit mode; many timeout / auth / session-routing knobs were unreachable
through the form
> - The serializer also forgot to inject the configured `authToken` into
the `x-openclaw-token` header, and the server-side execute path lacked
retries on transient gateway errors and an `OPENCLAW_TOKEN` env fallback
> - This pull request exposes the full set of config fields in both
create and edit modes, fixes the serializer, hardens the server-side
execute path, and pins the existing default request timeouts (120s /
120000ms) — see the dedicated commit and the new unit tests
> - The benefit is operators can configure and reconfigure an
`openclaw_gateway` agent end-to-end through the UI, with no silent
change to the defaults documented in the adapter README and
`doc/ONBOARDING_AND_TEST_PLAN.md`
## Linked Issues or Issue Description
Closes#414Closes#1901Closes#2309
## What Changed
- **UI**: Removed the `!isCreate` guard so all `openclaw_gateway` config
fields are visible in both create and edit modes (`authToken`,
`agentId`, `sessionKeyStrategy`, `sessionKey`, `timeoutSec`,
`waitTimeoutMs`, `disableDeviceAuth`, `autoPairOnFirstConnect`, `role`,
`scopes`, `paperclipApiUrl`, `headersJson`, `payloadTemplate`,
`runtimeServices`).
- **Serialization**
(`packages/adapters/openclaw-gateway/src/ui/build-config.ts`): inject
`authToken` into headers as `x-openclaw-token`; apply safe defaults on
create (`timeoutSec=120`, `waitTimeoutMs=120000`,
`sessionKeyStrategy="issue"`, `role="operator"`,
`scopes=["operator.admin"]`).
- **Backend**
(`packages/adapters/openclaw-gateway/src/server/execute.ts`): add
`OPENCLAW_TOKEN` env-var fallback for `authToken`, retry logic (max 2
retries with backoff for transient gateway errors), session-key prefix
`agent:{agentId}:{sessionId}` when `agentId` is configured.
- **Defaults restoration** (dedicated commit): an earlier revision of
this PR lowered the default request timeouts to `60` / `30000`. The
current branch restores the historical `timeoutSec=120` /
`waitTimeoutMs=120000` defaults that match the values documented in
`packages/adapters/openclaw-gateway/src/index.ts`,
`src/server/execute.ts` on master, and the worked example in
`doc/ONBOARDING_AND_TEST_PLAN.md`.
- **Tests** (new):
`packages/adapters/openclaw-gateway/src/ui/build-config.test.ts` pins
the documented timeout and identity defaults so the silent-halve
regression cannot recur.
## Verification
- `pnpm --filter @paperclipai/adapter-openclaw-gateway typecheck`
- `pnpm typecheck` (root)
- Manual: create a new `openclaw_gateway` agent — all fields visible,
defaults populate as documented.
- Manual: edit an existing `openclaw_gateway` agent — every field
round-trips correctly and saves.
- Manual: unset `authToken` in the form and set `OPENCLAW_TOKEN` env var
— adapter picks up the env-var fallback.
- Manual: simulate a transient gateway error — execute retries up to 2
times with backoff before failing.
## Risks
- Low risk. Surface area is one adapter, behind explicit operator
configuration. The defaults change in this PR is a restoration of values
that already exist on master and in the adapter docs, so no production
agent sees a behavioral shift relative to the prior release. Field
exposure in edit mode is purely additive — existing values are preserved
on save.
## Model Used
- Provider/model: Claude (Anthropic) — `claude-opus-4-7`
- Mode: standard tool use, no extended thinking
- Capability notes: code execution + repository file edits via Claude
Code
## Cross-references and status (maintainer)
Closes#414Closes#1901Closes#2309
## 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 Bot <bot@paperclip.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
Production e2e on the merged #5790 plugin failed on every fresh lease
with "Failed to install the adapter runtime command" for a harness that
was present in the runtime image. Tracing the lease showed the first
exec resolved no pod: the exact-label fallback added during the #5790
review queries `agents.x-k8s.io/sandbox-name=<name>`, but the
kubernetes-sigs agent-sandbox controller labels pods only with
`agents.x-k8s.io/sandbox-name-hash` (see `sandboxLabel` in its
`controllers/sandbox_controller.go`) and NAMES the backing pod exactly
after the Sandbox CR. The selector matches nothing, `findPodForSandbox`
returns null, execute returns "podName could not be resolved", and
adapter-utils misreports it as a missing runtime command.
## What Changed
Between the `status.podName` read and the label fallback, try an
exact-name pod GET (`readNamespacedPod({namespace, name})`). This is
collision-free, so the original review concern (name-prefix matching
execing into a concurrent sandbox's pod) stays honored. A 404 falls
through to the existing full-name label selector for controller versions
that do set such a label. Non-404 errors propagate unchanged.
## Verification
- New unit test pins the controller reality: pod named exactly like the
sandbox, only a `sandbox-name-hash` label, no full-name label; fails
before the fix, passes after.
- Review-feedback round: the primary-path test now asserts the
exact-name GET is never called, and a new test covers non-404 error
propagation (403 rejects, no fallback). 153/153 plugin tests green, tsc
clean.
- Production-verified on our deployment: agent runs were broken on every
fresh lease before this patch and complete end-to-end after it (gVisor
sandbox pool, agent-sandbox controller v0.4.6; verified run with cost
event and agent reply on a fresh tenant).
## Risks
Low: one additional pod GET per first-exec on a fresh lease, only when
`status.podName` is unset. Non-404 errors from the GET propagate
unchanged (now test-pinned).
## Issue
No existing issue; the defect is described in full under Thinking Path
(introduced by the review-round fallback change in #5790, first hit in
production e2e on 2026-06-11).
## Model Used
Claude Fable 5 (claude-fable-5, Claude Code CLI, extended reasoning,
tool use)
## Duplicate search
Searched open and closed PRs for `findPodForSandbox`,
`sandbox-name-hash`, and pod-resolution fixes; no duplicate found.
Related parent: #5790 (introduced the fallback this PR repairs).
## 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 change)
- [x] I have updated relevant documentation to reflect my changes (code
comments; no doc surface affected)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The first-run experience (onboarding wizard) and the chat surfaces
(conference-room/board chat, task threads, composers) are the product's
front door — they decide whether a new operator understands "hire
agents, give them work, review results" in the first five minutes
> - Today those surfaces feel ticket-y and form-like: the wizard is a
static multi-step form that ends in an anticlimactic "Launch" screen,
the task composer and board chat behave differently from each other, and
agent-feed issue quicklooks misbehave (multiple flyouts open at once,
cards jump on hover)
> - We wanted to iterate toward a conversational, team-centric NUX — but
without risking the workflows of everyone already running Paperclip
> - This PR reworks the NUX behind a new default-OFF
`enableConferenceRoomChat` experimental flag: a capsule-motif onboarding
wizard that builds your team as you answer, a conference-room chat
surface, one shared ChatComposer across surfaces, brand-accurate status
chips, and feed-quicklook fixes — with the pre-existing UI
fork-and-frozen as `*Classic` components that flag-OFF users keep
> - The benefit is a complete, testable modern NUX that anyone can opt
into from Settings → Experimental, with zero default behavior change and
a clean path to either graduate or drop the experiment
## Linked Issues or Issue Description
No pre-existing GitHub issue — feature description per
`feature_request.yml`:
- **Problem / motivation:** Paperclip's onboarding wizard and chat
surfaces grew up as separate ticket-centric forms. New users get a
form-filling experience rather than the feeling of standing up a team;
the board chat and task threads use different composers with different
affordances; the agent feed's issue quicklook can stack multiple
popovers and shifts cards on hover.
- **Proposed solution:** A coherent NUX experiment behind one
experimental flag (`enableConferenceRoomChat`, Settings → Experimental,
default OFF): capsule onboarding wizard with an evolving team capsule,
conference-room chat, unified `ChatComposer`, team-centric copy, brand
status chips, quicklook single-flight fix. Flag-OFF users get the exact
pre-experiment UI via frozen `*Classic` forks, verified by an on/off
parity test matrix.
- **Alternatives considered:** (a) incremental unflagged restyling —
rejected: the changes interlock across surfaces and would drip risk into
every release; (b) a separate app shell / route for the new NUX —
rejected: too much divergence, the flag + classic-fork pattern keeps the
diff reviewable and reversible.
- **Roadmap alignment:** `ROADMAP.md` lists **CEO Chat** ("a
lighter-weight way to talk to leadership agents... should still resolve
to real work objects"). This experiment is groundwork in that direction
(conference-room chat resolves to issues/tasks via the same composer
used in task threads) and does not change the core task-and-comments
model.
Related PRs found in the dedup search (same area, none duplicate this
work — they target the classic wizard, which this PR intentionally
leaves intact and mergeable):
- #5385 — Coach-driven onboarding: conversational entry +
agent-companies package import
- #5378 — Onboarding wizard: reusable adapter picker + probe card
- #6636 — ui(onboarding): friendly error surface + retry for the wizard
- #7005 — fix(onboarding): explicitly await first-task wake
- #2616 — fix: restore workspace directory config in onboarding wizard
## What Changed
- **Experimental flag plumbing** — `enableConferenceRoomChat` in shared
types/validators, server instance-settings service + API, Settings →
Experimental card with explicit enable/disable copy
- **Onboarding wizard** — classic wizard forked and frozen
(`OnboardingWizardClassic`); flag-ON variant is a 5-step capsule wizard
with a persistent evolving `AgentCapsule` (gradient/glow motif),
team-centric reframed copy, and a typing-dots intro (hardened with
fake-timer tests)
- **Conference-room chat** — flag-ON board-chat surface with agent
bubble name/icon headers and copy/vote/timestamp action rows
(`AgentBubbleActionRow`)
- **Unified composer** — shared `ChatComposer` adopted across surfaces;
translucent surface + scroll-mask removal; "Agent mode"/"Plan mode"
relabels; no-assignee confirmation `AlertDialog` (new
`ui/alert-dialog.tsx` primitive); `@task` reference picker +
linkification in mentions
- **Agent feed** — single-flight issue-quicklook store (one popover at a
time), flyouts open to the left, removed hover translate-y jitter
- **Status chips** — brand-accurate task status chips behind the flag
(light/dark, 1px borders per paperclip.ing/brand)
- **Tests** — flag on/off parity matrix across IssueDetail,
NewIssueDialog, Sidebar, wizard, gate components; component tests for
all new pieces
- **Merge with `master`** — one conflict in
`ui/src/components/IssueChatThread.tsx`, resolved by keeping master's
new `AssigneeChip`/`HandoffWakeRow`/`RunStatusBadge` components inside
the flag-gated metadata-row chrome (details in commit `21a5642a`);
post-merge fixes: vitest 4 mock typing in `MarkdownEditor.test.tsx`,
flag hook made safe for provider-less mounts (master's new isolated
component tests)
- **Branch hygiene** — internal design wireframes/mockups stripped
before the PR (they live in the Paperclip issue threads)
- No user-facing documentation changes required: the flag is
intentionally experimental and self-described in the Settings card; no
existing docs reference the affected surfaces
## Verification
- `pnpm run typecheck` — green across the workspace (ui, server, shared,
plugins)
- Full UI suite (`vitest run` in `ui/`, clean worktree at this HEAD):
**1593/1595 passing, 223/224 files** — the 2 remaining failures are in
`src/components/artifacts/ArtifactCard.test.tsx` and **fail identically
on pristine `origin/master`** (pre-existing upstream, unrelated to this
branch)
- Full server suite (`vitest run` in `server/`, same clean worktree):
results in PR checks; flag plumbing covered by instance-settings tests
- Targeted post-merge resolution check: `IssueChatThread`,
`IssueChatThreadSystemNotice`, `IssueDetail`, `Sidebar`,
`ConferenceRoomChatGate`, `OnboardingWizardVariant`, `NewIssueDialog`,
`InstanceExperimentalSettings`, `MarkdownEditor` — 172/172 passing
- Manual walkthrough: flag OFF (default) → onboarding wizard, task
thread, board chat, composer all render the classic UI; flag ON via
Settings → Experimental → capsule wizard, conference-room chat, unified
composer, status chips active
- Screenshots: see below
**Flag on/off screenshots** (committed on this branch under
`screenshots/PR-8000-*`):
| Surface | Flag OFF (classic, default) | Flag ON (experimental) |
| --- | --- | --- |
| Settings → Experimental | 
| 
|
| Task thread | 
| 
|
| Home / nav | 
| 
|
| Conference Room (flag-ON only surface) | — | 
|
Capsule onboarding wizard walkthrough screenshots (flag ON) are attached
to the Paperclip design/implementation threads; the wizard requires a
fresh instance so it is captured via the e2e harness
(`tests/e2e/nux-phase4-screenshots.spec.ts`).
## Risks
- **Large surface, but gated:** all new behavior sits behind
`enableConferenceRoomChat`, default OFF; flag-OFF rendering is locked by
frozen `*Classic` forks plus an on/off parity test suite
- **Classic forks are frozen at the fork point (`e3aada1d`):** master
features added to the live thread component after that point (assignee
handoff chips, run status badge, composer mention coach) render in the
flag-ON path; the flag-OFF task thread keeps the fork-point behavior
until the experiment graduates (forks deleted) or is dropped (forks
restored as canonical). Called out for reviewer attention.
- **Merge-conflict resolution in `IssueChatThread.tsx`** (commit
`21a5642a`) deserves reviewer eyes: master's new handoff/run-status
components were kept; the base toast-style no-assignee flow remains
replaced by the AlertDialog flow introduced on this branch
- Schema/server changes are additive (one optional boolean instance
setting); no migrations of existing data
## Model Used
- Claude (Anthropic) via Claude Code running in the Paperclip agent
harness (agent: ClaudeCoder)
- Branch implemented across multiple agent sessions on Claude Opus-class
models with extended thinking + tool use (file edits, shell, Playwright
screenshots); merge/PR session model ID as reported by the harness:
`claude-fable-5` (Claude Code CLI)
- All code was agent-authored and board-reviewed through Paperclip issue
threads (plans, wireframes, confirmations) before merging
## 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 (none
required — experimental flag, self-documenting Settings card; noted
above)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green (run 3 on `8af3041a`: all 16
gates SUCCESS, incl. e2e and all 4 serialized-suite shards)
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(re-review verdict: Confidence 5/5, “Safe to merge”; all 4 round-1
findings fixed + confirmed resolved; both summary notes addressed in
`8af3041a`)
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents increasingly depend on reusable skills, so the control plane
needs a first-class way to browse, inspect, install, version, and attach
those skills.
> - The old skills surface was mostly operational plumbing; it did not
give operators a store-like discovery flow, canonical detail URLs, rich
source/version context, or creation paths.
> - The backend also needed stronger contracts around company skill
metadata, versions, install counts, runtime materialization, and adapter
skill preferences.
> - This pull request builds the Skills Store foundation across DB,
shared contracts, server routes/services, UI, and Storybook.
> - The benefit is a more inspectable, operator-friendly skill workflow
that still preserves company-scoped control-plane boundaries and agent
runtime behavior.
## Linked Issues or Issue Description
No GitHub issue exists for this Paperclip work item. Paperclip task
refs: PAP-10846 and PAP-10921.
Feature request:
Paperclip operators need a single Skills Store experience where company
skills can be discovered, inspected, created, versioned, installed, and
attached to agents without relying on scattered operational screens or
implicit runtime state.
Related PR search:
- Searched GitHub for `Skills Store`, `company skills`, and `skill
detail`.
- Found several open skills-related PRs such as #7809 and #4409, but no
duplicate PR for this end-to-end Skills Store branch.
## What Changed
- Added the Skills Store backend foundation: company skill schema
fields, migrations, shared types/validators, and expanded server skill
routes/services.
- Added skill discovery, category navigation, canonical skill detail
routes, tabs, source attribution, version snapshots/diffs, install count
backfill, and creation flows.
- Updated agent skill preference handling so version selections survive
runtime mention injection and runtime skill materialization honors
pinned versions.
- Preserved unversioned skill assignments as live/current selections
instead of silently pinning them to the current version at assignment
time.
- Added focused regression coverage for company skill routes/services,
route helpers, UI behavior, skill version diffs, and runtime skill
version pins.
- Added Storybook coverage for Skills Store discovery/detail states and
updated the main layout navigation.
- Addressed Greptile findings around version creation races,
soft-deleted comments, fork metadata scoping, GitHub skill directory
fallback, runtime snapshot materialization, shared runtime
skill-selection helpers, and version-assignment semantics.
## Verification
- `pnpm exec vitest run
server/src/__tests__/heartbeat-project-env.test.ts
server/src/__tests__/heartbeat-runtime-skills.test.ts`
- `pnpm exec vitest run
packages/shared/src/validators/company-skill.test.ts`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/company-skills-service.test.ts`
- `pnpm exec vitest run
cli/src/__tests__/company-import-export-e2e.test.ts`
- `pnpm exec vitest run
server/src/__tests__/agent-skills-routes.test.ts`
- `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/company-skills-routes.test.ts
server/src/__tests__/heartbeat-runtime-skills.test.ts`
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts`
- `pnpm exec vitest run ui/src/components/IssueProperties.test.tsx -t
"edits existing custom assignee model options from the properties pane"`
- `pnpm --filter @paperclipai/server typecheck`
- GitHub checks are green on `0823957a2`: Build, Canary Dry Run, General
tests, Typecheck + Release Registry, serialized server suites, e2e,
policy/review, Socket, Snyk, and aggregate `verify`.
- Greptile Review succeeded on `0823957a2` with `40 files reviewed, 0
comments added`; GitHub unresolved review threads: 0.
Not run in this heartbeat:
- Browser screenshot capture for the UI changes. This PR intentionally
omits screenshots per the Paperclip task direction not to add design
screenshots/images.
## Risks
- Broad feature branch touching DB, shared contracts, server, and UI;
reviewers should still scan merge conflicts carefully if `master` moves
again before landing.
- Skill version/runtime behavior is sensitive: pinned skill versions
must stay pinned while default selections should continue following the
current version.
- UI polish should get normal reviewer/browser attention before merge
because this PR includes a large Skills Store surface and screenshots
were intentionally omitted.
> 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-based coding agent with tool use and local command
execution.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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 (intentionally omitted per PAP-10921 direction)
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The gemini-local adapter runs gemini-cli headlessly, including on
remote/sandboxed execution targets where the adapter manages a dedicated
HOME under the runtime root
> - gemini-cli hard-refuses headless runs with "Invalid auth method
selected." unless `$HOME/.gemini/settings.json` persists an auth
selection; setting `GEMINI_DEFAULT_AUTH_TYPE` alone does NOT satisfy it
(proven in an isolated pod)
> - With a managed HOME the runtime root replaces the image home, so any
settings.json baked into the agent image (or the user's real home) is
invisible to the CLI, and every sandboxed gemini run dies before doing
any work
> - This affects any sandbox provider that runs gemini with API-key auth
through the managed-HOME path (SSH, E2B, Daytona, Kubernetes, or any
other remote execution target); it is a headless-execution bug fix, not
gateway- or deployment-specific behavior
> - This pull request makes the adapter pre-select the `gemini-api-key`
auth type in the managed `$HOME/.gemini/settings.json` whenever a
Gemini/Google API key is present, writing both settings schema
generations and never touching an existing settings.json
> - The benefit is that gemini agents actually run headlessly on remote
and sandboxed execution targets without any manual settings provisioning
## Linked Issues or Issue Description
No existing issue; describing the bug in-PR (bug template fields):
- **What happened:** Headless gemini-local runs on remote/sandboxed
execution targets fail immediately with `Invalid auth method selected.`
even though `GEMINI_API_KEY` is provided.
- **Expected:** Providing the API key should be enough for a headless
run to authenticate and proceed.
- **Root cause:** gemini-cli requires an auth selection persisted in
`$HOME/.gemini/settings.json` for non-interactive runs; the
`GEMINI_DEFAULT_AUTH_TYPE` env var does not substitute for it (verified
in an isolated pod with only the env var set). The adapter's
managed-HOME execution path points HOME at the runtime root, so any
pre-existing settings.json (image-baked or user home) is hidden and the
CLI finds no auth selection.
- **Reproduction:** Run the gemini-local adapter against a
remote/sandboxed execution target with `GEMINI_API_KEY` set and no
settings.json under the managed HOME; the run aborts with the error
above.
- Duplicate/related search: no existing PR or issue addresses this;
closest related is #7693 (bundles gemini-cli in the Docker image), which
makes the CLI available but does not fix headless auth selection.
## What Changed
- `packages/adapters/gemini-local/src/server/execute.ts`: after
provisioning the managed HOME, when a Gemini/Google API key is present,
write `$HOME/.gemini/settings.json` pre-selecting `gemini-api-key` auth.
Both settings schema generations are written (legacy top-level
`selectedAuthType` and current `security.auth.selectedType`) so old and
new gemini-cli versions are covered.
- The write is strictly scoped to the managed HOME (the per-run runtime
root on sandbox transports). On non-managed remote targets (SSH), where
the remote home is the user's real home and existing settings remain
visible to the CLI, the adapter creates nothing (review feedback, P1).
- The write is guarded by `[ -f ... ] ||` so a user-shipped
settings.json (e.g. via workspace) is never overwritten.
- The key-presence gate checks the run env AND the host process env
(`GEMINI_API_KEY` / `GOOGLE_API_KEY`): in sandboxed paths the key never
enters the adapter's run env; it reaches the agent pod via the sandbox
provider's per-run secret (env passthrough from the host env), so the
host env is the correct signal there.
- `packages/adapters/gemini-local/src/server/execute.remote.test.ts`: a
new sandbox-transport test asserts the settings.json write lands under
the per-run runtime root (path + `gemini-api-key` content), and the SSH
test asserts no settings.json is created on a non-managed home.
## Verification
- `npx vitest run packages/adapters/gemini-local`: 3 files, 17 tests,
all pass.
- `pnpm --filter @paperclipai/adapter-gemini-local typecheck` and
`build`: clean (test file is covered by the package tsconfig `include`).
- Negative control: in an isolated pod, gemini-cli with `GEMINI_API_KEY`
+ `GEMINI_DEFAULT_AUTH_TYPE` set but no settings.json still fails with
`Invalid auth method selected.`; with the settings.json written by this
change, the run proceeds.
- Verified end-to-end: a gemini agent in a hardened Kubernetes (gVisor)
sandbox completed a real task (with `GOOGLE_GEMINI_BASE_URL` pointing at
a GenAI-compatible endpoint), producing a billed usage row. That
deployment supplies the verification evidence; the fix applies to any
sandbox provider running gemini with API-key auth.
## Risks
- Low risk. The new write only fires on the managed-HOME path (per-run
runtime root) when an API key is present, and only when no settings.json
exists yet, so existing setups, real user homes on SSH targets, and
user-provided settings are unaffected.
- If a future gemini-cli changes the settings schema again, the file may
need a third generation key; both current generations are written today.
## Model Used
- Claude (Anthropic), Claude Opus 4.8, 1M context, extended thinking,
with tool use (code execution / shell) 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
- [ ] If this change affects the UI, I have included before/after
screenshots (no UI change)
- [x] I have updated relevant documentation to reflect my changes (code
comments document the behavior; no doc pages cover managed-home auth)
- [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
(review requested)
- [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 `codex-local` adapter runs the OpenAI Codex CLI; Paperclip
already maintains a managed `CODEX_HOME` per company and ships it to
remote/sandboxed execution targets
> - Deployments increasingly put an OpenAI-compatible LLM gateway
between the harness and the model for cost, governance, or
data-residency reasons: LiteLLM, OpenRouter, Portkey, Kong, a corporate
proxy, self-hosted models (vLLM/Ollama), or region-pinned/sovereign
endpoints. But Codex has no CLI flag or env var for a custom endpoint:
its only mechanism is `[model_providers.<id>]` tables (with `base_url`,
`env_key`, `wire_api`) in `$CODEX_HOME/config.toml`, selected by a
root-level `model_provider` key
> - Today there is no supported way to get such provider config into the
managed `CODEX_HOME`, so gateway routing requires hand-editing files the
adapter owns and regenerates
> - This pull request adds the codex analogue of #7837's opencode
mechanism: a `PAPERCLIP_CODEX_PROVIDERS` JSON env var whose shape maps
1:1 onto codex's TOML schema, merged into the managed `config.toml` so
the existing asset-shipping + `env.CODEX_HOME` mechanics deliver it to
local and sandboxed runs alike; nothing here is specific to one hosting
setup
> - The benefit is Codex works behind any OpenAI-compatible gateway with
config only; with no env set, behavior is unchanged
## Linked Issues or Issue Description
No existing issue; describing in-PR (feature / adapter enhancement).
- **Gap:** there is no supported way to register a custom/gateway
`[model_providers.*]` endpoint for `codex-local`. Codex's only
custom-endpoint mechanism is `config.toml` (`base_url` + `env_key` +
`wire_api`, selected via the root `model_provider` key), and the adapter
owns/regenerates the managed `CODEX_HOME`, so operators cannot durably
hand-edit it.
- Related: #7837 (the opencode-local analogue of this change, same
env-driven gateway-routing pattern). Searched for duplicate/related PRs:
no existing codex-local gateway/provider-routing PR found.
> Note on ROADMAP: this is adapter-level, opt-in config (defaults
unchanged) that *enables* gateway routing for one harness; it is not the
core "Cloud / Sandbox agents" platform work itself.
## What Changed
- New `prepareCodexRuntimeConfig()`
(`packages/adapters/codex-local/src/server/runtime-config.ts`): reads
`PAPERCLIP_CODEX_PROVIDERS` (run env first, then `process.env`), shaped
as `{"providers": {"<id>": {base_url, env_key, wire_api, ...}},
"model_provider": "<id>"}`, and merges it into the managed
`CODEX_HOME`'s `config.toml`. No-op when unset or empty.
- A malformed value (invalid JSON, not a JSON object, no `providers`
object, no usable provider entries, or individual entries with empty
names or non-object values, which are skipped by name) is never silently
dropped: each case surfaces a distinct, user-visible note (via the
prepare notes, which flow into command notes + `onLog`) and unusable
input leaves `config.toml` untouched.
- Merge is marker-delimited and TOML-correct: existing `config.toml`
content is preserved between two managed blocks. Root keys (e.g.
`model_provider`) are prepended **before the first table header** (TOML
root-region rule), `[model_providers.*]` tables are appended.
Pre-existing same-name provider sections and root `model_provider` keys
are excised so the managed definitions win without duplicate-table parse
errors.
- `{env:VAR}` placeholders are expanded server-side for
literal-credential fields; `env_key` indirection remains the preferred
path.
- Crash-safe restore: prepare writes a pre-run backup
(`config.toml.paperclip-backup`) before the merged file; `cleanup()`
restores the original in the execute `finally` and removes the backup.
If a run never reaches `cleanup()` (a throw during the setup between
prepare and execution, or SIGKILL), the next prepare restores the
original from the backup with full fidelity, including user
`[model_providers.*]` sections the merge excised (review feedback, P2);
plain block-stripping remains the fallback for pre-backup state.
- An explicit adapter-config `env.CODEX_HOME` override is treated as
user-managed: no merge, surfaced as a command note.
- Dependency-free hand-emitted TOML (strings/numbers/booleans, arrays of
scalars, plain objects as inline tables); basic strings escape
U+0000-U+001F and U+007F per TOML 1.0 (review feedback, P2). Merged
output was additionally validated locally with python tomllib during
development; the committed tests assert the structural invariants.
- `execute.ts` wiring: `prepareCodexRuntimeConfig` runs after
`prepareManagedCodexHome` (before the home ships to the remote target),
notes surface via `onLog` + command notes, and the `finally` calls
`cleanup()`.
**Note for reviewers:** current codex removed `wire_api = "chat"`
(openai/codex#10157, Feb 2026), so gateway provider configs must use
`wire_api = "responses"`, i.e. the gateway must speak `/v1/responses`.
The adapter passes the value through verbatim; this is a codex-side
constraint worth knowing when configuring it.
## Verification
- `pnpm --filter @paperclipai/adapter-codex-local build` and
`typecheck`: tsc clean against current `master`
- `pnpm exec vitest run packages/adapters/codex-local`: 45 passing
(incl. 17 `runtime-config` tests: fresh-merge + cleanup restore,
root-region placement, same-name provider override, inline
tables/arrays, DEL escaping, `{env:}` expansion from run env +
`process.env`, per-case malformed-input notes with `config.toml`
untouched, skipped-entry notes alongside a successful merge, silent
no-op when unset/empty, explicit-`CODEX_HOME` skip note, backup restore
of excised user sections after an interrupted run, backup removal on
cleanup, stale-block self-heal, re-run replacement)
- Verified end-to-end: a codex agent in a hardened Kubernetes (gVisor)
sandbox completed a real task routed through an OpenAI-compatible
gateway's `/v1/responses`, with a billed usage row recorded on the
gateway. That deployment supplies the verification evidence; the
mechanism is gateway-agnostic.
## Risks
Low. Entirely env-driven and opt-in; with `PAPERCLIP_CODEX_PROVIDERS`
unset the adapter never touches `config.toml` and behavior is
byte-identical to before. The merge preserves user content, restores the
original file on cleanup, and survives interrupted runs via the pre-run
backup; malformed input surfaces a visible note and is ignored without
touching `config.toml`. No migration/UI impact.
## 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 (adapter-level opt-in config enabling
gateway routing; not the core sandbox-platform work, noted above)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (#7837 is the opencode analogue; no codex-local duplicate
found)
- [x] I have either (a) linked existing issues 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 (n/a, no UI)
- [ ] I have updated relevant documentation to reflect my changes (env
var documented inline; no central doc references the adapter env yet)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green (green on the previous head;
re-running on the final note-copy polish commit)
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(both review P2s are fixed at head: the interrupted-run restore via the
pre-run backup and the U+007F escaping; a re-review is requested for the
note-copy polish)
- [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 `pi-local` adapter runs the Pi coding agent, including inside
remote/sandboxed execution targets; Pi resolves `--provider P --model M`
by an exact (provider, id) match against its model registry, and it has
no base-url CLI flag or env var: a `models.json` in its agent config dir
(`$PI_CODING_AGENT_DIR`, falling back to `$HOME/.pi/agent`) is its only
mechanism for custom or OpenAI/Anthropic-compatible endpoints
> - Deployments increasingly put an LLM gateway between the harness and
the model for cost, governance, or data-residency reasons: LiteLLM,
OpenRouter, Portkey, Kong, a corporate proxy, self-hosted models
(vLLM/Ollama), or region-pinned/sovereign endpoints. Today there is no
supported way to get such provider config into Pi's registry for
orchestrated runs
> - The opencode adapter gained the equivalent capability in #7837 and
codex in #7919; this pull request is the Pi analogue, so the harness
layer stays gateway-agnostic regardless of which CLI an agent uses;
nothing here is specific to one hosting setup
> - This pull request reads `PAPERCLIP_PI_PROVIDERS` (Pi's `models.json`
`providers` shape), materialises a managed `models.json` in a temp
agent-config dir, points `PI_CODING_AGENT_DIR` at it, and ships it to
remote execution targets with the run
> - The benefit is Pi works behind any compatible gateway with config
only; with no env set, behavior is unchanged
## Linked Issues or Issue Description
No existing issue; describing in-PR (feature / adapter enhancement).
- **Gap:** there is no supported way to register custom/gateway
providers + models for `pi-local`. Pi's only custom-endpoint mechanism
is a `models.json` in its agent config dir, and orchestrated (especially
sandboxed) runs have no way to provision one declaratively.
- Related: #7837 (the opencode-local analogue, same env-driven
gateway-routing pattern) and #7919 (the codex-local analogue). Searched
for duplicate or related PRs: no existing pi-local
gateway/provider-routing PR found.
> Note on ROADMAP: this is adapter-level, opt-in config (defaults
unchanged) that *enables* gateway routing for one harness; it is not the
core "Cloud / Sandbox agents" platform work itself.
## What Changed
- New `packages/adapters/pi-local/src/server/runtime-config.ts`:
`preparePiRuntimeConfig()` reads `PAPERCLIP_PI_PROVIDERS` (a JSON object
in pi's `models.json` `providers` shape) from the run env, then
`process.env`. When set, it expands `{env:VAR}` placeholders (run env
first, then process env; unresolvable placeholders left intact), writes
`{"providers": ...}` to a managed temp dir as `models.json`, and returns
env with `PI_CODING_AGENT_DIR` pointing at it plus a cleanup handle.
- `execute.ts`: the prepared dir ships to remote execution targets as
the managed-runtime asset `agentConfig` (same mechanism as opencode's
`xdgConfig`), and `PI_CODING_AGENT_DIR` is repointed to the in-target
path; cleanup runs in `finally`.
- Misconfiguration is visible, not silent: a set-but-unusable
`PAPERCLIP_PI_PROVIDERS` (invalid JSON, not an object, no provider
objects) surfaces an explanatory note instead of proceeding unconfigured
into an opaque model-not-found failure later, and provider entries with
non-object values are skipped with a note naming them. Unset/empty stays
a silent no-op (feature off).
- Defaults unchanged: with `PAPERCLIP_PI_PROVIDERS` unset, the adapter
behaves byte-for-byte as before, for local runs and for every existing
sandbox provider.
## Verification
- All pi-local tests green against this base (new: providers written
verbatim, `{env:VAR}` expansion from run env/process env/unresolvable,
no-op when unset, `PI_CODING_AGENT_DIR` set and shipped, the
misconfiguration notes incl. skipped non-object entries, remote asset
sync + env repoint). Typecheck and build clean.
- Production end-to-end evidence (our deployment, used as verification,
not as the scope of the change): a pi agent in a Kubernetes gVisor
sandbox resolved a custom provider from the shipped `models.json`,
completed an assigned issue through an Anthropic-compatible gateway, and
landed a billed usage row.
## Risks
Low. The entire feature is opt-in behind one env var; the only behavior
change when it is set is the intended one. The managed dir replaces the
host agent dir for the run by design (credentials travel inside the
provider config or via env-key indirection), which is the correct
posture for orchestrated runs. No migration/UI impact.
## 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 (adapter-level opt-in config enabling
gateway routing; not the core sandbox-platform work, noted above)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (#7837 and #7919 are the opencode/codex analogues; no
pi-local duplicate found)
- [x] I have either (a) linked existing issues 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 (n/a, no UI)
- [ ] I have updated relevant documentation to reflect my changes (env
var documented inline; no central doc references the adapter env yet)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green (green on the previous head;
re-running on the final note-copy polish commit)
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(both prior review findings are fixed at head: the indirect notes-based
guard is now an explicit `agentConfigDir` handle, and a failed
`models.json` write no longer leaks the temp dir; a re-review is
requested for the note-copy polish)
- [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 `opencode-local` adapter runs the OpenCode harness; its
model/provider routing assumes built-in providers (anthropic/openai/...)
and their default models
> - Deployments increasingly put an OpenAI/Anthropic-compatible LLM
gateway between the harness and the model for cost, governance, or
data-residency reasons: LiteLLM, OpenRouter, Portkey, Kong, a corporate
proxy, self-hosted models (vLLM/Ollama), or region-pinned/sovereign
endpoints. But OpenCode only resolves `--model provider/model` when the
model is registered in a provider's `models` map, and
`OPENCODE_ALLOW_ALL_MODELS` does NOT bypass its internal `getModel()`
> - Several lanes also fall back to built-in default models the gateway
may not serve: the auxiliary/title model (e.g. `claude-haiku-*`) and the
budget/recovery "cheap" lane (`openai/gpt-5.1-codex-mini`); these abort
runs with "no keys found that support model"
> - This pull request makes the adapter's provider/model wiring
declarative via env, so any such deployment can register gateway models
+ pin the auxiliary/budget lanes without code changes; nothing here is
specific to one hosting setup
> - The benefit is OpenCode works behind any compatible gateway with
config only; with no env set, behavior is unchanged
## Linked Issues or Issue Description
No existing issue; describing in-PR (feature / adapter enhancement).
- **Gap:** there is no supported way to register custom/gateway
providers + models for `opencode-local`, nor to pin the auxiliary
(title-gen) and budget (recovery) model lanes, so routing OpenCode
through a gateway fails at `getModel()` or on the default helper models.
- Related: #5737 (exe.dev sandbox installs for gemini/opencode local),
#5823 (unblock claude_local on remote sandbox providers).
> Note on ROADMAP: this is adapter-level, opt-in config (defaults
unchanged) that *enables* gateway routing for one harness; it is not the
core "Cloud / Sandbox agents" platform work itself. Happy to
redirect/discuss in #dev if preferred.
## What Changed
- `PAPERCLIP_OPENCODE_PROVIDERS`: merge custom/extended providers
(OpenCode `provider` shape) into the runtime `opencode.json`, so gateway
models are registered and `--model provider/model` resolves. `{env:VAR}`
placeholders are expanded server-side (so a key need not depend on the
sandbox run env).
- A malformed `PAPERCLIP_OPENCODE_PROVIDERS` is no longer silently
ignored: invalid JSON, a non-object value, and individual provider
entries with non-object values (which are skipped by name) each append a
visible note to the run notes so the misconfiguration is diagnosable
(addresses both review P1s).
- `PAPERCLIP_OPENCODE_SMALL_MODEL` / `PAPERCLIP_OPENCODE_CHEAP_MODEL`:
pin the auxiliary (title-generation) and budget (recovery-retry) lanes
to gateway-served models; defaults unchanged.
- Honour `OPENCODE_ALLOW_ALL_MODELS` on the **remote** execution path
too (was local-only, a parity gap).
- `PAPERCLIP_OPENCODE_PRINT_LOGS`: optional toggle adding `--print-logs`
so OpenCode logs surface on stderr for diagnosing remote/sandbox runs.
- `buildOpenCodeModelProfiles()` guards its `process.env` default with
`typeof process` so the shared client/server module stays browser-safe
(a bare `process.env` at module load threw ReferenceError in the browser
under Vite dev middleware and broke UI rendering in the e2e lane).
## Verification
- `pnpm --filter @paperclipai/adapter-opencode-local build` and
`typecheck` (tsc clean)
- `pnpm exec vitest run packages/adapters/opencode-local/src` shows 33
passing (incl. new tests for the provider merge, `{env:}` expansion, the
malformed/non-object/skipped-entry provider notes, small/cheap-model
resolution, and the remote allow-all bypass)
- Manually verified end-to-end against a real
OpenAI-/Anthropic-compatible gateway: with the providers + small/cheap
model set, both the title-gen and main task route to the configured
gateway model and the agent completes (a real completion is returned and
billed). That deployment supplies the verification evidence; the
mechanism is gateway-agnostic.
## Risks
Low. Everything is env-driven and opt-in; with no env set the generated
config output is unchanged, and the cheap model profile keeps its model
(the only difference is its updated human-readable description).
Defaults preserved: built-in providers, Codex-mini cheap lane with
`variant: low`, no `--print-logs`. No migration/UI impact.
## 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 (adapter-level opt-in config enabling
gateway routing; not the core sandbox-platform work, noted above)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (#5737, #5823)
- [x] I have either (a) linked existing issues 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 (n/a, no UI)
- [ ] I have updated relevant documentation to reflect my changes (env
vars documented inline via comments; no central doc references the
adapter env yet)
- [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
(the P1 about silently dropped malformed providers JSON is addressed in
6eeb803, the follow-up P1 about silently skipped non-object entries in
2f4045a; the latest review has no further findings, and a re-review is
requested for the final note-copy/test-fixture polish at head)
- [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 execution subsystem runs those agents in environments (local,
ssh, sandbox), and sandbox-provider plugins let an environment
materialize per-run sandboxes
> - Stage 1 (#5790) contributed a first-party Kubernetes
sandbox-provider plugin, but the server core has no way to adopt it
operationally: no per-run adapter selection, no way to force an instance
onto sandboxed execution, no declarative adapter/model configuration,
and the plugin must be installed by hand
> - Without this, a multi-tenant or security-conscious deployment cannot
guarantee that agent runs never execute on the host, and a single
environment cannot serve agents with different harnesses
> - This pull request adds the server + SDK integration: per-run
adapterType on the lease protocol, an env-gated forced-Kubernetes
execution policy with provisioning and a per-run allowlist guard, a
declarative adapter registry and model list, in-cluster env passthrough
for sandbox plugin workers, fail-safe auto-install of the bundled
plugin, and the matching UI affordance
> - The benefit is that sandbox-provider plugins become fully usable for
Kubernetes execution: operators configure everything via environment
variables and GitOps, while self-hosters who set none of the variables
see exactly the behavior they have today
## Linked Issues or Issue Description
Refs #5790 (stage 1 of 3: the Kubernetes sandbox-provider plugin
package).
No existing issue. Feature description: the server core lacks the
integration seams to operate a sandbox-provider plugin as the mandatory
execution path of an instance. This PR is stage 2 of 3 of the staged
Kubernetes contribution; stage 3 will contribute the agent runtime
images and their build pipeline.
## What Changed
One line per piece:
- `packages/plugins/sdk/protocol.ts`: optional `adapterType` on
`PluginEnvironmentAcquireLeaseParams` so a provider can select the
runtime image per run; existing providers simply ignore it
- `server/services/environment-runtime.ts` +
`environment-run-orchestrator.ts`: thread the agent's adapter type into
both lease-acquiring drivers, including the heartbeat path (the two call
sites have historically drifted, hence the pinned test)
- `server/services/environments.ts`: `ensureKubernetesEnvironment` /
`findKubernetesEnvironment`, an idempotent managed Kubernetes
environment per company, identified by a metadata marker and refreshed
(not recreated) on config change; `timeoutMs` rides on the config for
slow cold-start leases
- `server/services/execution-allowlist.ts`: pure (driver, provider,
policy) -> allow/deny guard; `executionMode=kubernetes` only allows the
kubernetes sandbox provider
- `server/services/execution-policy-bootstrap.ts` + startup hook in
`server/index.ts`: parse `PAPERCLIP_EXECUTION_MODE` / `PAPERCLIP_K8S_*`,
persist `executionMode` into instance general settings, and provision
the managed environment for every company; fails loud on
misconfiguration
- `server/services/heartbeat.ts`: when the policy forces Kubernetes, pin
run selection to the managed environment (also overriding any persisted
workspace environment id), refuse to fall back to local, and re-check
the actually acquired environment against the allowlist as defense in
depth
- `server/services/adapter-registry-bootstrap.ts` + shared
`AdapterRegistryEntry` type/validator: declarative `PAPERCLIP_ADAPTERS`
registry (inline JSON or file) that reconciles adapter availability at
startup and rides on the Kubernetes environment config
- `server/services/adapter-models-env.ts` + `adapters/registry.ts`:
`PAPERCLIP_ADAPTER_MODELS` lets an operator declare picker model lists
the server cannot CLI-discover
- `server/services/plugin-loader.ts`: pass
`KUBERNETES_SERVICE_HOST/PORT(_HTTPS)` through to plugin workers that
register environment drivers, so in-cluster API clients can be
constructed; all other host env stays stripped
- `server/app.ts`: fail-safe auto-install of the bundled kubernetes
plugin at boot; no-ops when the bundle is absent and never blocks
startup on error
- `packages/shared` types/validators: `InstanceExecutionMode` on general
settings (optional, strict schema)
- `ui/lib/forced-kubernetes-environment.ts` + `AgentConfigForm`: when
the policy is active, show a read-only Kubernetes environment instead of
the environment picker and default new agents onto the managed
environment
- Tests for every new module plus the adapterType pin in
`heartbeat-plugin-environment` and the managed-environment lifecycle in
`environment-service`
Everything is gated: with `PAPERCLIP_EXECUTION_MODE`,
`PAPERCLIP_ADAPTERS`, and `PAPERCLIP_ADAPTER_MODELS` unset (and no
bundled plugin present), every code path reduces to current behavior.
The per-run `adapterType` is an optional SDK parameter that existing
providers ignore.
## Verification
- `cd server && npx tsc --noEmit`: clean (0 errors); `ui` typecheck also
clean
- Targeted suites all green (11 files, 90 tests): `npx vitest run
server/src/__tests__/heartbeat-plugin-environment.test.ts
server/src/__tests__/environment-service.test.ts
server/src/__tests__/environment-runtime.test.ts
server/src/__tests__/environment-run-orchestrator.test.ts
server/src/__tests__/plugin-database.test.ts
server/src/services/execution-policy-bootstrap.test.ts
server/src/services/execution-allowlist.test.ts
server/src/services/adapter-registry-bootstrap.test.ts
server/src/services/adapter-registry-bootstrap.reconcile.test.ts
server/src/services/adapter-models-env.test.ts
packages/shared/src/validators/adapter-registry.test.ts`
- `npx vitest run ui/src/components/AgentConfigForm.test.ts`: green (6
tests)
- Full `npx vitest run server/src/__tests__`: 2323 passed, 1 skipped;
the only failures (heartbeat-process-recovery pid-retry,
workspace-runtime symbolic-ref/git tests) reproduce identically on
pristine `master` in the same environment, so they are
machine-environment issues unrelated to this change;
`server-startup-feedback-export` needed its `services/index.js` mock
extended with the new export and is green
- This integration has been running in production on a hosted
multi-tenant deployment, where it executes agent runs across five
different harnesses through the stage 1 plugin
## Risks
- Low for existing deployments: every behavior is env-gated and the
defaults preserve current semantics; the auto-install block is wrapped
fail-safe and skips silently when the plugin bundle is absent
- `executionMode` is a new optional field on a strict zod schema; absent
input normalizes exactly as before
- The forced policy intentionally fails runs loudly (rather than falling
back to local) when no managed Kubernetes environment exists; this only
affects instances that explicitly set
`PAPERCLIP_EXECUTION_MODE=kubernetes`
## Model Used
Claude Opus 4.8 (claude-opus-4-8, 1M context), extended thinking,
agentic tool use via Claude Code.
## UI screenshots
The UI change is a new read-only "Execution" section in
`AgentConfigForm`, shown only when the instance execution policy forces
Kubernetes (`executionMode=kubernetes`); there is no "before" state for
it (the section did not exist, and instances without the forced policy
render the existing picker unchanged). Captured from the new Storybook
stories added in this PR (`Product/Agent Management`):
Managed Kubernetes environment present (read-only display, no local/SSH
picker):

No managed environment available yet (warning notice, no silent local
fallback):

## 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
🤖 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
> - Sandbox providers are the seam that lets agent runs execute in
isolated environments; today the only first-party remote provider is
Daytona, a hosted third-party service
> - Self-hosters running Paperclip on their own infrastructure (often
Kubernetes already) have no first-party way to run agent sandboxes on a
cluster they control
> - That gap matters for teams with data-residency, sovereignty, or cost
constraints who cannot or will not send workloads to a hosted sandbox
service
> - This pull request adds a Kubernetes sandbox-provider plugin as a
standalone, workspace-excluded package: it implements every
SandboxProvider hook the Daytona provider does, on infrastructure the
operator owns
> - The benefit is that any Paperclip deployment with a Kubernetes
cluster gets multi-tenant, network-isolated, quota-bounded agent
sandboxes with zero new external dependencies
## Linked Issues or Issue Description
No existing issue. Following the feature template:
- **Problem:** Paperclip's remote sandbox execution requires a hosted
third-party provider. Self-hosters cannot run agent sandboxes on their
own Kubernetes clusters with a first-party provider.
- **Proposed solution:** A `@paperclipai/plugin-kubernetes`
sandbox-provider plugin with two backends: long-lived sandboxes via the
[kubernetes-sigs/agent-sandbox](https://github.com/kubernetes-sigs/agent-sandbox)
CRD (multi-command exec, adapter-install pattern) and one-shot
`batch/v1` Jobs (stable APIs only, no extra controllers).
- **Alternatives considered:** Driving kubectl from a generic shell
provider (no lifecycle/lease semantics), or requiring a hosted provider
(exactly the constraint this removes).
## What Changed
This is **stage 1 of 3** of a staged contribution (direction agreed with
maintainers): the plugin package alone. Stage 2 (server integration:
lease params, provider registration) and stage 3 (agent runtime images +
CI) are companion PRs that will be cross-linked from a comment here.
- New package `packages/plugins/sandbox-providers/kubernetes`
(workspace-excluded, like the path already carved out in
`pnpm-workspace.yaml`): src, unit + kind integration tests, operator
prerequisite manifests, README, smoke-test guide
- Implements the full SandboxProvider hook surface the Daytona provider
implements: `validateConfig`, `probe`, `acquireLease`, `resumeLease`,
`releaseLease`, `destroyLease`, `realizeWorkspace`, `execute`
- Two backends: `sandbox-cr` (default; long-lived pod via the
agent-sandbox `Sandbox` CR, supports multi-command exec) and `job`
(one-shot `batch/v1` Job; nothing beyond k8s 1.27+ required)
- Per-run adapter resolution: one environment serves mixed harnesses;
the per-run `adapterType` hint is read through a local optional type
extension, so the plugin typechecks and builds against the current
plugin SDK and simply falls back to the environment's configured default
adapter until stage 2 lands
- Exec-env wrapping: the Kubernetes exec API carries no environment, so
commands are wrapped to receive the run's env
- Fast-upload interception for workspace realization, scoped per lease
- Per-tenant isolation: derived namespace per company, RBAC,
ResourceQuota, restricted-PSS pod security (runAsNonRoot, drop ALL,
seccomp RuntimeDefault, no SA token automount)
- Network egress policy in two flavors: native `NetworkPolicy` and
`CiliumNetworkPolicy` (FQDN allowlists)
- Image allowlist with glob matching, registry override, and per-run
image override validation
- Per-run Kubernetes Secrets carrying agent credentials, ownerRef'd to
the Job or Sandbox CR for cascade GC
## Verification
- Standalone build, exactly as the README documents:
```bash
cd packages/plugins/sandbox-providers/kubernetes
pnpm install --ignore-workspace
pnpm test # 147 unit tests, 17 files, all green
pnpm typecheck # clean against the in-repo plugin SDK on master
pnpm build # dist/ emitted, manifest + worker entrypoints present
```
- A kind-cluster end-to-end integration test is included
(`RUN_K8S_INTEGRATION_TESTS=1 pnpm test
test/integration/end-to-end-run.test.ts`)
- Beyond CI: this provider has been verified in a production
multi-tenant deployment against five harnesses (opencode, pi, codex,
gemini, claude code) with real billed runs
## Risks
- **Zero behavior change for any existing deployment.** The package is
workspace-excluded; nothing in the server imports or loads it until
stage 2's integration lands. No existing code paths are touched.
- The default `sandbox-cr` backend depends on an alpha CRD
(`agents.x-k8s.io/v1alpha1`); the README flags this and the `job`
backend uses only stable APIs as a fallback.
- Risk surface is confined to deployments that explicitly install and
configure the plugin.
- The default runtime images (`ghcr.io/paperclipai/agent-runtime-*`) are
published by the stage 3 companion PR (#7934); until that lands,
deployments must point `runtimeImage` at their own images.
## Model Used
Claude Opus 4.8 (1M context), extended thinking, with tool use (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
- [ ] 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 (pending this push)
- [ ] 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 orchestrates AI agents through pluggable local adapters;
codex_local wraps OpenAI's `codex` CLI.
> - The codex_local adapter declares a hard-coded
`DEFAULT_CODEX_LOCAL_MODEL = "gpt-5.3-codex"` and multiple Paperclip
consumers (UI build-config, server route, OnboardingWizard, NewAgent
form, AgentConfigForm) fall back to it when the operator doesn't pick a
model.
> - That model — and every `*-codex` model plus the older
`gpt-5/5.1/5.2` lines — is API-key-only. Codex CLI rejects them on
ChatGPT subscription auth with "The 'gpt-5.3-codex' model is not
supported when using Codex with a ChatGPT account."
> - Every codex_local agent created through the default onboarding path
inherits this pin and breaks on its first heartbeat for any user authed
via `codex login` (ChatGPT).
> - claude_local already takes the right shape: its build-config only
sets `adapterConfig.model` when the operator actually picked one, and
falls through to whatever default `claude` CLI uses.
> - Codex CLI's own default is auth-mode-aware. ChatGPT-subscription
accounts get `gpt-5.5`; API-key accounts get the codex-tuned default. A
Paperclip-side pin masks this and downgrades whichever group it wasn't
built for.
> - This PR makes codex_local match claude_local's shape: omit
`adapterConfig.model` when the user picks "default," and let the CLI
choose. Subscription users stop breaking; API-key users stop getting
downgraded.
> - The benefit is auth-mode-correct defaults with no Paperclip-side
hard pin, plus future-proofing: when OpenAI bumps the CLI default we
inherit it for free.
## What Changed
- `packages/adapters/codex-local/src/ui/build-config.ts` — only set
`adapterConfig.model` when the operator picked one (parity with
`packages/adapters/claude-local/src/ui/build-config.ts`).
- `server/src/routes/agents.ts` — drop the codex_local-specific
`next.model = DEFAULT_CODEX_LOCAL_MODEL` fallback in
`applyCreateDefaultsByAdapterType`. Bypass-sandbox default is left in
place (security posture, not a model choice).
- `ui/src/pages/NewAgent.tsx`, `ui/src/components/AgentConfigForm.tsx`,
`ui/src/components/OnboardingWizard.tsx` — stop pre-populating the model
field with `DEFAULT_CODEX_LOCAL_MODEL` when the user selects the Codex
adapter. Other adapters' defaults (gemini_local, cursor, opencode_local)
are unchanged.
- `DEFAULT_CODEX_LOCAL_MODEL` is preserved as an exported constant for
downstream consumers / plugin authors who want to opt in to a pin; we
just stop forcing it on operators who didn't ask for one.
- Test: assert `buildCodexLocalConfig` omits `model` when input is
blank.
## Verification
- `pnpm exec vitest run
packages/adapters/codex-local/src/ui/build-config.test.ts
packages/adapters/codex-local/src/server/codex-args.test.ts
server/src/__tests__/adapter-registry.test.ts
server/src/__tests__/heartbeat-model-profile.test.ts
server/src/__tests__/agent-permissions-routes.test.ts` → 74/74 passing
- `pnpm exec vitest run ui/src/lib/duplicate-agent-payload.test.ts
ui/src/lib/acpx-model-filter.test.ts` → passing
- `pnpm tsc --noEmit -p .` → clean
- Live: I separately verified live during initial investigation that on
ChatGPT-subscription auth, `gpt-5.3-codex` is rejected and `gpt-5.5` is
what Codex CLI picks by default. Omitting model lets the CLI handle
that.
## Risks
- Telemetry: any sink that reads `adapterConfig.model` for cost
attribution will now see the empty/omitted case more often. The CLI
emits the actually-used model in its event stream; downstream telemetry
should already read from there for accuracy, but worth a check.
- Operator UX: "default" now means "whatever the CLI picks" instead of a
Paperclip-known model. The selectable catalog still includes `gpt-5.5`,
`gpt-5.4`, `gpt-5.3-codex`, etc. for operators who want to pin
explicitly.
- Existing agents are unaffected — their `adapterConfig.model` is
already set; this only changes the *new-agent* default flow.
## Related work
- Depends on: an open catalog-add PR adding `gpt-5.5` to the selectable
model list and to `CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS`. Operators
who want to switch to `gpt-5.5` explicitly need that PR merged first;
this PR is the structural change that makes "default" mean "let the CLI
choose."
- Closes#5371 — codex_local default model selection persists
`gpt-5.3-codex` instead of adapter default (this PR is the exact fix
#5371 proposes).
- Related: #5132 (opencode-local: hire-time default model fails on
ChatGPT-OAuth accounts) — same problem shape on a sibling adapter; not
fixed here but worth tracking for a parallel.
- Related: #5939 (codex_local adapter hardcodes `gpt-5.3-codex-spark`
validation, fails on ChatGPT OAuth accounts regardless of configured
model) — separate validation-path bug; not fixed here.
## Model Used
Claude (Sonnet-class), running inside Paperclip as a claude_local
executor.
## 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
- [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>
## Related work
This PR is the cleanest "add `gpt-5.5` to the codex-local catalog"
change open against master. Several other PRs propose the same
catalog/fast-mode update; they should close as duplicates once this
lands:
- #4646 — Add Codex gpt-5.5 model option
- #6044 — feat(codex-local): add gpt-5.5 to model catalog, default
reasoning to medium, cheap profile xhigh
- #6045 — feat(codex-local): add gpt-5.5 to model catalog, default
medium reasoning, xhigh cheap profile
- #6595 — feat(adapters): add new Codex models (gpt-5.5, gpt-5.4-mini,
gpt-5.3-codex, gpt-5.2)
Related issues this enables (catalog-level surface area):
- #5371 — codex_local default model selection persists `gpt-5.3-codex`
instead of adapter default. This PR makes `gpt-5.5` selectable in the
dropdown; a separate follow-up changes the *default* behavior so users
who don't pick a model are subscription-compatible.
- #5132 — opencode-local: hire-time default model fails on ChatGPT-OAuth
accounts. Sibling adapter, same problem shape; not fixed here but worth
tracking as a parallel for the opencode side.
---
## Thinking Path
> - Paperclip orchestrates AI agents through adapter-backed local and
remote runtimes.
> - The `codex_local` adapter declares built-in model options that feed
the server model list and, in turn, the agent configuration UI dropdown.
> - GPT-5.5 is available in newer Codex environments but was missing
from Paperclip's fallback `codex_local` model list.
> - Operators could still type a manual model ID, but the default
dropdown made the supported path look unavailable.
> - Codex fast mode support is declared separately, so adding GPT-5.5 to
the visible list should also include it in the supported fast-mode set.
> - This pull request adds GPT-5.5 to the built-in Codex local model
options and updates focused tests around argument generation and adapter
model listing.
> - The benefit is a clearer default setup path for agents using GPT-5.5
without changing existing defaults or migrations.
## What Changed
- Added `gpt-5.5` to the `codex_local` fallback model list.
- Added `gpt-5.5` to `CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS`.
- Updated Codex argument tests to cover GPT-5.5 fast mode and preserve
manual-model fast mode behavior.
- Updated adapter model listing tests to assert the Codex fallback list
includes GPT-5.5.
## Verification
- `pnpm exec vitest run
packages/adapters/codex-local/src/server/codex-args.test.ts
server/src/__tests__/adapter-models.test.ts`
- `git diff --check`
- UI note: this is a dropdown data-source change rather than a
layout/component change; the adapter model listing test covers the list
consumed by the UI.
## Risks
- Low risk. This only extends a static fallback model list and fast-mode
allowlist.
- Existing defaults remain unchanged (`gpt-5.3-codex`).
- If a local Codex CLI does not support `gpt-5.5`, selecting it will
still fail at execution time the same way any unavailable manual model
would.
> 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 desktop coding agent, GPT-5-family model. The exact
backing model ID was not exposed by the local runtime; the session used
shell, Git, test execution, and GitHub CLI tool access.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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
- [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: apple <apple@appledeMacBook-Pro.local>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Devin Foley <devin@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents can run in remote/sandboxed environments via the shared
sandbox managed-runtime in `@paperclipai/adapter-utils` (used by
SSH/E2B/Daytona and other sandbox providers), which syncs the workspace
into the sandbox by tarring it up and extracting it inside the pod/host
> - When the sandbox runs the harness as a non-root user whose
home/workspace dir it does not own (for example a hardened, non-root,
gVisor pod with an `emptyDir`-mounted workspace), the workspace upload
aborts before the agent can start
> - Root cause: `createTarballFromDirectory` archives `.`, embedding a
`./` self-entry whose mode/mtime tar then tries to restore onto the
**extraction target directory**; `chmod`/`utime` of `.` fails with
`Operation not permitted` for a non-owner
> - This is not specific to any one deployment: the `.` self-entry EPERM
can bite every sandbox provider built on the shared managed runtime as
soon as the extracting user does not own the target directory, which is
the norm for hardened non-root sandboxes
> - This pull request archives the directory's top-level entries by name
instead of `.`, so there is no `./` self-entry and extraction never
touches the target dir's metadata
> - The benefit is that workspace sync works in any sandbox where the
target dir is non-root or not owned by the extracting user, without
GNU-only tar flags
## Linked Issues or Issue Description
No existing issue; describing in-PR (bug).
- **What happens:** managed sandbox runs that sync the workspace fail at
upload with `tar: .: Cannot utime: Operation not permitted` / `tar: .:
Cannot change mode to ... : Operation not permitted`, aborting the run
before the harness starts.
- **Where:** `packages/adapter-utils/src/sandbox-managed-runtime.ts`, in
`createTarballFromDirectory` (archives `.`).
- **When:** the extraction target directory is not owned by the
(non-root) user extracting the tar inside the sandbox.
- Closely related (different root cause): #6560 (E2B workspace upload +
lease idle failures).
## What Changed
- `createTarballFromDirectory` enumerates the directory's top-level
entries with `fs.readdir` and passes them by name after `--` (guards
flag-like filenames) instead of archiving `.`, eliminating the `./`
self-entry that triggers the EPERM.
- Empty workspaces (legitimate for blank-workspace runs) write a valid
1024-byte all-zero EOF tar instead of invoking `tar` with no paths.
- `--exclude` patterns continue to apply (to nested matches and any
named entry).
## Verification
- `pnpm --filter @paperclipai/adapter-utils build` (tsc clean)
- `pnpm exec vitest run
packages/adapter-utils/src/sandbox-managed-runtime.test.ts` runs green
- New tests: uploaded workspace/asset tarballs contain no `.`/`./`
member yet still extract correctly; empty workspace produces a valid
(no-op) tarball. Existing managed-runtime sync test unchanged.
- Manually verified in a hardened (non-root, gVisor) sandbox pod: with
the fix, the workspace upload that previously aborted with the EPERM now
succeeds. That deployment is the reproduction and verification
environment; the fix itself is provider-agnostic.
## Risks
Low. Behavior is unchanged for owned/root targets; the archive contents
are the same minus the `./` self-entry (which tar recreates implicitly
on extract). Portable across GNU/BSD/busybox tar (no GNU-only
`--no-overwrite-dir`). No API/migration/UI impact.
## 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 (bug fix in shared sandbox utils, not core feature
work)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (#6560)
- [x] I have either (a) linked existing issues 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 (n/a, no UI)
- [ ] I have updated relevant documentation to reflect my changes (n/a,
internal behavior, no docs reference this)
- [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
(the only finding was the description-template P2, resolved by this
description; the latest review covers the current head with no code
findings and all CI gates are green)
- [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
The same 2026-04-30 audit that produced PR #4118 (`Invalid session`
regex extension) and the ENOTFOUND classifier (#4931) identified a third
stuck-session pattern: **13 failures in 7 days, all on a single agent
(Ernest)**, with stderr matching:
```
_ApiError: {"error":{"code":400,"message":"The input token count exceeds
the maximum number of tokens allowed 1048576","status":"INVALID_ARGUMENT"}}
at ChatCompressionService.compress
```
The root cause is that gemini-cli's `ChatCompressionService` blew the 1M
token context limit **during its compression step itself**. Resuming the
same session ID will hit the same wall on the next attempt — the session
is effectively dead the same way it is when "Invalid session identifier"
fires (PR #4118).
## What Changed
Extends the `isGeminiUnknownSessionError` regex in `parse.ts` with two
phrases:
- `exceeds\s+the\s+maximum\s+number\s+of\s+tokens`
- `input\s+token\s+count\s+exceeds`
Both trigger the **existing** fresh-session retry path in
`execute.ts:596` — no new code path. Same extension pattern as PR #4118.
## Verification
- `npx vitest run --project @paperclipai/adapter-gemini-local` → 14/14
pass (11 in `parse.test.ts` + 3 existing in `execute.remote.test.ts`)
- 2 new tests cover the token-overflow patterns
- `pnpm --filter @paperclipai/adapter-gemini-local typecheck` → clean
- Audit query against `heartbeat_runs.stderr_excerpt` confirms regex
matches all 13 occurrences
## Stacking
This PR is stacked on top of #4931 (the ENOTFOUND classifier) which adds
the `parse.test.ts` file. If #4931 merges first, this PR's diff is just
the regex + 2 tests. If this PR is reviewed first, please merge #4931
first to avoid touching the same test scaffolding twice.
## Risks
- **Low.** Single-line regex extension. No new code paths.
- The session-reset path is well-trodden (PR #4118 in flight).
- If a non-Gemini caller produces a stderr containing "exceeds the
maximum number of tokens" by coincidence, they would trigger one
unnecessary fresh-session retry. Not plausible in the gemini-cli output
context where this stderr is sourced.
## Model Used
Claude Opus 4.7 (1M context), Anthropic SDK via Claude Code CLI.
## Checklist
- [x] Thinking path traces from audit data to single-line regex change
- [x] Model specified
- [x] No duplicate of planned core work
- [x] Tests pass locally
- [x] Tests added (2 new)
- [x] N/A — server-side regex
- [x] Internal pattern; no docs change
- [x] Risks documented
- [x] Will address Greptile + reviewer comments before merge
- [x] I searched the GitHub PR list for similar PRs and confirmed this
is not a duplicate (related: #4118 covers the "Invalid session
identifier" regex; this PR extends the same regex with token-overflow
phrases)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - The `codex-local` adapter sets up a per-company Codex home with an
auth symlink. Between `lstat` and `symlink` there is a race where two
concurrent setups can both try to create the same symlink, surfacing
`EEXIST`.
> - Master already handles this at runtime via `createExpectedSymlink`,
which accepts `EEXIST` only when the raced-in entry resolves to the
expected source, and ships a regression test for the tolerated-race path
(symlink already points at the right place).
> - The symmetric path — `EEXIST` raised by a symlink pointing somewhere
else — must stay strictly rejected so a future refactor cannot silently
weaken the guard.
> - This PR locks that in with a single additive test. No production
code change.
## What Changed
- Added one regression test in
`packages/adapters/codex-local/src/server/codex-home.test.ts` that
injects an `EEXIST` whose raced-in symlink target points at a different
file, and asserts:
- `prepareManagedCodexHome` rejects with `code: "EEXIST"`.
- The mismatched symlink is left on disk (we do not blindly overwrite
the raced-in entry).
Complements the existing "treats a concurrently-created expected auth
symlink as success" test already on master.
Refs #5240 (Stack B — codex-home adapter session/auth handling).
## Verification
- `pnpm --filter @paperclipai/adapter-codex-local exec vitest run
src/server/codex-home.test.ts` — passes.
- `pnpm --filter @paperclipai/adapter-codex-local typecheck` — clean.
## Risks
- Test-only change. No production code is modified.
## Model Used
- Provider: Anthropic
- Model: Claude (Opus 4.7)
- Mode/capabilities: tool-using coding agent with shell execution and
test verification
## 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
- [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] I will address all Greptile and reviewer comments before
requesting merge
- [x] I searched the GitHub PR list for similar PRs and confirmed this
is not a duplicate
Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - codex_local runs Codex CLI under a per-company "managed home" so
multiple companies don't trample on each other's session state
> - For `auth.json` specifically, the managed home keeps a SYMLINK to
the user's real `~/.codex/auth.json` rather than a copy — Codex refresh
tokens rotate and are single-use, so any copy goes stale the moment the
source rotates and every subsequent run dies with `401
refresh_token_reused`
> - Older Paperclip versions copied `auth.json` instead. After
upgrading, `ensureSymlink()` saw a regular file at the target, hit `if
(!existing.isSymbolicLink()) return;`, and silently kept the stale copy
> - This pull request makes the upgrade path self-healing inside
`ensureSymlink()` itself: when the target is a regular file, unlink it
and create the symlink, since the target lives under the
Paperclip-managed home and is safe to delete. Directories are skipped to
avoid `EISDIR` on Unix (and inconsistent behavior on Windows)
> - The benefit is operators who upgraded from a copy-based version stop
getting refresh-token-reused failures without having to manually purge
`companies/<id>/codex-home/auth.json`, and the healing is
defense-in-depth even outside the `prepareManagedCodexHome` cleanup path
## What Changed
- `packages/adapters/codex-local/src/server/codex-home.ts` —
`ensureSymlink()` previously bailed out of the
`!existing.isSymbolicLink()` branch, leaving any pre-existing regular
file untouched. Now unlinks and recreates the symlink in that branch via
the existing `createExpectedSymlink()` helper (preserves the EEXIST
race-tolerance behavior added in #5119). A guard skips directories so
the call never throws `EISDIR` and aborts `prepareManagedCodexHome`.
Inline comment explains the safety: target is always under the
company-scoped managed home
(`<paperclipHome>/instances/<id>/companies/<companyId>/codex-home/`),
never the user's real `~/.codex`.
- `packages/adapters/codex-local/src/server/codex-home.test.ts` — adds a
regression test for #5028: pre-seed a stale copy at the target, run
`prepareManagedCodexHome`, assert the target is now a symlink and reads
through to the fresh source. The existing concurrent-symlink test is
preserved.
## Verification
```
pnpm --filter @paperclipai/adapter-codex-local exec vitest run
# Test Files 8 passed (8)
# Tests 26 passed (26)
pnpm --filter @paperclipai/adapter-codex-local exec tsc --noEmit
# clean
```
Manual repro flow that the regression test mirrors:
1. Create a stale copy: `echo '{"token":"old"}' >
<managedHome>/auth.json`.
2. Rotate source: `echo '{"token":"new"}' > ~/.codex/auth.json`.
3. Trigger any codex_local run — `prepareManagedCodexHome` is called
from the execute path, the managed file is now a symlink to the source,
and the CLI sees the fresh token.
## Risks
- **Low risk.** The new branch only fires when the target file is a
regular file (the upgrade path) — a pure copy that Codex couldn't have
written, since Codex never writes into the managed home. Operators in
steady-state on the symlink-based version are unaffected.
- The `fs.unlink` only runs against the per-company managed-home path,
never the user's real `~/.codex`. Inline comment makes this guarantee
explicit.
- A directory at the auth.json path is left in place (no silent `EISDIR`
crash) — this requires operator inspection rather than autonomous
deletion.
- The healing uses `createExpectedSymlink()` so it remains tolerant of
EEXIST races with concurrent prepare calls (the concurrent-symlink test
still passes).
- No DB / migration / schema impact.
## Model Used
- Anthropic Claude Opus 4.7 (claude-opus-4-7), via Claude Code CLI with
extended tool use (Read / Edit / Bash / Grep). No extended-thinking
budget consumed beyond default.
## 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
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots — N/A, adapter-only
- [x] I have updated relevant documentation to reflect my changes —
inline comment explains the why and the safety of the unlink
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
- [x] I searched the GitHub PR list for similar PRs and confirmed this
is not a duplicate
Fixes#5028.
---------
Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The Claude-local adapter resumes prior sessions via `claude --resume
<session-id>` so work continues across heartbeats.
> - When a resumed session contains an image whose content is no longer
accessible, Claude returns a 400 "Could not process image" — but the
session itself is poisoned and will keep returning the same error on
every resume.
> - The existing retry path only catches the "unknown session" 400 case;
image-processing 400s on resume fall through and the run fails for the
user.
> - This PR adds an `isClaudeImageProcessingError` detector mirroring
`isClaudeUnknownSessionError` and wires it into the same fresh-session
retry branch in `execute.ts`.
> - The benefit is that a poisoned-image resume self-recovers by
retrying once with a fresh session, exactly like the existing
unknown-session path.
## Linked Issues or Issue Description
Fixes#3275
Refs #3123
## What Changed
- Added `isClaudeImageProcessingError()` in
`packages/adapters/claude-local/src/server/parse.ts` that matches `Could
not process image` in 400 error messages.
- Wired the new detector into the existing session-resume retry branch
in `packages/adapters/claude-local/src/server/execute.ts` alongside
`isClaudeUnknownSessionError`.
- Retry only fires when `sessionId` is present (i.e. we were resuming),
so fresh-session runs that hit the same error are not retried (no
infinite loop).
## Verification
- `pnpm --filter @paperclipai/adapter-claude-local test` covers
`parse.ts` patterns and the resume-retry decision branch.
- `pnpm --filter @paperclipai/adapter-claude-local typecheck`
## Risks
Low. Behavior change is narrowly additive: a previously-fatal 400 on
resume now triggers a single fresh-session retry. No effect on
fresh-session runs, unknown-session retries, or non-image 400s.
## Model Used
Claude (Opus 4.6) — used to mirror the existing unknown-session pattern
and verify the guard against infinite loops.
## 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 (N/A — 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 (in progress)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents on pluggable adapters
(`claude_local`, `opencode_local`, `codex_local`, …); each adapter wraps
an external CLI.
> - The heartbeat service stores a session ID per agent and replays it
back to the adapter via `--resume` so within-task continuity is
preserved.
> - Session IDs are adapter-specific in format: claude expects a UUID,
opencode emits `ses_…`, etc. They cannot be cross-replayed.
> - When the cross-adapter session ID does slip through (operator
changes `adapterType`, edge cases in the resume path, foreign-format ID
in stored task sessions), the claude CLI hard-fails with a validation
error and every subsequent heartbeat loops on the same error until the
stored ID is manually cleared.
> - Master now ships a canonical-session-ID guard at `heartbeat.ts:8450`
(via #5972) that prevents most of this at the source, and
`isClaudePoisonedPreviousMessageIdError` recovers from the 400-class API
error.
> - This PR adds defense-in-depth at the adapter layer: the `--resume
requires a valid session ID … not a UUID …` validation error from the
claude CLI is now classified as an unknown-session signal, so the
existing fresh-session retry recovers instead of hard-failing.
## Linked Issues or Issue Description
Refs #5972 — sibling fix on the same cluster (recovers from poisoned
`previous_message_id` 400). This PR complements it by handling the
CLI-layer `--resume` validation error class.
## What Changed
- `packages/adapters/claude-local/src/server/parse.ts` — broaden
`isClaudeUnknownSessionError` regex to also match `--resume requires a
valid session`, `is not a UUID`, and `does not match any session title`.
The existing fresh-session retry at `execute.ts:612-625` now fires for
this error class.
- `packages/adapters/claude-local/src/server/parse.test.ts` — adds 4 new
test cases for `isClaudeUnknownSessionError` covering the legacy and new
patterns plus a negative case.
**Dropped from the original PR on rebase** (already on master, would
conflict):
- `server/src/services/heartbeat.ts` runtimeSessionFallback gate —
superseded by the stricter `isCanonicalSessionIdForAdapter` check on
master (#5972 lineage).
- `packages/adapters/claude-local/vitest.config.ts` and
`vitest.config.ts` projects entry — both already in master.
## Verification
```sh
pnpm --filter @paperclipai/adapter-claude-local vitest run
# 19/19 passed (3 files, includes 4 new isClaudeUnknownSessionError cases)
```
Pre-existing failure on
`server/src/__tests__/heartbeat-process-recovery.test.ts > queues
exactly one retry when the recorded local pid is dead` reproduces on
`origin/master` — unrelated to this PR.
## Risks
- **Low-to-medium.** The added regex fragments are narrow. `--resume
requires a valid session` and `does not match any session title` are
unambiguously session-related. `is not a UUID` is more generic; worst
case is one extra retry on an unrelated CLI validation error that would
also fail on the same root issue. Happy to drop `is not a UUID` if
reviewers prefer.
- **No DB migration; no schema change; no behavior change when adapter
types match (the common path).**
## Model Used
- Provider: Anthropic (Claude)
- Model: `claude-opus-4-7` (Opus 4.7), 1M context window
- Tool: Claude Code CLI with extended thinking + tool use; human review
on the rebase and the regex narrowing tradeoffs
## Checklist
- [x] I searched the GitHub PR list for similar PRs and confirmed this
is not a duplicate (related: #5972 already merged, complementary scope)
- [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 (19/19 claude-local)
- [x] I have added or updated tests where applicable
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The Claude-local adapter uses `claude --resume <session-id>` to
continue prior sessions; the `--resume` value MUST be a UUID per
Claude's CLI contract.
> - Paperclip internally uses session IDs prefixed with `ses_` (not
UUIDs); these get passed straight through to `--resume` and crash the
run.
> - On top of the crash, when the underlying error path triggers a
secret-decryption failure or heartbeat setup failure, the diagnostics
are too thin to tell key-mismatch from other failures, and the heartbeat
error code is mis-classified as `adapter_failed` instead of
`setup_failed`.
> - This PR validates `runtimeSessionId` against a UUID regex before
letting `canResumeSession` become true, adds `not a valid UUID` to
Claude's own retry-error regex, improves AES-256-GCM decryption
diagnostics in the local encrypted provider, and re-classifies
pre-adapter setup failures.
> - The benefit is that Paperclip session IDs are detected and skipped
gracefully (logged, no crash), legitimate Claude UUID-rejection errors
are treated as retriable, and operators can diagnose decryption/setup
failures from the run log.
## Linked Issues or Issue Description
**What happened?**
The `claude-local` adapter passes Paperclip's internal session
identifiers (e.g. `ses_…`) straight to `claude --resume <session-id>`.
Because Claude's CLI requires the `--resume` argument to be a UUID, the
run crashes with a `not a valid UUID` error. When the surrounding code
path also hits a secret-decryption failure, the heartbeat reports it as
`adapter_failed`, hiding the real `setup_failed` cause and making
diagnosis hard.
**Expected behavior**
Non-UUID session IDs should be detected before `--resume` is called, the
run should fall back to a fresh session with a clear log line, and any
decryption / setup failure should be reported with enough detail (and
the correct error code) for an operator to tell what failed.
**Steps to reproduce**
1. Have a persisted task session whose ID is not a UUID
(Paperclip-issued `ses_…` form).
2. Trigger a heartbeat that resumes that session via the `claude-local`
adapter.
3. Observe: the adapter crashes with a UUID-validation error; if the
path also involves a decryption failure, the heartbeat surfaces
`adapter_failed` instead of `setup_failed`.
## What Changed
- `packages/adapters/claude-local/src/server/execute.ts`: Validates
`runtimeSessionId` against a UUID regex before setting
`canResumeSession`; non-UUID IDs are logged and skipped gracefully.
Guards the cwd-mismatch log block on `isValidUuid` so it does not fire
for non-UUID session IDs.
- `packages/adapters/claude-local/src/server/parse.ts`: Adds `not a
valid UUID` to the session-error retry regex so Claude's own UUID
rejection is treated as a retriable error.
- `server/src/services/secrets/local-encrypted-provider.ts`: Wraps
AES-256-GCM decryption in try/catch and re-throws with a key fingerprint
hint to aid key-mismatch diagnosis.
- `server/src/services/heartbeat.ts`: Corrects the outer-catch
`errorCode` from `adapter_failed` to `setup_failed` for pre-adapter
setup failures.
- `AGENTS.md`: Adds task/PR/CI governance sections (10–13) and expands
the Definition of Done.
## Verification
- `pnpm --filter @paperclipai/adapter-claude-local test` covers UUID
validation and the parse retry regex.
- `pnpm --filter @paperclipai/server test src/services/secrets` covers
decryption diagnostics.
- `pnpm --filter @paperclipai/server typecheck`
## Risks
Low. UUID validation is strictly additive (non-UUIDs that previously
crashed now log and skip). Decryption diagnostics only fire on failure
paths. The `setup_failed` error code change is a clearer classification,
not a behavior change.
## Model Used
Claude (Opus 4.6) — used to identify the UUID-validation root cause,
mirror existing parse patterns, and re-classify the heartbeat setup
error 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 (N/A — 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 (in progress)
- [ ] 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: CTO Agent <cto@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Devin Foley <devin@paperclip.ing>
## Thinking Path
> - Paperclip's `claude_local` adapter persists Claude Code session
jsonls under `~/.claude/projects/…/{sessionId}.jsonl` and resumes them
on the next heartbeat
> - When Claude Code injects `<synthetic>` placeholder assistant
messages (after rate-limit, max-turn exhaustion, or transient-upstream
failures) those placeholders get UUID-format `message.id`s rather than
`msg_…`-format ids
> - On the next `--resume`, Claude Code passes that UUID as
`previous_message_id` and Anthropic's API rejects it with a 400:
``diagnostics.previous_message_id: must be the `id` from a prior
/v1/messages response (starts with `msg_`)``
> - The adapter had a session-rotation fallback only for "unknown
session" errors, so the poisoned session was `--resume`-d indefinitely
and the agent flipped between `idle` and `error` every heartbeat
> - Even worse, the *result* event of the failing run still carried a
`session_id`, and the adapter was persisting that id into the
issue-scoped session store (`agentTaskSessions`). So even after we
detected the 400, every subsequent continuation re-loaded the same
poisoned id and hit the same 400 again — the issue was permanently
stranded
> - We observed this on multiple agents in our deployment; the only
manual fix was to rename the `.jsonl`, which is not a viable long-term
workaround
> - This PR detects the 400, runs the same session-rotation fallback the
unknown-session path uses **and** stops persisting the poisoned id, so
the next attempt starts genuinely fresh
## Linked Issues or Issue Description
No external GitHub issue is linked. Describing the problem inline
following the bug-report template:
**What happened:** `claude_local` agents flipped between `idle` and
`error` on every heartbeat because the persisted session jsonl carried a
synthetic UUID `previous_message_id` (from `<synthetic>` assistant
placeholders injected after rate-limit/max-turn/upstream errors).
Anthropic's API rejected every `--resume` with a 400:
``diagnostics.previous_message_id: must be the `id` from a prior
/v1/messages response (starts with `msg_`)``.
**Expected behavior:** When the persisted session is poisoned and
unrecoverable, the adapter should rotate to a fresh session — the same
fallback path already used for unknown-session errors — and stop
re-persisting the poisoned `session_id`.
**Actual behavior:** The session-rotation fallback only matched the
"unknown session" pattern, so the poisoned session was `--resume`-d
forever. The result event of the failing run still carried `session_id`,
which was being persisted into `agentTaskSessions`, so every subsequent
continuation reloaded the same poisoned id and hit the same 400.
**Reproduction:** Inject any flow that causes Claude Code to emit a
`<synthetic>` placeholder (rate-limit, max-turn exhaustion, transient
upstream failure). The next `--resume` will fail with the 400 and the
agent will not self-recover.
**Scope of fix:** Add a `previous_message_id` 400 detector; route it
through the existing unknown-session fallback; drop the poisoned
`sessionId` and emit `clearSession: true` so the heartbeat service wipes
the persisted row; best-effort delete the local poisoned `.jsonl`.
## What Changed
Two commits:
1. **`adapter-claude-local: auto-rotate session on previous_message_id
400 (synthetic-msg poisoning)`** — detector + execute-time rotation
2. **`adapter-claude-local: guard against persisting poisoned
sessionId`** — validate-before-persist + `clearSession`
Combined diff:
- `parse.ts`: new `isClaudePoisonedPreviousMessageIdError(parsed)`
matching ``/diagnostics\.previous_message_id.*starts with `msg_`/i``
against `parsed.result` and `extractClaudeErrorMessages(parsed)`
- `parse.ts`: `isClaudeTransientUpstreamError()` excludes the new error
from transient classification so it isn't masked as retryable upstream
noise
- `execute.ts`: expand the resume-fallback branch so it triggers on both
`isClaudeUnknownSessionError` and the new
`isClaudePoisonedPreviousMessageIdError`, with a distinct log line
(`"returned a poisoned message-id"` vs `"is unavailable"`)
- `execute.ts`: for local (non-remote) execution targets, best-effort
delete the poisoned `~/.claude/projects/.../{sessionId}.jsonl` before
retrying so the file can't be accidentally resumed by an out-of-band
caller. The `fs.unlink` and follow-up log call are in separate try/catch
blocks so a closed log stream cannot mask a successful unlink (and vice
versa)
- `execute.ts` / `toAdapterResult`: when a result carries the poisoned
400, **drop** `sessionId`/`sessionParams`/`sessionDisplayId` (return
`null`) and emit `clearSession: true` so the heartbeat service's
`resolveNextSessionState` wipes the persisted row. The result also
surfaces `errorCode: "claude_poisoned_previous_message_id"` for
observability
- `docs/adapters/claude-local.md`: runbook entry — symptom,
auto-recovery flow, on-call checklist
- Tests:
- 4 new `parse.test.ts` cases covering positive detection in `result`
and `errors[]`, negative cases, and non-transient classification
- 3 new `claude-local-execute.test.ts` cases: (a) fresh run reports the
poisoned error → sessionId dropped + `clearSession: true`; (b) recovery
retry also reports the poisoned error → same guards apply; (c)
session-rotation success on retry
## Verification
```bash
pnpm --filter @paperclipai/adapter-claude-local exec vitest run src/server/parse.test.ts
pnpm --filter @paperclipai/server exec vitest run src/__tests__/claude-local-execute.test.ts
```
Both suites green locally. This patch is also currently running as a
hot-patch over the published `2026.513.0` adapter on the reporting
deployment — sessions that previously looped indefinitely now
self-recover on the first heartbeat after the 400 surfaces.
## Risks
- Low risk. The detector is conservative (regex over `result` +
`errors[]` only) and the rotation reuses the existing unknown-session
fallback path
- The local-only `fs.unlink` of the poisoned `.jsonl` is wrapped in
`try/catch` and ignored on failure — strictly an optimization; the
server-side session clear is the authoritative reset
- Remote execution targets (`executionTargetIsRemote`) skip the disk
cleanup because the file lives on a remote host that we can't safely
reach from the adapter
- The `clearSession: true` + nulled session fields path is a no-op on
healthy runs; it only fires when the new detector matches, so existing
successful continuations are unaffected
- No DB schema changes, no public API changes, no new dependencies
## Model Used
- Provider: Anthropic Claude
- Model: `claude-opus-4-7` (Opus 4.7)
- Context window: 1M
- Capabilities: extended reasoning, tool use, code execution
- Role: implemented the detector, expanded the fallback branch, added
the persist-guard + `clearSession`, wrote the unit + integration tests,
validated locally, and applied the equivalent hot-patch to the deployed
`2026.513.0` install while this PR is in review
## 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 similar or duplicate PRs and linked
them — closed#2295, #2361, #3572, #5438 as duplicates of this canonical
fix; complementary fixes#4838 (heartbeat_timer reset) and #4932 (gemini
context-overflow rotation) target different code paths
- [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, adapter-only change
- [x] I have updated relevant documentation
(`docs/adapters/claude-local.md` runbook entry)
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Danial Jawaid <danial.jawaid@gmail.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Devin Foley <devin@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent work is issue-centered, and reviewers often need to inspect
files, artifacts, and path references produced during that work.
> - Before this branch, workspace-relative paths and artifact file
references were not first-class inspectable objects in the board UI.
> - Safe file viewing needs shared resource contracts, server-side
workspace boundary checks, and UI that opens files without exposing
arbitrary host paths.
> - The workspace file viewer branch needed to stay as one active PR and
be rebased onto current `paperclipai/paperclip:master` for review.
> - This pull request adds the workspace file resource API, issue-page
file viewer and browser, markdown file-reference links, and artifact
file chips.
> - The benefit is that board users can inspect relevant files from
issue context while preserving workspace boundaries and auditability.
## Linked Issues or Issue Description
No public GitHub issue exists for this branch. Internal Paperclip
issues: `PAP-1953`, `PAP-10539`, `PAP-10733`.
Problem / motivation:
- Board users need to open workspace-relative files mentioned by agents
or attached as work-product metadata without switching to a terminal.
- The UI needs to support both direct file-path opening and workspace
browsing/searching from an issue page.
- The server must enforce company access, workspace boundaries, size
limits, rate limits, and safe audit logging.
Related PR:
- Prior closed attempt: #4442
- Single active PR for this branch: #7681
## What Changed
- Added shared workspace file resource types, validators, and
workspace-file `resourceRef` metadata validation for work products.
- Added server routes/services for resolving, listing, and previewing
workspace-relative files with access checks, scan caps, list-specific
limits, and audit logging.
- Added the issue file viewer provider, sheet, workspace browser,
command-palette action, markdown workspace-file autolinks, and artifact
file chips.
- Updated issue workspace UI and stories/tests for file browsing and
workspace file opening.
- Rebased the branch onto current `paperclipai/paperclip:master` and
updated the existing single PR branch.
- Addressed current-head Greptile follow-ups by applying `offset`
consistently across search/recent/changed file listings, restoring
stopped-service port ownership checks before auto-port reuse, and
stabilizing the workspace browser pagination test.
## Verification
Current local verification after rebase to `public/master`:
- `pnpm exec vitest run packages/shared/src/work-product.test.ts
server/src/__tests__/file-resources.test.ts
server/src/__tests__/instance-settings-routes.test.ts
server/src/__tests__/instance-settings-service.test.ts
server/src/__tests__/workspace-runtime.test.ts
ui/src/components/FileViewerSheet.test.tsx
ui/src/components/FileViewerSheet.copy.test.tsx
ui/src/components/WorkspaceFileBrowser.test.tsx
ui/src/components/WorkspaceFileMarkdownBody.test.tsx
ui/src/context/FileViewerContext.test.ts
ui/src/lib/remark-workspace-file-refs.test.ts
ui/src/lib/workspace-file-parser.test.ts
ui/src/components/IssueWorkspaceCard.test.tsx` - 13 files passed, 197
tests passed.
- `pnpm -r --filter @paperclipai/shared --filter @paperclipai/server
--filter @paperclipai/ui typecheck` - passed.
- `pnpm exec vitest run ui/src/components/WorkspaceFileBrowser.test.tsx`
- 1 file passed, 25 tests passed.
- `pnpm exec vitest run server/src/__tests__/file-resources.test.ts
server/src/__tests__/workspace-runtime.test.ts` - 2 files passed, 90
tests passed.
- `pnpm -r --filter @paperclipai/server typecheck` - passed.
- Confirmed branch is `0` behind and `46` ahead of current
`public/master` after rebase and follow-up commits.
- Confirmed the PR diff does not include `pnpm-lock.yaml`.
- Confirmed the PR diff does not include `.github/workflows` changes.
- Searched GitHub for duplicate or related workspace file viewer
PRs/issues; #4442 is the prior closed attempt and this PR is the single
active PR for the branch.
- No screenshots were committed; the task explicitly asked not to add
design screenshots or images unless they were part of the work.
Current remote verification on head
`a698a7bc10137baf7d25bd5722e1d6e0343387c1`:
- Greptile Review - success, 64 files reviewed, 0 comments added, no
unresolved Greptile review threads.
- PR workflow `verify` - success.
- Typecheck + Release Registry, General tests, workspace test shards,
serialized server suites, Build, Canary Dry Run, e2e, Socket, and Snyk -
success.
- `security-review` - neutral, with output saying a draft advisory was
filed for maintainer review and is not a merge block.
- `commitperclip PR Review / review` - cancelled after the security gate
detected flags and timed out while creating/reviewing the advisory. I
reran it once and it cancelled the same way; no actionable code/test
failure was exposed in the job logs.
## Risks
- This is a broad UI/server feature PR, so review needs to pay attention
to route authorization, workspace boundary handling, and markdown
autolink false positives.
- Workspace browsing intentionally caps list results and scan depth;
very large workspaces may require users to refine search terms.
- Remote workspace preview remains unavailable until remote file-access
support is implemented.
- The neutral commitperclip security-review advisory needs maintainer
review, but the check output says it is not a merge block.
> 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 coding agent in a Paperclip/Codex local tool-use
environment, medium reasoning, with shell/GitHub CLI tool use for branch
inspection, verification, rebase, PR update, Greptile review, and CI
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 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
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
Adds the newly released Claude models from the [models
overview](https://platform.claude.com/docs/en/about-claude/models/overview)
to the `claude_local` adapter's model selector:
- **Claude Fable 5** (`claude-fable-5`) — generally available as of
2026-06-09, Anthropic's most capable widely-released model.
- **Claude Mythos 5** (`claude-mythos-5`) — limited availability
(Project Glasswing).
**Opus 4.8 stays first in the list so it remains the default selection**
— per the request, the new flagship models are *offered* but not
defaulted (not Fable, not Mythos).
## Changes
- `packages/adapters/claude-local/src/index.ts` — add `claude-fable-5`
and `claude-mythos-5` to the adapter model list, right after
`claude-opus-4-8`.
- `packages/adapters/claude-local/src/server/models.ts` — add the Fable
5 Bedrock identifier (`us.anthropic.claude-fable-5-v1`) to the Bedrock
fallback list. Mythos 5 is limited-availability on Bedrock, so it's
intentionally left out of that fallback.
- `server/src/__tests__/adapter-models.test.ts` — assert the new models
are present and that `claude-opus-4-8` remains first (the default).
These flow through the single `claudeModels` source, so they also appear
in the ACPX combined list (`registry.ts` prefixes them with `Claude:`)
and are recognized by the ACPX Claude model filter. The UI selector
reads models dynamically from the adapter, so no UI changes are needed.
## Testing
- `npx vitest run src/__tests__/adapter-models.test.ts` — 13 passed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source control plane people use to manage AI
agents, work, and company context.
> - The board UI sidebar is the main way operators keep orientation
across companies, projects, agents, issues, and settings.
> - The existing fixed expanded sidebar competes with route-specific
navigation, especially company settings and plugin routes that bring
their own contextual sidebar.
> - A collapsible primary rail preserves global navigation while giving
contextual pages more horizontal room.
> - This pull request adds a persisted collapsed rail, hover/focus peek,
keyboard toggle, and a secondary sidebar takeover model for settings and
plugin `routeSidebar` surfaces.
> - The benefit is a denser board shell that keeps the app rail
available without replacing it when a route needs its own navigation.
## Linked Issues or Issue Description
Paperclip issue: PAP-10638 Create collapsible sidebar branch.
Related GitHub PR found during duplicate search: #3838
(`feat/collapsible-sidebar`) covers a similar sidebar area but is a
different head branch and implementation. This PR intentionally packages
the work from `PAP-10638-collapsable-sidebar` into one reviewable
branch.
Problem description:
The board shell needs a first-class collapsed sidebar mode. Contextual
surfaces such as company settings and plugin route sidebars should not
replace the global app sidebar; they should collapse the app sidebar to
a rail and render their contextual navigation beside it.
## What Changed
- Added desktop collapsed/sidebar-peek state to `SidebarContext`,
including persisted user pins, route collapse requests, and forced
collapse for secondary-sidebar routes.
- Replaced the old resizable sidebar pane with `SidebarShell`, which
supports a fixed 64px rail, persisted expanded width, keyboard/pointer
resizing, and hover/focus peek overlay behavior.
- Updated `Sidebar`, sidebar nav items, project/agent sections, badges,
and account/company menu presentation for expanded, collapsed, and
peeking states.
- Added `RequestCollapsedSidebar` and `SecondarySidebar` so routes and
plugin `routeSidebar` slots can request contextual sidebar layouts
without replacing the primary app sidebar.
- Wired company settings and plugin route sidebars into the
secondary-pane takeover model.
- Added focused Vitest coverage for sidebar state precedence, shell
sizing, nav item rail rendering, keyboard shortcuts, layout takeover
behavior, and route collapse requests.
- Updated plugin authoring docs/spec references for route sidebar
behavior.
## Verification
Targeted local verification passed:
```sh
NODE_ENV=test pnpm run preflight:workspace-links && NODE_ENV=test pnpm exec vitest run ui/src/context/SidebarContext.test.tsx ui/src/components/SidebarShell.test.tsx ui/src/components/Sidebar.test.tsx ui/src/components/Layout.test.tsx ui/src/components/RequestCollapsedSidebar.test.tsx ui/src/components/SidebarNavItem.test.tsx ui/src/components/SidebarAgents.test.tsx ui/src/components/SidebarProjects.test.tsx ui/src/components/KeyboardShortcutsCheatsheet.test.tsx ui/src/hooks/useKeyboardShortcuts.test.tsx
```
Result: 10 test files passed, 88 tests passed.
Additional follow-up verification passed after review fixes:
```sh
NODE_ENV=test pnpm run preflight:workspace-links && NODE_ENV=test pnpm exec vitest run ui/src/components/Layout.test.tsx ui/src/context/SidebarContext.test.tsx && pnpm --filter /ui typecheck
```
Result: 2 test files passed, 28 tests passed, and UI typecheck passed.
Latest PR-head remote checks: Paperclip PR workflow, Snyk, Socket, and
Greptile are green; commitperclip `review` is cancelled in its
security-gate step after filing a non-blocking neutral `security-review`
check.
Notes:
- A direct run without `NODE_ENV=test` loads React's production build in
this workspace, where `act` is unavailable; the command above matches
the repo stable runner's test environment.
- I did not run Playwright/browser e2e or full workspace build/typecheck
in this PR-creation heartbeat.
- QA screenshots are attached in
https://github.com/paperclipai/paperclip/pull/7824#issuecomment-4661968387
for expanded, collapsed rail, hover peek, and settings secondary-sidebar
states.
## Risks
- Medium UI layout risk: this changes the board shell and primary
sidebar composition across many routes.
- Local storage migration risk is low: new collapsed state uses a new
key and existing width storage remains scoped to the sidebar width.
- Plugin route risk: plugin `routeSidebar` slots now render as secondary
panes on desktop, so plugin authors should confirm their route sidebar
content fits a 240px contextual pane.
- Mobile risk appears low because mobile keeps the drawer model and
gates collapsed/peek behavior to desktop.
> 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 local shell/git/GitHub
CLI tool use. Exact service-side model identifier and context window
were not exposed in this 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
- [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: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators manage both company-scoped configuration and
instance-level runtime/admin settings from the board UI
> - Instance settings previously lived as their own top-level sidebar
area, separate from the company settings context operators already use
> - That split made settings navigation feel heavier and made instance
configuration less discoverable from the settings tab
> - This pull request moves instance settings under company settings
while preserving the existing instance settings routes and plugin/admin
surfaces
> - The benefit is a smaller primary sidebar and a more coherent
settings hierarchy for operators
## Linked Issues or Issue Description
- Refs #338
- Internal: PAP-10491, PAP-10538
## What Changed
- Moved instance settings navigation under the company settings area.
- Added route helpers and sidebar entries for nested instance settings
paths.
- Updated plugin/admin settings routes to use the company settings
instance scope.
- Preserved legacy instance-settings bookmarks through compatibility
redirects that keep the active company prefix.
- Updated focused UI and plugin tests for the new navigation shape.
- Stabilized the process-loss retry test that was failing the serialized
server shard in CI.
- Rebased the branch onto current `paperclipai/paperclip` `master` and
pushed the current head.
## Verification
- `pnpm exec vitest run
ui/src/components/CompanySettingsSidebar.test.tsx
ui/src/components/access/CompanySettingsNav.test.tsx
ui/src/lib/instance-settings.test.ts
ui/src/components/InstanceSidebar.test.tsx
ui/src/components/Layout.test.tsx
ui/src/components/SidebarAccountMenu.test.tsx
ui/src/pages/PluginPage.test.tsx ui/src/plugins/bridge.test.ts
packages/shared/src/validators/plugin.test.ts`
- `pnpm exec vitest run ui/src/lib/instance-settings.test.ts
ui/src/components/CompanySettingsSidebar.test.tsx
ui/src/components/access/CompanySettingsNav.test.tsx
ui/src/components/Layout.test.tsx ui/src/plugins/bridge.test.ts`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "queues
exactly one retry when the recorded local pid is dead"`
- `pnpm test:run:serialized -- --shard-index 0 --shard-count 4`
- GitHub PR checks are green on head
`fe7b0955169dcae55cbe10889c1876a70ab0b80c`, including `verify`, `General
tests (server)`, all serialized server shards, build, e2e, policy,
security checks, and Greptile.
- Confirmed the PR diff does not include `pnpm-lock.yaml` or
`.github/workflows` changes.
## Risks
- Medium UI/navigation risk: instance settings links are intentionally
moving under company settings, so stale external bookmarks to legacy
paths rely on the compatibility routing in this branch.
- Low test-only risk from the CI stabilization commit: it makes the
recovery assertion select the actual retry run by `retryOfRunId` instead
of whichever non-original run appears first.
- No database migrations.
- No dependency lockfile or workflow changes.
> 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/tool execution in
a local repository worktree. Exact context window was 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
- [ ] 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>
## Thinking Path
> - Paperclip is the open source control plane people use to manage AI
agents for work.
> - Agent runtime state is surfaced in both the server API and the board
UI so operators can tell whether an agent is idle, running, paused, or
in error.
> - When an agent is already in `error`, the existing pause/resume
action slot is not useful because there is no running work to pause.
> - Operators need a direct, audited recovery path that clears the stale
error state only for agents in the same company.
> - This pull request adds a company-scoped clear-error mutation,
exposes the shared API contract, and wires the board action cluster to
show Clear error in the pause/resume slot for errored agents.
> - The benefit is that operators can recover CEO/CTO-style errored
agents without resorting to database edits or unrelated session reset
actions.
## Linked Issues or Issue Description
Refs #4021
Paperclip issue: PAP-10515 — right now the CEO and CTO agents are in
error state, but there is no way to clear the error; they appear
otherwise fine.
## What Changed
- Added shared constants, API path, and agent status type support for a
company-scoped clear-error action.
- Added the server service and route to clear an agent from `error` back
to `idle`, with company access enforcement and activity logging.
- Added OpenAPI/docs coverage for the clear-error endpoint.
- Added backend coverage for service behavior and cross-tenant
authorization.
- Updated the board agent action cluster to show a red-tinted Clear
error button only when `agent.status === "error"`.
- Updated agent properties to show a red active last-error indicator
only while the agent is currently errored.
- Added UI component tests for the error-state action and the non-error
pause/resume behavior.
## Verification
Local:
- `pnpm exec vitest run
server/src/__tests__/agents-service-clear-error.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
ui/src/components/AgentActionButtons.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts`
PR checks:
- Main Paperclip workflow is green on
`a7378e584d50594e7bd507a1a02985bfaaa5abf8`.
- Greptile is 5/5 with no files requiring special attention and no new
comments on the latest review.
- `commitperclip PR Review` is still red because its security-gate step
canceled after filing a draft advisory; the linked `security-review`
check is neutral and says the draft advisory is not a merge block.
Visual artifact:
- 
## Risks
Low to medium risk. The mutation is intentionally narrow, but reviewers
should check that clearing `lastError`/`lastRunError` and returning to
`idle` is the desired recovery semantics for every adapter state. The
remaining red check is from the external commitperclip security-review
workflow, not from the code/test workflow for this PR.
> 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-family coding model, tool-assisted with local shell,
git, GitHub CLI, and targeted Vitest 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 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
- [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
Bumps [drizzle-kit](https://github.com/drizzle-team/drizzle-orm) from
0.31.9 to 0.31.10.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/drizzle-team/drizzle-orm/releases">drizzle-kit's
releases</a>.</em></p>
<blockquote>
<h2>drizzle-kit@0.31.10</h2>
<ul>
<li>Updated to <code>hanji@0.0.8</code> - native bun
<code>stringWidth</code>, <code>stripANSI</code> support, errors for
non-TTY environments</li>
<li>We've migrated away from <code>esbuild-register</code> to
<code>tsx</code> loader, it will now allow to use
<code>drizzle-kit</code> seamlessly with both <code>ESM</code> and
<code>CJS</code> modules</li>
<li>We've also added native <code>Bun</code> and <code>Deno</code>
launch support, which will not trigger <code>tsx</code> loader and
utilise native <code>bun</code> and <code>deno</code> imports
capabilities and faster startup times</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="4aa6ecfee4"><code>4aa6ecf</code></a>
Kit updates (<a
href="https://redirect.github.com/drizzle-team/drizzle-orm/issues/5490">#5490</a>)</li>
<li>See full diff in <a
href="https://github.com/drizzle-team/drizzle-orm/compare/drizzle-kit@0.31.9...drizzle-kit@0.31.10">compare
view</a></li>
</ul>
</details>
<br />
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
> - The control plane owns agent lifecycle, issue assignment, routine
dispatch, heartbeat wakeups, and recovery paths
> - Terminated, paused, pending-approval, or otherwise invalid agents
should not receive new work or new execution attempts
> - The old behavior left eligibility checks spread across routes and
services, so assignment and run paths could drift apart
> - This pull request centralizes agent lifecycle eligibility and
applies it consistently to assignment, invocation, routines, recovery,
and UI affordances
> - The benefit is safer autonomy: terminated agents stay paused,
invalid org-chain agents are surfaced, and active agents keep receiving
valid work
## Linked Issues or Issue Description
Refs #5103
Related: #1864
Bug fix context:
- What happened: agent assignment and heartbeat/run paths did not share
one eligibility contract, so invalid lifecycle states could still be
considered in some paths.
- Expected behavior: terminated agents must never receive new
assignments or heartbeat runs, and paused or otherwise invalid agents
should be treated as non-invokable consistently.
- Steps to reproduce: create or select an agent in an invalid lifecycle
state, then attempt assignment, routine dispatch, or heartbeat/recovery
wake paths.
- Paperclip version/commit: fixed on top of `paperclipai/paperclip`
`master` at the PR base.
- Deployment mode: applies to the server control plane in local and
authenticated deployments.
## What Changed
- Added shared agent lifecycle eligibility helpers and exported the
related shared types.
- Centralized server-side assignability and invokability checks for
issue assignment, agent routes, heartbeat dispatch, routines, recovery,
and liveness logic.
- Hardened issue assignment so invalid assignees are rejected instead of
queued for work.
- Hardened heartbeat/routine/recovery paths so terminated and otherwise
invalid agents are not woken for new runs.
- Updated board UI affordances to disable invalid agent actions and
surface org-chain warnings where relevant.
- Added targeted shared, server, and UI tests for the new eligibility
behavior.
## Verification
- `pnpm exec vitest run packages/shared/src/agent-eligibility.test.ts
server/src/__tests__/agent-invokability.test.ts
server/src/__tests__/heartbeat-archived-company-guard.test.ts
server/src/__tests__/issue-liveness.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/routines-service.test.ts
ui/src/lib/company-members.test.ts ui/src/pages/Agents.test.tsx` — 8
files, 144 tests passed.
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck && pnpm --filter @paperclipai/ui
typecheck` — passed.
- Checked the PR diff does not include `pnpm-lock.yaml` or
`.github/workflows` changes.
- Checked `ROADMAP.md`; this is a targeted control-plane safety fix and
does not duplicate a planned core feature.
- Searched GitHub for duplicate or related PRs/issues; closest related
items are linked above.
- CI and Greptile verification are pending on the opened PR and will be
followed up before requesting merge.
## Risks
Low to moderate risk. The intended behavioral shift is that invalid
agents are refused earlier and more consistently, which could expose
existing data with paused, pending, terminated, or broken org-chain
assignees. The added tests cover the critical assignment, heartbeat,
routine, recovery, shared helper, and UI paths. No database migrations
are included.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex via the Paperclip `codex_local` adapter, with
shell/git/GitHub CLI tool use. Reasoning mode and context window are
managed by the adapter runtime and 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 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 (not applicable: no design screenshots requested; UI
behavior is covered by tests)
- [x] I have updated relevant documentation to reflect my changes (not
applicable: no user-facing command or schema docs changed)
- [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 Greptile)
- [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 artifacts surface is where board users inspect files, media, and
documents produced by agents.
> - Grouped artifact stacks make that surface easier to scan by task,
but the first pass still made grouping feel secondary to media filters.
> - The follow-up request was to make grouping the default and give the
grouping control the same icon-only outline treatment used on the issues
page.
> - This pull request keeps the existing artifact grouping API/UI, then
polishes the artifacts toolbar state and Storybook review coverage.
> - The benefit is that `/artifacts` now opens in the task-stack view by
default while preserving explicit flat-mode filtering via
`groupBy=none`.
## Linked Issues or Issue Description
No public GitHub issue exists for this internal Paperclip task.
### Subsystem affected
ui/ — React + Vite board UI.
### Problem or motivation
The `/artifacts` grouping affordance was visually placed after the media
filters, rendered as a text button, and defaulted to a flat artifact
list. Internal follow-up `PAP-10465` requested the grouping icon move
left of the filters, become an icon-only outlined button like `/issues`,
and make Task grouping the default.
### Proposed solution
Default `/artifacts` to grouped Task stacks, keep explicit flat mode
available as `groupBy=none`, move the grouping control before the media
chips, and restyle it as the shared icon-only outline button pattern.
### Alternatives considered
Leaving flat mode as the implicit default was rejected because it does
not satisfy the follow-up. Keeping a text label on the grouping trigger
was rejected because `/issues` already established the icon-only outline
pattern for this class of toolbar control.
### Roadmap alignment
This aligns with the `Artifacts & Work Products` roadmap item by making
generated outputs easier to inspect and operate from the board UI.
## What Changed
- Defaulted the `/artifacts` page to `groupBy=task` when no grouping URL
param is present, while keeping explicit flat mode available with
`groupBy=none`.
- Moved the group control before the media filter chips and changed it
to an icon-only outlined button using the shared `Button` pattern.
- Updated artifact page tests to cover default Task grouping, explicit
flat mode, trigger ordering, and icon-only outline metadata.
- Updated the artifact Storybook story so its toolbar mock matches the
production ordering and grouped Task is documented as the default mode.
## Verification
- `pnpm exec vitest run ui/src/pages/Artifacts.test.tsx
ui/src/components/artifacts/ArtifactGroupCard.test.tsx` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `git diff --check` — passed.
- QA visual validation from internal follow-up PAP-10466 passed
desktop/mobile scenarios. Screenshot evidence attached there:
- Desktop default:
http://paperclip-dev:3100/api/attachments/bc81305d-f5de-485c-abeb-9e7c3d9d8539/content
- Desktop toolbar close-up:
http://paperclip-dev:3100/api/attachments/3375a62b-2110-48f3-bafa-ea98c00f99f7/content
- Mobile default:
http://paperclip-dev:3100/api/attachments/bfc5642e-9248-431e-9bac-36284dec1c89/content
- Mobile toolbar close-up:
http://paperclip-dev:3100/api/attachments/ca79401a-5ba8-464d-bc6e-aeffd47fe695/content
- GitHub PR checks on head `431964c8b` — passed, including Greptile 5/5.
## Risks
Low to medium risk. The main behavior shift is intentional: `/artifacts`
now queries grouped Task stacks by default. Existing flat mode remains
available through the grouping menu and explicit `groupBy=none` URLs.
## Model Used
OpenAI Codex, GPT-5.4 class coding model in this Paperclip heartbeat
environment, with shell, git, test, and GitHub CLI tool use. Context
window managed by the Codex 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
- [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 affected subsystem is the heartbeat execution path that turns
issue assignment into adapter-backed work in a selected workspace.
> - PAP-10409 and sibling follow-ups failed before useful adapter output
because project/workspace identity became incoherent.
> - A project-workspace-linked child issue could keep
`projectWorkspaceId` / execution workspace state while losing
`projectId`, then a git-sensitive local adapter could fall through
toward an invalid fallback cwd.
> - Paperclip needs to treat coherent workspace identity as part of the
live-path contract, not only as post-failure cleanup.
> - This pull request documents that rule, repairs issue inheritance,
and blocks git-sensitive adapter launch before it can run from the wrong
cwd.
> - The benefit is a bounded recovery path: affected issues are repaired
explicitly, future malformed workspaces fail fast with a clear recovery
action, and the UI surfaces that reason.
## Linked Issues or Issue Description
Refs #7646
Bug report fields:
- Summary: adapter-backed follow-up issues can fail before doing work
when issue creation/inheritance preserves workspace ids but drops
project identity.
- Affected issues: internal Paperclip issues PAP-10408 through
PAP-10412, especially PAP-10409.
- Steps to reproduce: create a project-scoped parent/follow-up tree
where a child issue keeps `projectWorkspaceId` or an inherited execution
workspace but has `projectId: null`, then launch a git-sensitive local
adapter such as `codex_local`.
- Expected behavior: Paperclip derives or preserves coherent project
identity during issue creation, and heartbeat refuses malformed
git-sensitive workspace launches with one clear recovery action.
- Actual behavior before this PR: the run could reach adapter bootstrap
with an incoherent workspace context and fail with git errors such as
`fatal: not a git repository (or any parent up to mount point /srv)`.
- Root cause: child/follow-up issue inheritance preserved workspace
execution context without coherent project context. That let heartbeat
workspace resolution/adapter launch reach a fallback cwd instead of
refusing the malformed workspace state up front.
## What Changed
- Documented the adapter workspace-coherence live-path precondition in
`doc/execution-semantics.md`.
- Updated issue creation/inheritance so workspace-inheriting issues
preserve or derive project identity, while existing mismatch validation
still rejects incoherent project/workspace combinations.
- Added a heartbeat preflight guard for git-sensitive local adapters
that validates effective cwd, persisted workspace identity, project
workspace identity, and required git metadata before launch.
- Added `workspace_validation` recovery actions for this failure class
and ensured the source issue gets a visible, idempotent recovery
comment.
- Surfaced workspace-validation recovery state in issue rows, blocked
notices, and recovery action cards, including the manual-repair wake
policy label.
- Added focused regression coverage for issue inheritance, all heartbeat
workspace-validation guard branches, recovery display helpers, and UI
recovery components.
## Verification
- `pnpm exec vitest run
server/src/__tests__/heartbeat-workspace-session.test.ts`
- Result: 1 test file passed, 68 tests passed.
- `pnpm exec vitest run
ui/src/components/IssueRecoveryActionCard.test.tsx`
- Result: 1 test file passed, 12 tests passed.
- `pnpm exec vitest run ui/src/components/IssueBlockedNotice.test.tsx
ui/src/components/IssueRecoveryActionCard.test.tsx`
- Result: 2 test files passed, 18 tests passed.
- `pnpm --filter @paperclipai/ui typecheck`
- Result: passed.
- `pnpm exec vitest run
server/src/__tests__/heartbeat-plugin-environment.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/heartbeat-workspace-session.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
ui/src/components/IssueBlockedNotice.test.tsx
ui/src/components/IssueRecoveryActionCard.test.tsx
ui/src/lib/recovery-display.test.ts`
- Result: 7 test files passed, 200 tests passed before the final
guard-branch additions; the changed server file was re-run above.
- UI coverage: `ui/storybook/stories/source-issue-recovery.stories.tsx`
contains rendered scenarios for the generic recovery chip,
workspace-validation recovery chip, blocked notice indicator, recovery
action card, and issue-row chip.
- Screenshot capture attempt: Storybook started successfully on
`http://127.0.0.1:6016/`, but screenshots could not be captured in this
runner because `agent-browser` launched an unusable Chrome binary and
Playwright Chromium failed on missing system library `libatk-1.0.so.0`;
the runner is non-root and lacks passwordless sudo for installing
browser dependencies.
- Hosted CI on final commit `969594e7` is green, including `verify`,
`Build`, `Typecheck + Release Registry`, `General tests (server)`,
workspace suites, serialized server suites, `Canary Dry Run`, and `e2e`.
- Roadmap checked: no duplicate roadmap item; this is a tightly scoped
reliability fix for existing heartbeat/workspace behavior.
- Duplicate PR search checked: no open PR matched `workspace coherence
adapter cwd`.
## Risks
- Medium risk: heartbeat launch is stricter for git-sensitive local
adapters and can now block malformed workspace states before adapter
execution.
- Mitigation: the guard is limited to local git-sensitive adapters and
records a source-scoped recovery action with structured evidence instead
of retrying indefinitely.
- Compatibility: valid project/workspace execution paths continue
normally; explicit project/workspace mismatches remain rejected.
> 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-based `codex_local` coding agent with terminal/tool
use. Work was produced through Paperclip issue execution with focused
local test runs.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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
- [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 board UI is the control surface for issues, projects, agents,
goals, workspaces, and operator settings.
> - The existing navigation and list surfaces make several
high-frequency workflows feel harder to scan than they should,
especially around projects and agents.
> - The product direction is to improve those surfaces without breaking
the existing route model or forcing a new IA on every operator at once.
> - This pull request now keeps the dependent IA, project identity, and
agent-list visual refresh work together while the Issue-to-Task copy
migration is split into #7651.
> - The benefit is a clearer left nav, better project identity, denser
agent/project list rows, and brand-aligned status treatment while
preserving the classic default experience behind a flag.
## Linked Issues or Issue Description
Refs #7645
Refs #7651
Internal planning/work references: PAP-53, PAP-56, PAP-58, PAP-59,
PAP-60, PAP-61, PAP-68, PAP-69, PAP-70, PAP-71, PAP-72, PAP-75, PAP-76,
PAP-80, PAP-85, PAP-86, PAP-87, PAP-88, PAP-89.
## What Changed
- Adds `enableStreamlinedLeftNavigation`, defaulting off, and gates
sidebar presentation so classic navigation remains the default.
- Adds project icon persistence, validation, portability, picker UI, and
`ProjectTile` rendering while defaulting new projects to neutral gray.
- Adds projects-list task-count and budget summary data with focused
server/shared/UI coverage.
- Refreshes agent list rows, row actions, active/recent sidebar
behavior, and status capsule/chip styling for the approved brand state
system.
- Removes the placeholder Conference room and Artifacts nav/routes from
the finalized experimental nav direction.
- Removes `pnpm-lock.yaml` and the Issue-to-Task copy migration from
this PR diff; the copy migration now lives in #7651.
## Verification
- Existing branch verification from the authored commits: UI typecheck,
targeted unit tests, and light/dark visual checks for `/agents`, agent
detail, and design-guide status states.
- Maintainer cleanup verification on `75e34e5`: `git diff --check
origin/master...HEAD` passed, the `design/` diff is empty, and the PR
diff is 61 files, below Greptile's 100-file review limit.
- `pnpm --filter @paperclipai/ui build` passed.
- `NODE_ENV=test pnpm exec vitest run
ui/src/components/Sidebar.test.tsx` passed: 1 file, 8 tests.
- CI and Greptile should rerun on the latest push.
## Risks
- Broad UI surface area: the experimental flag keeps the classic nav
default, but changed shared components such as `EntityRow`,
`ProjectTile`, and agent status badges could affect multiple pages.
- Database migration: `projects.icon` is additive and nullable, but
migration ordering and portability import/export must stay aligned.
- The Issue-to-Task copy migration is now separated into #7651, so
reviewers should evaluate this PR as IA/project/agent presentation work
only.
- Visual regressions are possible across smaller widths because the PR
intentionally changes dense list-row layouts.
## Model Used
Claude Opus 4.8 assisted the original feature commits.
Paperclip-Paperclip agents assisted some planning/design commits. Codex
/ GPT-5-class coding agent with shell, GitHub CLI, and repository access
performed this PR-readiness cleanup and split.
## 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
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Dotta <bippadotta@protonmail.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent work is coordinated through issues, comments, interactions,
and approval-style handoffs.
> - Existing issue-thread interactions could ask questions, suggest
tasks, and request confirmation, but they did not support a structured
checkbox confirmation payload for choosing one or more options.
> - That gap made board/user confirmations harder to validate
consistently across API callers, plugin helpers, CLI tooling, and the
UI.
> - This pull request adds the shared checkbox confirmation contract,
server handling, client helpers, and issue-thread UI needed to render
and submit structured selections.
> - The benefit is that agents can request bounded multi-select
confirmations in the same audited issue-thread flow as other Paperclip
interactions.
## Linked Issues or Issue Description
- No public GitHub issue found for this exact branch. Internal Paperclip
issue: PAP-10415 / PAP-10441 requested creating this PR for the checkbox
confirmation issue-thread UI component work.
- GitHub duplicate search performed for checkbox confirmation /
issue-thread interaction PRs; no matching open PR was found.
- Related issue search result `#7497` was unrelated company file cleanup
work, so it is not linked as a related issue.
## What Changed
- Added shared types, validators, constants, and tests for
`request_checkbox_confirmation` interactions.
- Extended server issue-thread interaction service and routes for
checkbox confirmation creation, validation, expiration, and response
handling.
- Added CLI, MCP, and plugin SDK helper coverage so external callers can
create the new interaction shape consistently.
- Updated the issue-thread interaction UI to render checkbox
confirmations with min/max bounds, selection summaries, stale-target
states, and accept/decline flows.
- Documented the checkbox confirmation interaction contract in the
Paperclip skill/API reference.
## Verification
- Rebased cleanly on `paperclipai/paperclip` `master` fetched into
`public-gh/master` at `a4fa0eaf5`.
- Confirmed the PR diff does not include `pnpm-lock.yaml` or
`.github/workflows` changes.
- Ran focused tests with `NODE_ENV=test`:
```sh
NODE_ENV=test pnpm run preflight:workspace-links
NODE_ENV=test pnpm exec vitest run packages/shared/src/issue-thread-interactions.test.ts server/src/__tests__/issue-thread-interaction-routes.test.ts server/src/__tests__/issue-thread-interactions-service.test.ts ui/src/components/IssueThreadInteractionCard.test.tsx ui/src/lib/issue-thread-interactions.test.ts cli/src/__tests__/issue-subresources.test.ts cli/src/__tests__/project-goal.test.ts packages/mcp-server/src/tools.test.ts packages/plugins/sdk/tests/testing-actions.test.ts
```
Result: 8 test files passed, 78 tests passed.
- CI on latest head `63b9e55` is green.
- Greptile Review passed on latest head; GraphQL review-thread check
shows all Greptile threads resolved.
## Risks
- Medium surface area because the interaction contract touches shared
validators, server routes/services, UI rendering, CLI, MCP, plugin SDK
helpers, and docs.
- No database migrations are included.
- `pnpm-lock.yaml` is intentionally excluded per repository lockfile
policy.
- UI screenshots are not attached because the task explicitly requested
not to add design screenshots or images unless they were part of the
work; component tests cover the new rendering and interaction states.
> 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 repository file access,
shell command execution, git/GitHub CLI tooling, and Paperclip
control-plane API access. Exact hosted model ID/context-window metadata
is not exposed inside this 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
- [ ] 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>
## 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 is the open source app people use to manage AI agents for
work.
> - Operators need a way to inspect files and work products created by
agents across a company without opening each issue one by one.
> - The existing issue detail surfaces already show attachments and
outputs, but there was no company-level artifacts index or search-result
affordance for artifact-like records.
> - The backend needed a company-scoped artifacts projection API that
preserves issue/run attribution and safe links back to source records.
> - The UI needed a first-class Artifacts page, sidebar entry, reusable
artifact cards, and deep-link handling that keeps company prefixes
intact.
> - This pull request adds the company artifacts API and page, then
wires artifacts into search and issue output surfaces.
> - The benefit is a single place to browse, filter, and open generated
work products and attachments while preserving company boundaries.
## Linked Issues or Issue Description
Fixes#7622.
Feature request fields:
- Problem/motivation: company operators need a consolidated artifacts
surface for attachments and work products produced by agents.
- Proposed solution: add a company-scoped artifacts projection endpoint,
a board Artifacts route, reusable cards, sidebar navigation, and
artifact search integration.
- Alternatives considered: keep artifact discovery only on individual
issue pages; that forces operators to know the source issue before
finding generated outputs.
- Roadmap alignment: checked `ROADMAP.md`; this is a focused board
UI/API improvement and does not duplicate a listed roadmap item.
## What Changed
- Added shared artifact types and validators.
- Added a company-scoped artifact projection service/API with tests for
attachment/work-product attribution.
- Added Artifacts board UI route, API client, sidebar link, cards,
filters, and storybook coverage.
- Added artifact result handling to company search and issue
output/deep-link flows.
- Rebased the branch onto the latest `public-gh/master` state and
resolved the route-test conflict by preserving both upstream
team-catalog coverage and artifact route coverage.
- Fixed a local Sidebar test helper so it no longer depends on a
runtime-undefined `React.act` export in this dependency install.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/artifacts/ArtifactCard.test.tsx src/api/artifacts.test.ts
src/lib/company-routes.test.ts`
- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/Artifacts.test.tsx src/pages/Search.test.tsx
src/components/Sidebar.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/company-artifacts-service.test.ts
server/src/__tests__/company-search-service.test.ts
server/src/__tests__/company-search-rate-limit-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts`
- Confirmed the PR diff does not include `pnpm-lock.yaml` or
`.github/workflows/*`.
- Duplicate search: no open PRs or issues found for `artifact page
ArtifactCard` in `paperclipai/paperclip`.
Screenshots are intentionally omitted per the internal task instruction
not to add design screenshots or images to this PR unless they are
specifically part of the work. I also attempted browser capture in this
runner, but `agent-browser` failed to launch Chrome and Playwright
Chromium is missing `libatk-1.0.so.0`.
## Risks
- Low-to-medium risk: this adds a new API projection and UI surface, so
attribution/link regressions could affect artifact navigation.
- Company scoping is covered in the new service/API tests.
- No database migrations are included.
- No lockfile or workflow changes are included.
> 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 coding agent with tool use and local command
execution. Exact hosted model identifier is not exposed in this 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
- [ ] If this change affects the UI, I have included before/after
screenshots (intentionally omitted per task instruction; browser capture
unavailable in this runner)
- [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 is a control plane for AI-agent companies, so execution
policy and trust boundaries are part of the product's safety contract.
> - Low-trust review work needs narrower authority than normal
same-company agents because hostile PRs, comments, attachments, and
generated output can carry prompt-injection payloads.
> - The current V1 shape gives trusted workers broad company context,
which is useful for normal execution but too permissive for a reviewer
assigned to hostile content.
> - This branch adds a `low_trust_review` preset, source-trust tagging,
route-level containment, and quarantine handling so low-trust output
does not automatically flow into higher-trust wake context.
> - The branch has been rebased onto current `origin/master`, and the
low-trust migration was renumbered to `0097_low_trust_source_trust.sql`
to avoid collisions with existing `0091` through `0096` migrations.
> - Greptile feedback was addressed by tightening low-trust detection,
preserving project-level trust policy checks, fixing issue-kind
promotion lookup, removing duplicate post-lease isolation assertion,
documenting fail-closed source-trust behavior, bounding ancestry checks,
enforcing runtime issue context for CEOs, awaiting accepted-plan monitor
authorization, and making low-trust issue source-trust tagging atomic.
> - The benefit is a first production slice of deny-by-default review
containment with regression coverage for the main control-plane pivot
surfaces.
Fixes#7531.
## What Changed
- Added shared trust-policy types and validators, plus
database/source-trust fields for issues, comments, documents, and work
products.
- Implemented server enforcement for low-trust issue scope, agent
self-view redaction, secret/plugin/runtime denial paths, promotion
checks, and quarantined continuation/wake context.
- Added focused low-trust regression tests for resolver behavior, source
trust, route authorization, heartbeat preflight ordering, runtime
containment, and quarantine redaction.
- Added board UI affordances for selecting/reviewing the low-trust
preset and surfacing source-trust badges in relevant issue views.
- Added `doc/LOW-TRUST-PRESETS.md`, updated
`doc/SPEC-implementation.md`, and committed the low-trust review
contract plan under `doc/plans/`.
- Rebasing note: the original `0097_low_trust_source_trust.sql`
migration was renamed to `0097_low_trust_source_trust.sql`; the SQL uses
`ADD COLUMN IF NOT EXISTS` so users who already applied the old-numbered
migration are not broken by the renumbered migration.
## Verification
- Rebased branch onto current `origin/master` and force-pushed with
lease to `origin/PAP-10211-low-trust-agent` at head `2719f31e3`.
- Confirmed the PR diff does not include `pnpm-lock.yaml` or
`.github/workflows` changes.
- Resolved upstream UI/comment conflicts by preserving deleted-comment
tombstone behavior and low-trust source-trust badges/metadata.
- Renumbered the low-trust source-trust migration to
`0097_low_trust_source_trust.sql`; the SQL uses `ADD COLUMN IF NOT
EXISTS` so users who already applied an old-numbered copy are not
broken.
- `pnpm exec vitest run ui/src/lib/issue-chat-messages.test.ts
server/src/__tests__/heartbeat-workspace-session.test.ts`
- `pnpm exec vitest run server/src/__tests__/source-trust.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
ui/src/lib/trust-policy-ui.test.ts
ui/src/components/TrustPresetSection.test.tsx`
- `pnpm run typecheck:build-gaps`
- `git diff --check`
- GitHub checks pass on head `2719f31e3`: build, typecheck/release
registry, general tests, serialized server suites, e2e, canary, verify,
policy/review, Socket, and Snyk.
- Greptile Review passes with Confidence Score 5/5 and zero unresolved
Greptile review threads.
- No design screenshots/images were added because the task explicitly
says not to add them unless they are specifically part of the work.
## Risks
- Medium risk: this touches shared trust-policy contracts, server
authorization paths, heartbeat context generation, migration metadata,
and UI preset controls.
- Low-trust containment is intentionally deny-by-default; legitimate
future review workflows may need explicit allowlisted exceptions.
- Plugin/runtime/security surfaces are broad, so regression tests cover
the current known routes but future integrations must route through the
same containment layer.
- The PR is ready for review; GitHub checks are green and Greptile is
5/5.
> 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 coding agent, tool-enabled shell and GitHub CLI
workflow.
## 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
- [x] I have added or updated tests where applicable
- [x] UI changes are covered by focused tests; no screenshots were added
per task instruction not to add design images unless specifically
required
- [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>
Fixes#7551
## Thinking Path
> - Paperclip is the control plane for AI-agent companies, and reusable
company/team setup is part of making those companies faster to launch.
> - The teams catalog work introduces app-shipped team templates that
can be browsed, previewed, and installed into a company.
> - Catalog installation crosses several contracts: bundled package
contents, shared API types, server import/install behavior, CLI
workflows, and the board UI.
> - Agents also need a safe path through catalog installs: scoped
company selection, explicit source policy, approval fallback for agent
creation, and preserved catalog provenance.
> - This pull request extracts the completed teams catalog branch into
one reviewable PR on top of `public-gh/master`.
> - The benefit is a reusable teams catalog foundation with server, CLI,
package, docs, and hidden UI surfaces kept in sync.
## What Changed
- Added the `@paperclipai/teams-catalog` package with bundled/optional
team definitions, generated manifest, validators, catalog builder tests,
and migration notes.
- Added shared teams catalog types/validators plus server routes and
services for listing, previewing, and installing catalog teams.
- Integrated catalog install with company portability, skill/source
policy checks, provenance metadata, origin hashes, target-manager
reparenting, and installed/out-of-date detection.
- Added CLI `teams` commands and agent-safe company selection behavior,
including `company current` and approval fallback for forbidden
agent-run installs.
- Added hidden Team Catalog UI/API/query surfaces, Storybook fixtures,
and targeted UI tests while keeping the UI route out of primary
navigation.
- Added docs for CLI/company/teams catalog behavior and removed
generated screenshot artifacts from the PR diff.
## Verification
- `pnpm exec vitest run cli/src/__tests__/company.test.ts
cli/src/__tests__/teams.test.ts
packages/teams-catalog/src/catalog-builder.test.ts
packages/teams-catalog/src/shipped-catalog.test.ts
server/src/__tests__/agent-permissions-service.test.ts
server/src/__tests__/company-portability.test.ts
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/teams-catalog-routes.test.ts
server/src/__tests__/teams-catalog-service.test.ts
server/src/__tests__/teams-catalog-install-no-overrides.test.ts
ui/src/lib/company-routes.test.ts ui/src/pages/TeamCard.test.tsx
ui/src/pages/TeamCatalog.test.tsx
ui/src/pages/useInstallTeamCatalogEntry.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/teams-catalog typecheck && pnpm --filter paperclipai
typecheck && pnpm --filter @paperclipai/server typecheck && pnpm
--filter @paperclipai/ui typecheck`
- Confirmed branch is rebased onto `public-gh/master` (`78dc3625a`) and
`public-gh/master` is an ancestor of `HEAD`.
- Confirmed PR diff excludes `pnpm-lock.yaml`, `.github/workflows/*`,
generated screenshot images, and screenshot helper scripts.
## Risks
- Medium review surface: this crosses package generation, shared
contracts, server install behavior, CLI, docs, and hidden UI code.
- Catalog install behavior creates agents/projects/tasks/skills and must
keep company scoping, permissions, source policy, and provenance checks
strict.
- `pnpm-lock.yaml` is intentionally excluded per repo policy;
CI/default-branch automation owns lockfile refresh.
- The Team Catalog UI is included but hidden from primary navigation, so
future enablement should re-check visual QA before exposure.
> 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`.
>
> ROADMAP checked: this aligns with reusable companies/templates and
plugin-adjacent onboarding work. This PR packages work already developed
on the Paperclip task branch for review.
## Model Used
- OpenAI Codex, GPT-5 series coding agent in this Paperclip session;
exact runtime context window was not exposed. Used shell, git, `gh`, and
local test/typecheck tooling.
## 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
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots, or documented why screenshots are intentionally omitted
- [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>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Bumps [@cursor/sdk](https://github.com/cursor/cursor) from 1.0.12 to
1.0.18.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/cursor/cursor/commits">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>
Bumps [@codemirror/language](https://github.com/codemirror/language)
from 6.12.1 to 6.12.3.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/codemirror/language/blob/main/CHANGELOG.md">@codemirror/language's
changelog</a>.</em></p>
<blockquote>
<h2>6.12.3 (2026-03-25)</h2>
<h3>Bug fixes</h3>
<p>Fix a crash in <code>bracketMatching</code> when composing at end of
document.</p>
<h2>6.12.2 (2026-02-25)</h2>
<h3>Bug fixes</h3>
<p>Make sure brackets are highlighted in the initial editor state.</p>
<p>Pause bracket matching updates during composition, to avoid
disrupting Mobile Safari's fragile composition handling.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f5af31eed7"><code>f5af31e</code></a>
Mark version 6.12.3</li>
<li><a
href="371c9ba6be"><code>371c9ba</code></a>
Fix bogus bracket highlighting being generated at end of document</li>
<li><a
href="9531899bd8"><code>9531899</code></a>
Remove duplicated slash in forum url in README</li>
<li><a
href="2f4e7014a5"><code>2f4e701</code></a>
Fix forum link in readme</li>
<li><a
href="b5cd54b2ff"><code>b5cd54b</code></a>
Mark version 6.12.2</li>
<li><a
href="5f86763640"><code>5f86763</code></a>
Pause bracket matching updates during composition</li>
<li><a
href="af8dca9d06"><code>af8dca9</code></a>
Properly show matched brackets in the initial editor state</li>
<li><a
href="693a25efaa"><code>693a25e</code></a>
Use git+https format for package.json repository field</li>
<li>See full diff in <a
href="https://github.com/codemirror/language/compare/6.12.1...6.12.3">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>