Commit Graph
872 Commits
Author SHA1 Message Date
DottaandPaperclip e1df4c6068 fix(workspaces): keep deferred seed databases reliable (#11706)
## Thinking Path

> - Paperclip manages agent work in isolated execution workspaces.
> - A workspace depends on a valid database seed before it can run.
> - Deferred seed failures were hidden behind a successful provision
status.
> - The seed restore also had two possible owners for the embedded
PostgreSQL process.
> - That allowed the target database to stop while the restore was still
running.
> - This pull request makes seed failures visible and gives the seed
process sole lifecycle ownership.
> - The benefit is that workspace provisioning reports the real result
and does not stop its own target database.

## Linked Issues or Issue Description

Related: #11684

**What happened?**

Initial worktree provisioning could report success before its deferred
database seed completed. The seed restore could also reuse a target
embedded PostgreSQL process with another shutdown owner. This could stop
the target database during the restore.

**Expected behavior**

Workspace status must show a failed deferred seed as a failure. The seed
restore must own the target embedded PostgreSQL process until restore,
migration, and validation finish.

**Steps to reproduce**

1. Provision a worktree with deferred database seeding.
2. Make the seed manifest end in a failed state while the command exits
with code 0.
3. Observe that the provision status remains successful on `master`.
4. Start a seed restore against an already-running target embedded
PostgreSQL process.
5. Observe that another lifecycle owner can stop the target during
restore.

**Paperclip version or commit**

`51a843e135`

**Deployment mode**

Local dev with execution workspaces and embedded PostgreSQL.

## What Changed

- Add a first-class `workspace_seed` operation for deferred database
seeds.
- Require terminal, verified seed evidence before the seed operation
succeeds.
- Surface the seed phase and failure metadata in workspace status and UI
state.
- Give the seed process exclusive lifecycle ownership of the target
embedded PostgreSQL process.
- Suppress imported embedded-Postgres exit hooks without removing
existing host listeners.
- Record a credential-safe shutdown diagnostic in failed seed manifests.

## Verification

- The original deferred-seed commit passed 4 server tests, 24
workspace-status UI tests, shared/server/UI typechecks, and the UI token
gate.
- The original PostgreSQL-lifecycle commit passed 3 lifecycle tests, 3
ownership/diagnostic tests, 1 real embedded-Postgres seed integration,
and the affected package typechecks.
- No local tests were rerun after the clean cherry-pick because the
operator requested the shortest landing path.
- Review the automatic PR checks for the clean `origin/master` replay.

## Risks

- A live target database now causes an early error instead of being
reused. The error includes recovery guidance.
- Workspace consumers must handle the new `workspace_seed` operation
type. Shared types and UI state handling are updated in this pull
request.

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

## Model Used

- OpenAI Codex, GPT-5, high-reasoning mode, with repository, shell, and
GitHub tool use. The runtime does not expose a more specific deployment
suffix or context-window value.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-19 10:41:29 -05:00
DottaandPaperclip a2bf936f9a feat(workspaces): sign the workspace login handoff and gate readiness (#11671)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Managed worktree services run isolated Paperclip instances with
cloned databases.
> - A reachable service was reported as ready even when its database,
runtime identity, or login path was not usable.
> - The first candidate added verified database seeding and managed
repair in #11665.
> - This pull request consolidates that candidate with signed login
handoff and a complete readiness contract.
> - Post-QA fixes close five defects in repair identity, repair
responses, UI retry, seed journal handling, and seed-source trust.
> - The benefit is a workspace that either opens safely or reports one
accurate recovery action.

## Linked Issues or Issue Description

No public GitHub issue exists for this work, so the problem is described
here.

**What happened**

Managed workspace URLs could return HTTP 200 and report ready while
login failed. QA also found cases where repair used the wrong instance
identity, returned a generic error, left the UI stuck, rejected a safe
journal lag, or trusted a mutable workspace manifest.

**Expected behavior**

Opening a ready workspace signs the board user in to the correct
isolated instance. Provisioning and repair use a registered source and
report a structured recovery state.

**Actual behavior**

Entry depended on a password copied into the clone. Several failure
paths could publish stale readiness, hide the repair precondition, or
trust state that the workspace could modify.

**Additional context**

This pull request includes the commits first published in #11665. That
pull request keeps the original base head for review history. This
consolidated pull request is the merge candidate. Related open readiness
work includes #11575 and #11621.

## What Changed

- Adds a short-lived, signed, single-use login ticket. It binds the
user, workspace, instance, and runtime origin.
- Exchanges the ticket through Better Auth. It creates the session and
cookie through the supported adapter path.
- Adds protected workspace readiness fields for the database, clone
data, login handoff, seed phase, and runtime identity.
- Fails readiness closed when the guest has no company or
execution-workspace binding.
- Binds ticket issuance to the exact cloned user and active company
membership selected for the handoff.
- Verifies every current active board identity through the exact-user
handoff before publication or reuse.
- Gates managed runtime publication on the readiness contract and the
recorded worktree instance identity.
- Refreshes runtime work products from the live runtime row after a port
change.
- Adds one workspace access card with ready, degraded, repairing, and
failed states.
- Uses the runtime response identity for repair. It returns structured
repair precondition errors.
- Lets a valid source journal lag converge during provisioning.
- Binds seed and repair manifests to a source registered outside the
agent-writable worktree.
- Clears recovered UI errors so a successful retry can open the
workspace.
- Makes runtime tests register canonical sources and avoid ports owned
by live host listeners.
- Keeps Vitest on source suites when compiled `dist` trees exist.
- Isolates CLI and adapter tests from ambient AWS and runtime API
environment variables.
- Preserves a 404 response for cross-company workspace ID lookups before
runtime authorization.
- Makes concurrent single-flight coverage independent of
path-canonicalization scheduling order.

## Verification

The following checks passed on the integrated head:

```sh
pnpm -r typecheck
pnpm build
pnpm check:token-gates
pnpm --filter @paperclipai/db check:migrations
```

- The server source lane passed 420 files and 4,953 tests. Five tests
were skipped.
- The CLI lane passed 57 files and 385 tests.
- The database lane passed 26 files and 97 tests.
- The shared package passed 58 files and 506 tests.
- The adapter utility lane passed 640 tests. Four tests were skipped.
- The Claude adapter passed 220 tests. One test was skipped.
- The Codex adapter passed 323 tests.
- The OpenClaw adapter passed 13 tests.
- The OpenCode adapter passed 42 tests.
- The plugin SDK passed 45 tests.
- The workspace runtime suite passed 124 tests.
- The caller-scoped readiness and handoff suite passed 52 tests.
- The workspace provisioning shell suite passed 7 tests.
- The runtime exposure suite passed 17 tests while live host mappings
occupied fixed test ports.
- `git diff --check` passed and the worktree is clean.

The serialized route lane will run in GitHub CI with its normal shards.
No deployment or active-workspace migration was performed.

## Risks

- This is a medium-risk authentication and runtime-readiness change.
- The login ticket uses exact origin, workspace, instance, and user
binding. It has a short expiry and a one-time nonce.
- Runtime publication is stricter. A real readiness, identity, per-user
handoff, or control-plane database disagreement now blocks publication.
- This pull request supersedes #11665 as the merge candidate. Close
#11665 after this pull request merges.
- No new database migration is included. The lockfile and workflow files
are unchanged.
- Deployment and active-workspace migration are intentionally outside
this pull request.

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

## Model Used

Claude Opus 5 (`claude-opus-5[1m]`), 1M context, extended thinking, tool
use, and code execution produced the main candidate. OpenAI GPT-5
(`gpt-5`) through Codex, with agentic reasoning, tool use, and code
execution, integrated the post-QA fixes and hardened the test gates. The
Codex context-window size was not exposed.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-19 02:37:02 -05:00
3dd2d21e4d build(deps-dev): bump @storybook/react-vite from 10.5.0 to 10.5.8 (#11514)
Bumps
[@storybook/react-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/react-vite)
from 10.5.0 to 10.5.8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/storybookjs/storybook/releases">@​storybook/react-vite's
releases</a>.</em></p>
<blockquote>
<h2>v10.5.8</h2>
<h2>10.5.8</h2>
<ul>
<li>React: Fix RDT tsconfig selection for Vite project references - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35743">#35743</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>Tanstack React: Remove <code>@​cloudflare/vite-plugin</code> from
the inherited Vite config - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35706">#35706</a>,
thanks <a
href="https://github.com/FrancoKaddour"><code>@​FrancoKaddour</code></a>!</li>
<li>Tanstack: Wait for router to load before rendering - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35784">#35784</a>,
thanks <a
href="https://github.com/huang-julien"><code>@​huang-julien</code></a>!</li>
<li>Test: Fix Illegal invocation when reading prototype.focus - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35528">#35528</a>,
thanks <a
href="https://github.com/FrancoKaddour"><code>@​FrancoKaddour</code></a>!</li>
</ul>
<h2>v10.5.7</h2>
<h2>10.5.7</h2>
<ul>
<li>Angular: Serve ancestor node_modules for addon-vitest in browser
mode - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35600">#35600</a>,
thanks <a
href="https://github.com/brandonroberts"><code>@​brandonroberts</code></a>!</li>
<li>Refactor: Update getVersionedPackages method to handle non-Storybook
packages correctly - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35769">#35769</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
</ul>
<h2>v10.5.6</h2>
<h2>10.5.6</h2>
<ul>
<li>Dependencies: Pin `@testing-library/jest-dom` to `6.9.1` - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35614">#35614</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>ESLint Plugin: Add plugin meta and document oxlint usage - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35655">#35655</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Vue: Skip docgen for module ids carrying a query - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35598">#35598</a>,
thanks <a
href="https://github.com/seanogdev"><code>@​seanogdev</code></a>!</li>
</ul>
<h2>v10.5.5</h2>
<h2>10.5.5</h2>
<ul>
<li>CLI: Update AI setup instructions to msw-storybook-addon v3 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35512">#35512</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Core: Upgrade `ws` to fix security advisories - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35584">#35584</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>ReactNative: Telemetry framework detection fix - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35560">#35560</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>SyntaxHighlighter: Fix PrismJS dark mode mismatch - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35541">#35541</a>,
thanks <a
href="https://github.com/hxy-asdw"><code>@​hxy-asdw</code></a>!</li>
<li>TanStack: Preserve explicit route ids on pathful clones - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35499">#35499</a>,
thanks <a
href="https://github.com/unpunnyfuns"><code>@​unpunnyfuns</code></a>!</li>
<li>TanStack: Resolve mock redirects through Vite's resolver - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35501">#35501</a>,
thanks <a
href="https://github.com/unpunnyfuns"><code>@​unpunnyfuns</code></a>!</li>
<li>TanStack: Respect routeOverrides component overrides in stories - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35497">#35497</a>,
thanks <a
href="https://github.com/unpunnyfuns"><code>@​unpunnyfuns</code></a>!</li>
</ul>
<h2>v10.5.4</h2>
<h2>10.5.4</h2>
<ul>
<li>ReactNative: Telemetry framework detection fix - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35560">#35560</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>SyntaxHighlighter: Fix PrismJS dark mode mismatch - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35541">#35541</a>,
thanks <a
href="https://github.com/hxy-asdw"><code>@​hxy-asdw</code></a>!</li>
</ul>
<h2>v10.5.3</h2>
<h2>10.5.3</h2>
<ul>
<li>Dependencies: Upgrade TypeScript to 6.0.3 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34971">#34971</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
</ul>
<h2>v10.5.2</h2>
<h2>10.5.2</h2>
<ul>
<li>Angular-Vite: Drop <code>@​angular/platform-browser-dynamic</code>
peer dependency - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35457">#35457</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Angular-Vite: Widen TypeScript peer dependency range to support
TypeScript 6 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35455">#35455</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Core: Include chromatic packages in ecosystem identifier - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35170">#35170</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>TanStack: Fix createServerFn validator mock - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35185">#35185</a>,
thanks <a
href="https://github.com/sjh9714"><code>@​sjh9714</code></a>!</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md">@​storybook/react-vite's
changelog</a>.</em></p>
<blockquote>
<h2>10.5.8</h2>
<ul>
<li>React: Fix RDT tsconfig selection for Vite project references - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35743">#35743</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>Tanstack React: Remove <code>@​cloudflare/vite-plugin</code> from
the inherited Vite config - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35706">#35706</a>,
thanks <a
href="https://github.com/FrancoKaddour"><code>@​FrancoKaddour</code></a>!</li>
<li>Tanstack: Wait for router to load before rendering - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35784">#35784</a>,
thanks <a
href="https://github.com/huang-julien"><code>@​huang-julien</code></a>!</li>
<li>Test: Fix Illegal invocation when reading prototype.focus - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35528">#35528</a>,
thanks <a
href="https://github.com/FrancoKaddour"><code>@​FrancoKaddour</code></a>!</li>
</ul>
<h2>10.5.7</h2>
<ul>
<li>Angular: Serve ancestor node_modules for addon-vitest in browser
mode - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35600">#35600</a>,
thanks <a
href="https://github.com/brandonroberts"><code>@​brandonroberts</code></a>!</li>
<li>Refactor: Update getVersionedPackages method to handle non-Storybook
packages correctly - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35769">#35769</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
</ul>
<h2>10.5.6</h2>
<ul>
<li>Dependencies: Pin <code>@testing-library/jest-dom</code> to
<code>6.9.1</code> - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35614">#35614</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>ESLint Plugin: Add plugin meta and document oxlint usage - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35655">#35655</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Vue: Skip docgen for module ids carrying a query - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35598">#35598</a>,
thanks <a
href="https://github.com/seanogdev"><code>@​seanogdev</code></a>!</li>
</ul>
<h2>10.5.5</h2>
<ul>
<li>CLI: Update AI setup instructions to msw-storybook-addon v3 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35512">#35512</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Core: Upgrade <code>ws</code> to fix security advisories - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35584">#35584</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>ReactNative: Telemetry framework detection fix - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35560">#35560</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>SyntaxHighlighter: Fix PrismJS dark mode mismatch - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35541">#35541</a>,
thanks <a
href="https://github.com/hxy-asdw"><code>@​hxy-asdw</code></a>!</li>
<li>TanStack: Preserve explicit route ids on pathful clones - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35499">#35499</a>,
thanks <a
href="https://github.com/unpunnyfuns"><code>@​unpunnyfuns</code></a>!</li>
<li>TanStack: Resolve mock redirects through Vite's resolver - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35501">#35501</a>,
thanks <a
href="https://github.com/unpunnyfuns"><code>@​unpunnyfuns</code></a>!</li>
<li>TanStack: Respect routeOverrides component overrides in stories - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35497">#35497</a>,
thanks <a
href="https://github.com/unpunnyfuns"><code>@​unpunnyfuns</code></a>!</li>
</ul>
<h2>10.5.4</h2>
<ul>
<li>ReactNative: Telemetry framework detection fix - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35560">#35560</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>SyntaxHighlighter: Fix PrismJS dark mode mismatch - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35541">#35541</a>,
thanks <a
href="https://github.com/hxy-asdw"><code>@​hxy-asdw</code></a>!</li>
</ul>
<h2>10.5.3</h2>
<ul>
<li>Dependencies: Upgrade TypeScript to 6.0.3 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34971">#34971</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
</ul>
<h2>10.5.2</h2>
<ul>
<li>TanStack: Fix createServerFn validator mock - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35185">#35185</a>,
thanks <a
href="https://github.com/sjh9714"><code>@​sjh9714</code></a>!</li>
<li>TanStack: Support pathless layout routes (id-only) in story routing
- <a
href="https://redirect.github.com/storybookjs/storybook/pull/35465">#35465</a>,
thanks <a
href="https://github.com/unpunnyfuns"><code>@​unpunnyfuns</code></a>!</li>
<li>Tanstack-react: Add missing Hydrate export - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35111">#35111</a>,
thanks <a
href="https://github.com/arun-357"><code>@​arun-357</code></a>!</li>
<li>Tanstack-react: Keep JSX-only component references during dead-code
elimination - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35206">#35206</a>,
thanks <a
href="https://github.com/yatishgoel"><code>@​yatishgoel</code></a>!</li>
<li>Vitest: Fix coverage toggle crash on Vite 6 by clearing closed
Vitest instance on restart - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35461">#35461</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
</ul>
<h2>10.5.1</h2>
<ul>
<li>Angular-Vite: Drop <code>@​angular/platform-browser-dynamic</code>
peer dependency - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35457">#35457</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Angular-Vite: Widen TypeScript peer dependency range to support
TypeScript 6 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35455">#35455</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Core: Include chromatic packages in ecosystem identifier - <a
href="https://redirect.github.com/storybookjs/storybook/pull/35170">#35170</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/storybookjs/storybook/commit/6ef7d1ae816ebd5fb8bf84b8dec7d4a92410d73c"><code>6ef7d1a</code></a>
Bump version from &quot;10.5.7&quot; to &quot;10.5.8&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/647e982151f1bb4e15163b0d2a31c5a1022efda3"><code>647e982</code></a>
Merge pull request <a
href="https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/react-vite/issues/35743">#35743</a>
from storybookjs/norbert/revive-34415-file-aware-ts...</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/7c6fb3a5ecf4495d73de6d70f802251934e079bd"><code>7c6fb3a</code></a>
Bump version from &quot;10.5.6&quot; to &quot;10.5.7&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/43f2316559b30948d9ef724e5cb3140ddeca8867"><code>43f2316</code></a>
Merge pull request <a
href="https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/react-vite/issues/35490">#35490</a>
from storybookjs/valentin/remove-tsconfig-baseurl</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/3126f0a14a351e971679f61cd0bd5609d086ed57"><code>3126f0a</code></a>
Bump version from &quot;10.5.5&quot; to &quot;10.5.6&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/05a52b7a888c6b85c3f8aa6765ed9a0a69a79e4c"><code>05a52b7</code></a>
Bump version from &quot;10.5.4&quot; to &quot;10.5.5&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/3327dc44697304275e28ceaa2cd34d9bede4e333"><code>3327dc4</code></a>
Bump version from &quot;10.5.3&quot; to &quot;10.5.4&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/9ac273930a49ad33b6a331f1f36dc472f5c36054"><code>9ac2739</code></a>
Bump version from &quot;10.5.2&quot; to &quot;10.5.3&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/b4b00f27662caf7328330b5ab47e9b903218f43a"><code>b4b00f2</code></a>
Merge pull request <a
href="https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/react-vite/issues/34971">#34971</a>
from storybookjs/valentin/upgrade-typescript-6</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/518f711cb367d8df184be22f1fab9a218b2743df"><code>518f711</code></a>
Bump version from &quot;10.5.1&quot; to &quot;10.5.2&quot; [skip
ci]</li>
<li>Additional commits viewable in <a
href="https://github.com/storybookjs/storybook/commits/v10.5.8/code/frameworks/react-vite">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>
Co-authored-by: Priya Raman <priya@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-19 00:27:37 -07:00
DottaandPaperclip 1b4de0b65c fix(workspaces): recover degraded runtime databases (#11651)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Managed workspaces run local web services and embedded PostgreSQL
databases
> - A listening process could return an unhealthy response and still be
reused
> - Embedded PostgreSQL failures had no bounded restart owner
> - Cleanup inferred ownership from a branch slug instead of exact
persisted instance data
> - This pull request validates runtime health, supervises database
recovery, and uses exact cleanup ownership
> - The benefit is reliable replacement of degraded services without
deleting active instances

## Linked Issues or Issue Description

**What happened?**

Workspace reconciliation could reuse a degraded Paperclip process after
any successful HTTP response. Embedded PostgreSQL could stop without
bounded recovery. Cleanup could infer database ownership from a branch
slug and select the wrong instance.

**Expected behavior**

Paperclip must require a semantic healthy response from the assigned
loopback listener. It must replace degraded processes. It must supervise
embedded PostgreSQL with bounded restarts. Cleanup must use exact
persisted worktree and instance-root ownership.

**Steps to reproduce**

1. Start a managed workspace runtime.
2. Make its health endpoint return HTTP 200 with an unhealthy status, or
stop its embedded PostgreSQL process.
3. Reconcile the workspace or run instance cleanup.
4. Observe that the old implementation can reuse the degraded runtime or
infer ownership from its branch slug.

**Paperclip version or commit**

Current `master` before this pull request.

**Deployment mode**

Local dev with managed workspace services.

## What Changed

- Require `{ "status": "ok" }` from the assigned loopback health
endpoint before runtime reuse or adoption.
- Refresh persisted runtime health and replace degraded managed
processes.
- Add bounded embedded PostgreSQL restart supervision with coordinated
shutdown and hot-restart support.
- Stop the unhealthy web process when PostgreSQL recovery is exhausted
so reconciliation can replace it.
- Require exact persisted instance-root ownership before cleanup can
reclaim an embedded database.
- Add focused regression tests for degraded HTTP responses, ownership
mismatches, bounded recovery, active instance preservation, and
confirmed orphan reclamation.

## Verification

- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/server test --
src/embedded-postgres-supervisor.test.ts
src/services/workspace-instance-cleanup.test.ts
src/services/workspace-runtime.test.ts
src/services/execution-workspaces-service.test.ts`
- `pnpm -r typecheck`
- `pnpm build`
- `git diff --check`
- The full local stable test runner also found host-owned listeners on
ports 42000 and 52000. Those listeners conflict with the exposure test
fixture. The focused changed suites pass, and CI runs on a clean host.

## Risks

- A custom process that returns HTTP 2xx without the Paperclip health
contract is now degraded by design.
- Restart exhaustion terminates the managed web process. The runtime
reconciler then starts a clean process.
- Cleanup now fails closed when persisted ownership is missing. This can
retain an ambiguous orphan for manual review.

> 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. The exact serving revision and context-window size
are not exposed. The model used agentic reasoning, repository tools,
code execution, and test 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)
- [ ] 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>
2026-08-18 17:40:54 -05:00
Nicky LeachandPaperclip 120ae5428f feat(server): add a one-click relink action for detached custom-image templates (#11641)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip environments can use captured custom images for agent runs
> - A configuration fingerprint change can detach a valid custom-image
template
> - Operators need a safe way to confirm that the image still matches
the boot source
> - This pull request adds a guarded relink action with drift
classification and audit logging
> - The benefit is a deliberate relink without a new sandbox boot or
provider snapshot

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting environment, server, and UI behavior.

**Problem or motivation**

A custom-image template detaches when the environment configuration
fingerprint changes. The runtime then uses the base image, even when the
boot source did not change. The only prior remedy required a full
re-capture.

**Proposed solution**

Add an operator-triggered relink action. Classify configuration drift
from a server-owned boot-relevant snapshot. Relink knob-only drift
without confirmation. Require explicit confirmation for boot-source or
unclassified drift. Guard the route for instance administrators and
record a safe activity event.

**Alternatives considered**

Keep requiring a full re-capture. This adds a sandbox boot and provider
snapshot for cases where the image remains correct.

**Roadmap alignment**

The roadmap has no matching custom-image relink item. This change
addresses an environment operation gap.

**Additional context**

The relink response exposes raw drift values only in the transient 409
response to the instance administrator. The service never persists or
logs fingerprints or configuration values. Reserved identity-path
segments fail closed.

## What Changed

- Add `relinkActiveTemplate` with drift classification and conditional
fingerprint update.
- Persist a server-owned boot-relevant configuration snapshot during
capture.
- Add the guarded relink route with strict request validation and
activity logging.
- Add the relink action and confirmation flow to the environment page.
- Add service, route, UI, and OpenAPI coverage.

## Verification

- Run the focused service suite: `pnpm vitest run
server/src/services/environment-custom-images-service.test.ts`.
- Run the focused route suite: `pnpm vitest run
server/src/routes/environment-custom-image-routes.test.ts`.
- Run the focused UI suite: `pnpm vitest run
ui/src/pages/CompanyEnvironments.test.tsx`.
- Run server and UI TypeScript checks.
- Confirm the OpenAPI snapshot matches the new route.
- Confirm all required GitHub checks pass on commit
`e46fdcfe94a719be854adf8849d30714e5b70b93`.
- Confirm Greptile reports 5/5 with no unresolved review threads.

## Risks

The relink action can keep an image after configuration drift. The
service requires explicit confirmation for boot-source or unclassified
drift. Reserved path segments produce a safe unresolved marker and never
enter stored values.

## Model Used

OpenAI GPT-5 Codex. The model used repository inspection, GitHub
operations, and PR preparation with tool use and code execution. The
runtime did not expose a context-window value or a separate
reasoning-mode value.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-18 11:43:39 -07:00
fe803bedf1 feat(server): add a configurable cooldown to the terminal workspace reaper (#11642)
## Thinking Path

> - Paperclip is an open source app that manages AI agents for work.
> - The server manages execution workspaces and their worktrees.
> - The terminal workspace reaper removes a workspace when its issue
tree reaches a terminal state.
> - Immediate removal prevents a person from reopening recently
completed work.
> - This pull request adds a configurable cooldown before the reaper
archives the workspace.
> - The cooldown keeps recent work available and keeps immediate cleanup
available with value `0`.

## Linked Issues or Issue Description

Refs: #7790

**Problem**

The reaper archives an execution workspace and deletes its worktree as
soon as the issue tree becomes terminal. A person cannot reopen recent
work without extra effort.

**Expected behavior**

The reaper should keep a recently completed workspace during a
configurable cooldown window. It should archive older work and support
immediate cleanup when the value is `0`.

**Proposed solution**

Read the cooldown from `PAPERCLIP_WORKSPACE_REAPER_COOLDOWN_DAYS`. Use a
seven-day default. Use the latest terminal timestamp in the source issue
tree as the cooldown anchor.

## What Changed

- Add `PAPERCLIP_WORKSPACE_REAPER_COOLDOWN_DAYS` with a seven-day
default.
- Treat `0` as no cooldown and use the default for negative or
non-numeric values.
- Use the latest `completedAt` or `cancelledAt` value in the source
issue tree.
- Use `updatedAt` when a terminal timestamp is null.
- Skip candidates inside the cooldown and report them in
`skippedCooldown`.
- Recheck the cutoff during the guarded archive operation.
- Document the environment variable and add focused tests.

## Verification

- Run `npx vitest run
server/src/__tests__/execution-workspaces-service.test.ts`.
- Confirm that the test run passes 66 tests.
- Confirm that the tests cover a recent tree, an old tree, value `0`,
and a null terminal timestamp.
- Confirm that the changed files pass `tsc --noEmit`.

## Risks

The default changes terminal workspace cleanup from immediate removal to
a seven-day delay. A value of `0` preserves immediate cleanup. The
guarded archive check limits race risk during concurrent lifecycle
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.

## Model Used

OpenAI Codex, GPT-5, tool use and code execution. This model assisted
with the implementation review and PR preparation.

## 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>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-08-18 11:23:27 -07:00
Nicky LeachandPaperclip 0e9b03832d fix(server): skip host provisionCommand for sandbox-driver environments (#11626)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The run orchestrator prepares an execution environment before an
agent starts
> - A sandbox driver creates a remote folder before the adapter uploads
repository content
> - The orchestrator ran the host `provisionCommand` in that empty
folder
> - The command failed with exit 127 before the adapter could run its
`stage.sync` step
> - This pull request skips host provisioning for sandbox drivers and
keeps the existing local and SSH behavior
> - The benefit is that sandbox runs reach the adapter sync step without
an empty-folder setup failure

## Linked Issues or Issue Description

**What happened?**

A sandbox environment ran the host `provisionCommand` before the adapter
uploaded repository content. The command ran in an empty remote folder
and failed with exit 127.

**Expected behavior**

The orchestrator should skip host provisioning for a sandbox driver. The
adapter should upload the provisioned tree during its `stage.sync` step.

**Steps to reproduce**

1. Configure an environment with the `sandbox` driver and a host
`provisionCommand`.
2. Start a run that uses this environment.
3. Observe that the command runs in the empty sandbox folder and the run
fails with `setup_failed`.

**Paperclip version or commit**

Reproduced on the current `master` commit before this change.

**Deployment mode**

Built from source with a sandbox environment.

**Installation method**

Built from source with pnpm.

**Agent adapter(s) involved**

Not adapter-specific (core bug). The sandbox adapter syncs the tree
after environment setup.

**Database mode**

Not database-related.

**Additional context**

Related context:
[#11091](https://github.com/paperclipai/paperclip/pull/11091) changes
provision behavior for reused workspaces. This pull request covers the
separate sandbox ordering failure.

## What Changed

- Skip the orchestrator provision step when `environment.driver` is
`sandbox`.
- Keep the existing skip for `local` and the provision step for `ssh`.
- Log one info message when a sandbox skip drops a present command.
- Keep the existing `plugin` path because it has no `stage.sync` step
and runs against the host filesystem.
- Add tests for sandbox, local, SSH, plugin, logging, and provision
failures.

## Verification

- Run `./node_modules/.bin/vitest run
server/src/__tests__/environment-run-orchestrator.test.ts`.
- Confirm that the test run passes all 10 tests.
- Confirm that CI checks pass on this pull request.

## Risks

- Low risk. The change affects only the provision gate for sandbox
drivers.
- SSH and local behavior stays unchanged.
- The plugin driver stays on its current path.
- The new log line makes a sandbox skip visible to operators.

## Model Used

OpenAI, GPT-5, exact runtime model `gpt-5`, with tool use and code
review support. The implementation author used this model to inspect
code, edit source and tests, and run the targeted test suite.

## 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 exact duplicate found; related PR #11091 reviewed)
- [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 (no
documentation change applies to this internal gate correction)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-18 09:29:31 -07:00
DottaandPaperclip 6b8e42168e Add governed secret alias confirmation cards (#11486)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents need scoped secret bindings to use external services safely.
> - Agents could not request an existing secret under a new config name
without an internal secret identifier.
> - Existing binding proposals were only visible in Settings and did not
create an issue-thread approval path.
> - A confirmation card could record acceptance without proving that the
binding was created.
> - This pull request extends the existing secret proposal system with
safe source references and governed issue-thread confirmation cards.
> - The benefit is a one-click flow that creates the binding or shows a
clear failure without exposing secret material.

## Linked Issues or Issue Description

Related prerequisite: #11482.

**Subsystem affected**

Cross-cutting: server REST APIs, shared interaction contracts, database
proposal schema, and issue-thread UI.

**Problem or motivation**

An agent can need an existing bound secret under a second config name.
The agent cannot safely discover the internal secret identifier. The
existing proposal is also easy for the operator to miss because it only
appears in Settings. A generic confirmation can record acceptance
without executing the binding.

**Proposed solution**

Let an agent create a binding proposal from one of its existing config
paths. Mint a server-owned, human-only confirmation card on the
checked-out issue. Recheck the operator's target-agent permission under
the proposal row lock. Execute the existing proposal transaction after
card acceptance. Store an `executed` or `failed` result on the card.
Render the complete lifecycle in the issue thread and attention
resolver.

**Alternatives considered**

A new alias subsystem would duplicate proposal quotas, expiry,
authorization, and binding synchronization. A text-only issue comment
would not provide a governed action or an execution result. An
agent-supplied card payload would permit metadata smuggling. This change
uses the existing proposal transaction and a server-owned payload
instead.

**Roadmap alignment**

This change extends the completed "Secrets Manager with per-agent
access" roadmap item. It preserves scoped bindings and audited
resolution. The required GitHub search found no other open duplicate
issue or pull request.

## What Changed

- Added safe source-config-path binding proposals and preserved
user-secret ownership checks.
- Added a proposal-to-interaction link and an idempotent database
migration.
- Minted human-only `request_confirmation` cards with server-owned
`secretProposal` metadata.
- Rejected agent-supplied governed metadata and agent addressees.
- Rechecked `agent_config:update` authority under the proposal lock
before execution.
- Recorded `executed` or `failed` results and posted a failure comment
when no binding was created.
- Settled failed accepted proposals atomically and mirrored rejection,
withdrawal, and expiry in both directions.
- Emitted `secret.binding.created` for new agent binding writes.
- Added a dedicated issue-thread card for pending, executed, failed,
rejected, withdrawn, and expired states.
- Showed only the source label, target agent, config path, skeptical
justification, expiry, and safe failure code.
- Replaced resolved attention-query entries immediately with the
stitched server result.
- Added focused server, database, UI, and state-transition tests.
- Added Storybook fixtures for every review state and documented the API
and agent behavior.

## Verification

- `pnpm exec vitest run
ui/src/components/IssueThreadInteractionCard.test.tsx
ui/src/components/AttentionInteractionResolver.test.ts` — 58 passed.
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
- `pnpm build-storybook`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/db check:migrations`
- `NODE_ENV=test pnpm exec vitest run
server/src/__tests__/issue-thread-interaction-routes.test.ts
server/src/__tests__/secret-proposals-routes.test.ts
server/src/__tests__/secrets-routes.test.ts
server/src/__tests__/agents-service-secret-bindings.test.ts` — 142
passed.
- `NODE_ENV=test pnpm --filter @paperclipai/db exec vitest run
src/company-secret-proposals-migration.test.ts --silent` — 1 passed.
- `pnpm -r typecheck`
- `pnpm test:run` — server 4,175 passed, UI 4,109 passed; the CLI
AWS-doctor case passes 8/8 with runtime-injected static AWS credential
variables unset.
- `pnpm build`
- `git diff --check origin/master...HEAD`

## Risks

- Migration `0221` adds one nullable foreign key and one index. It uses
idempotent guards.
- The accept route performs a governed write after it records card
acceptance. A failed write is visible and settles the proposal as
rejected.
- Concurrent proposal and card resolution must use
proposal-before-interaction lock order. A race test covers direct
approval against card rejection.
- The new audit event increases activity rows for newly added agent
bindings. It does not include secret values or fingerprints.
- The card includes only safe proposal metadata. It does not include
secret value, fingerprint, version, or internal secret identifiers.
- The UI uses the stitched resolution result. Focused tests cover
immediate cache replacement and every terminal state.

> This work extends an existing completed roadmap capability. The GitHub
duplicate search returned no other open related work.

## Model Used

- OpenAI Codex with model ID `gpt-5`. The runtime did not expose its
context-window size. Reasoning, repository tools, code execution,
database integration tests, UI rendering, and GitHub tools 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 (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-18 09:44:24 -05:00
DottaandPaperclip 8087661bb8 fix: bound workspace Git scans (#11572)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Workspaces let users and agents inspect files that belong to an
issue
> - Changed-file views use full-tree Git status scans
> - Many issue views could start those scans at the same time and make
the server unresponsive
> - Route-level limits did not protect the process or coalesce work for
one repository
> - This pull request adds one bounded scheduler for every expensive
workspace Git scan
> - It also starts browser scans only when the file panel is open and
visible
> - The benefit is bounded child-process use and responsive health
checks during request storms

## Linked Issues or Issue Description

**What happened?**

Many changed-file requests could start full `git status --porcelain=v1
-z --untracked-files=all` scans at the same time. One production
incident produced about 270 direct Git child processes. The Node process
stayed alive but stopped answering health requests in time.

**Expected behavior**

Paperclip must bound expensive Git work across all companies, actors,
issues, repositories, and browser tabs. Duplicate requests for one
worktree must share work. Excess requests must fail fast with a
retryable response. Hidden or closed file panels must not start scans.

**Steps to reproduce**

1. Open changed-file views for many issue and actor keys.
2. Send requests for two large workspace roots at the same time.
3. Observe that route-level limiter keys allow many full Git scans to
run together.
4. Observe delayed health responses and accumulated Git children.

**Paperclip version or commit**

Reproduced on master before commit `43ab441f0f`.

**Deployment mode**

Self-hosted server with local workspace repositories.

## What Changed

- Add a process-wide scheduler with configurable concurrency, queue
capacity, timeout, and cache TTL.
- Add fair admission, a bounded queue, canonical worktree keys,
single-flight joins, and bounded result caching.
- Add subprocess timeouts, TERM-to-KILL escalation, bounded output,
waiter cancellation, and slot cleanup.
- Route full-tree status work from file resources, workspace runtime,
execution workspaces, and adapter overlay sync through the scheduler.
- Return stable retryable `503` and `504` error codes for saturation and
timeout.
- Add structured logs with safe workspace hashes, durations, queue
state, cache use, joins, and terminal outcomes.
- Gate UI queries on panel and document visibility. Cancel queries on
close, hide, unmount, and workspace change.
- Disable focus and reconnect bursts. Keep one explicit refresh action
and a retryable unavailable state.
- Document the 10-second default freshness tradeoff and all
configuration variables.
- Add unit, route, UI, adapter, and deterministic 500-request load
coverage.

## Verification

- `pnpm -r typecheck`
- `pnpm build`
- `pnpm check:token-gates`
- `pnpm --filter @paperclipai/server exec vitest run
src/services/workspace-git-operation-scheduler.test.ts
src/__tests__/file-resources-git-scan-load.test.ts --reporter=dot` — 16
tests passed.
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/WorkspaceFileBrowser.test.tsx
src/lib/page-visibility.test.ts --reporter=dot` — 38 tests passed.
- `pnpm --filter @paperclipai/adapter-utils exec vitest run
src/git-workspace-sync.test.ts --reporter=dot` — 16 tests passed.
- Existing file-resource, workspace-runtime, and execution-workspace
regression selections passed.
- Two cleanup safety regressions prove failed scans preserve the
worktree before archive and at the final deletion fence.
- Before: the incident produced about 270 Git children and health
requests timed out.
- After: 500 concurrent requests across 500 issue keys, 73 actors, and
two roots started two underlying scans. Peak scan concurrency was 2. All
500 requests succeeded. Health p99 was 4.94 ms. The harness found zero
unreaped children.
- The full local Vitest run passed 4,267 tests. Ten existing fixed-port
HTTPS exposure tests could not run because this host already owns
Tailnet listeners on ports 42000 and 52000. Clean GitHub CI is the final
full-suite result.
- Latest-head GitHub CI passed all required test, typecheck, build,
canary, e2e, policy, and security gates.
- Greptile completed at 5/5 with zero unresolved comments,
recommendations, or follow-ups.

## Risks

- Changed-file results can be up to 10 seconds old by default. Explicit
refresh remains available.
- A full queue returns a retryable `503` instead of waiting without a
bound.
- A scan that exceeds the default 8-second deadline returns a retryable
`504` and terminates its process group.
- Operators can tune all limits with documented environment variables.
Safe defaults protect local and shared servers.

> 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. The runtime does not expose the exact
deployment ID or context-window size. High reasoning, 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 (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-17 22:11:30 -05:00
Devin Foley 962e98b1be feat(server): operator declaration for platform edge TLS termination on the Claude login guard (#11579)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Claude local adapter supports a setup-token subscription login,
and its confidential routes pass a fail-closed transport guard
> - The guard accepts direct socket TLS, a local_trusted loopback peer,
or an allowlisted proxy peer that forwards https — and deliberately
never reads the global `TRUST_PROXY`
> - On a managed platform the edge terminates TLS, the app socket is
always plain HTTP, and the edge-proxy peer addresses are not stable or
documented, so none of the three cases can hold
> - Every login on such a deployment shows the clear-text transport
warning although the user's connection is HTTPS, and the agent-scoped
confidential routes fail closed entirely
> - This pull request adds a dedicated operator declaration that the
platform edge terminates TLS, as a fourth guard case
> - The benefit is a correct transport decision on managed platforms
with the default posture unchanged everywhere else

## Linked Issues or Issue Description

No public GitHub issue covers this. The problem is described in-PR
following the enhancement template. Related public PRs:
[#11347](https://github.com/paperclipai/paperclip/pull/11347) added the
new-agent login flow and the non-blocking transport advisory, and
[#11286](https://github.com/paperclipai/paperclip/pull/11286) added the
setup-token login and the guard with its `CLAUDE_LOGIN_TRUSTED_PROXIES`
allowlist.

**Subsystem affected**

server/ — the confidential transport guard for the Claude setup-token
login (`services/setup-token-session.ts`, `routes/agents.ts`, `app.ts`).

**Current behavior**

The guard allows a confidential response on direct socket TLS, on a
`local_trusted` loopback peer, or when the immediate peer is on the
dedicated `CLAUDE_LOGIN_TRUSTED_PROXIES` allowlist and forwards `https`.
Behind a managed platform's TLS-terminating edge (Railway, Render, Fly,
and similar), the app socket is plain HTTP and the edge-proxy peer
addresses are not operator-visible or stable, so the allowlist cannot
express them — IPv6 entries match by exact string only. The result: the
login panel shows "This connection is not encrypted" for a connection
that is HTTPS to the user, and the agent-scoped confidential routes
return the fixed no-secret error.

**Proposed behavior**

`CLAUDE_LOGIN_EDGE_TLS_TERMINATED=true` is an explicit, single-purpose
operator declaration that every client request reaches the server
through the platform's TLS-terminating edge. Under the declaration the
guard treats a request as confidential unless the edge itself labels the
client hop as plain `http` in `X-Forwarded-Proto`. The declaration is
never derived from the global `TRUST_PROXY` setting, which the guard
still never reads. Without the declaration, nothing changes.

**Reason and benefit**

The guard's spoofing concern does not apply to this deployment shape: a
client cannot pick its transport, because the platform admits HTTPS
only, and the header the guard consults is set by the platform edge, not
the client. A blanket warning that is always wrong teaches users to
ignore it. The declaration keeps the strict default for every deployment
that does not opt in, and it keeps the allowlist as the precise tool for
operators who do know their proxy addresses.

## What Changed

- `ConfidentialTransportConfig` gains optional `edgeTlsTerminated`
(default false), documented as the operator declaration for platform
edge TLS termination.
- `evaluateConfidentialTransport` adds the declaration as a guard case:
allowed unless the forwarded protocol's first hop is explicitly `http`
(reason `edge_labeled_plain_http` then; `operator_edge_tls_termination`
when allowed).
- `assessConfidentialStartup` reports `edge_tls_termination_declared`,
so the startup log shows why forwarded requests pass.
- `app.ts` parses `CLAUDE_LOGIN_EDGE_TLS_TERMINATED` (truthy:
`1/true/yes/on`) and passes it to the agent routes; the routes build the
guard config from it.
- The SR-7 operator-requirement comment on the setup-token routes
documents the new variable next to the allowlist.
- Tests: five new guard unit cases and a route case asserting the prompt
and code responses carry no `transportAdvisory` under the declaration.

## Verification

```sh
cd server
npx tsc --noEmit    # clean
npx vitest run src/services/setup-token-session.test.ts \
  src/routes/setup-token-route.test.ts \
  src/__tests__/openapi-routes.test.ts   # 3 files, 89 passed
```

The new "keeps failing closed when the declaration is absent" case pins
the unchanged default posture.

## Risks

The declaration is an operator statement the server cannot verify; an
operator who sets it on a deployment whose edge does not terminate TLS
re-labels plain-HTTP requests as confidential. This is the same trust
class as `CLAUDE_LOGIN_TRUSTED_PROXIES` (a wrong allowlist entry has the
same effect) and is opt-in, off by default, and scoped to the login
routes only. The guard still fails closed when the edge explicitly
labels a request `http`. No schema change, no API shape change —
`transportAdvisory` was already nullable.

## Model Used

Claude Fable 5 (`claude-fable-5`), extended thinking, with tool use and
code execution — investigation, implementation, and tests.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - Plugins can extend Paperclip with their own multi-step
workflow/graph engines that own an issue's lifecycle across many agent
handoffs
> - When such a plugin-managed issue legitimately stays `in_progress`
for a while (e.g. an anchor issue parked at a fan-out step, waiting on
child issues it spawned), Paperclip's generic recovery mechanisms have
no way to know that's intentional
> - The first commit on this branch fixed one such mechanism
(`decideSuccessfulRunHandoff`) to skip plugin-owned issues, and was
deployed to a real instance to verify the fix
> - Watching that same instance afterward, the identical symptom
(repeated "give a disposition" nags, the agent repeating "completed",
the plugin's own enforcement correctly reverting the status) recurred on
the same class of issue — meaning a second, independent code path had
the exact same gap
> - Traced it to `reconcileStrandedAssignedIssues` in `service.ts`: it
detects a stale successful-run-handoff corrective run via
`isExhaustedSuccessfulRunHandoff` and, once "exhausted" (default max
attempts is 1, so effectively immediate), escalates via
`escalateStrandedAssignedIssue` — with no check on who owns the issue's
lifecycle at all
> - Rather than duplicate the `originKind` check inline a second time
(which is exactly how it got missed the first time), extracted it into a
shared, exported, unit-tested helper (`isPluginManagedIssueLifecycle`)
that both recovery paths now call
> - The benefit is the same as the first commit, but closing the second
loop this pull request's earlier version left open: generic plugin-owned
issues (any workflow/graph-engine plugin, not just one specific plugin)
stop burning real agent-run cost in a loop that can never actually
resolve, across both recovery mechanisms that can trigger it

## Linked Issues or Issue Description

No existing public issue covers this — describing it directly, following
the bug report fields:

**What happened?**

An issue owned by a workflow-engine-style plugin (`originKind` starting
with `"plugin:"`) was correctly held at `in_progress` by the plugin
while it waited on spawned child issues to finish. The assigned agent's
heartbeat succeeded and posted a well-formed completion comment, but
`issue.status` stayed `in_progress` (the plugin's own enforcement
reverted it, correctly, since the underlying work wasn't done).

- **Path 1 (fixed in the first commit):** `decideSuccessfulRunHandoff()`
saw `status === "in_progress"` after a successful run and enqueued a
"missing disposition" corrective wake. The agent responded again, the
plugin reverted the status again, and the recovery re-triggered again.
- **Path 2 (fixed in the second commit, found after deploying and
verifying the first fix on a live instance):** separately,
`reconcileStrandedAssignedIssues` periodically re-scans `in_progress`
issues, sees the corrective run from Path 1 (or any prior
successful-run-handoff wake) as "exhausted" evidence, and escalates the
issue via `escalateStrandedAssignedIssue` regardless of plugin ownership
— producing the same nag-revert-nag cycle through a completely different
call path that the first commit's fix did not touch.

**Expected behavior**

Neither recovery mechanism should nag an agent for a disposition, or
escalate for one, on an issue whose lifecycle is already owned and
managed by a plugin — that plugin's own enforcement/recovery path is the
correct owner of "what happens next," not these generic core mechanisms.

**Steps to reproduce**

1. Install a plugin that creates/owns issues via the plugin host bridge
(`ctx.issues.create`/`ctx.issues.update`) with an `originKind` of
`"plugin:<pluginKey>"`.
2. Have the plugin's own graph/workflow logic hold an issue at
`in_progress` while some multi-step process it owns is still pending
(e.g. spawned child issues not yet complete).
3. Let an agent run a successful heartbeat on that issue that produces
visible progress (a comment) but does not change `issue.status` away
from `in_progress` in a way that sticks (the plugin's own logic reverts
any change back to `in_progress` on the next event).
4. Observe `decideSuccessfulRunHandoff` enqueue a corrective handoff
wake (Path 1), and/or `reconcileStrandedAssignedIssues` treat that
wake's run as exhausted and escalate (Path 2). Either one repeats
indefinitely on its own.

**Paperclip version or commit**

`eb2cb916be3271e3e7ab5f643ad3ca3eb7c34d01` (current `master` at time of
the first commit; rebased onto `ad961227f` for the second)

**Deployment mode**

Self-hosted server

## What Changed

- **First commit:** Added `originKind: issues.originKind` to the issue
query in `heartbeat.ts` that feeds `decideSuccessfulRunHandoff`, and a
skip condition there for `originKind` starting with `"plugin:"`.
- **Second commit:**
- Extracted the plugin-ownership check out of
`decideSuccessfulRunHandoff` into a new exported helper,
`isPluginManagedIssueLifecycle(issue)`, in `successful-run-handoff.ts`.
- Added the same check to `reconcileStrandedAssignedIssues` in
`service.ts`, immediately before it would otherwise escalate an issue
based on `isExhaustedSuccessfulRunHandoff` evidence — skipping
plugin-managed issues there too.
- Added unit tests for the new helper directly (plugin-prefixed origin
kinds → `true`; non-plugin/missing origin kinds → `false`), alongside
the existing `decideSuccessfulRunHandoff` tests (updated to import and
rely on the shared helper, behavior unchanged).

## Verification

- `npx vitest run
server/src/services/recovery/successful-run-handoff.test.ts` — 20/20
passed (18 pre-existing/from the first commit + 2 new for the extracted
helper).
- `npx vitest run
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/services/recovery/successful-run-handoff.test.ts` — full
suite still passes with the refactor.
- `pnpm --filter @paperclipai/server exec tsc --noEmit` — no new errors
introduced by either commit (confirmed against a pre-existing baseline
of unrelated `@paperclipai/plugin-sdk` module-resolution errors from the
workspace, present identically with the changes stashed out).
- Manually verified Path 1 against a real plugin-managed issue stuck in
that loop: after deploying the first commit and restarting the server,
the same agent posted the same completion comment again, and the
corrective-handoff recovery did not re-trigger.
- Path 2 was found live on the same instance after that first deploy
(the loop recurred through the second, independent mechanism) —
root-caused via direct inspection of
`heartbeat_runs`/`agent_wakeup_requests`/issue comment history, then
fixed in the second commit. Not yet re-verified live on the instance
(pending redeploy of this updated branch).

## Risks

- Low risk. Both changes are additive skip conditions — they only cause
a recovery decision to return early for a specific, narrow case
(`originKind` starting with `"plugin:"`) that previously fell through to
escalation/enqueue. No existing skip conditions are changed or reordered
in a way that affects non-plugin issues.
- Behavioral shift: plugin-managed issues that are genuinely stuck (not
just correctly mid-flight) will no longer get either of these corrective
nags. This is intentional — the plugin owning the issue is expected to
have its own recovery path — but it does mean these mechanisms are no
longer a safety net for buggy plugins that leave issues stranded. Plugin
authors should ensure their own enforcement handles stranded states.
- The refactor (extracting `isPluginManagedIssueLifecycle`) is a pure
code-motion change for the first commit's check — no behavior change
there, only a new call site added in `service.ts`.
- No migration required (query-shape and control-flow changes only, no
schema change).

## Model Used

Claude (Anthropic), model `claude-sonnet-5`, used within a coding-agent
harness (Claude Code) with tool use (file edit, test execution, git
operations, live production-instance debugging via SSH/SQL) and extended
reasoning across two sessions: the first implemented and deployed the
initial fix, the second discovered the second recovery path was still
looping on a live instance, root-caused it, and implemented/tested this
follow-up commit.

## 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
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 14:30:58 -07:00
santastabberandsantastabber 8774909361 fix(heartbeat): reuse sessions across execution handoffs (#9917)
## Thinking Path

> - Paperclip uses durable task sessions so local adapters can resume
work across sequential heartbeat runs.
> - `execution_review_requested` and `execution_changes_requested` are
issue-local execution-policy handoffs, not new task assignments.
> - The existing `agent_task_sessions` lookup, adapter session codec,
workspace resolution, and effective config freshness checks already
decide whether reuse is safe.
> - Treating those two handoff wake reasons as unconditional
fresh-session boundaries discards a valid saved task session before
adapter resume can be attempted.
> - This makes Dev → CodeReview → Dev loops repeatedly cold-start even
when task, issue, agent, adapter, workspace, and config identity are
unchanged.
> - The fix is to let normal review/change-request handoffs reach the
durable task-session path while preserving explicit fresh-session and
unsafe-boundary resets.

## Linked Issues or Issue Description

Fixes #8246.

cc @cryppadotta — this is the narrow handoff-session policy change
discussed there: normal `execution_review_requested` /
`execution_changes_requested` wakes no longer force a fresh task session
by wake reason alone, while assignment, approval, review-participant
recovery, timer wakes, explicit `forceFreshSession`, and
config/workspace/model/session freshness still keep their safety
boundaries.

## What Changed

- Removed normal `execution_review_requested` and
`execution_changes_requested` from the unconditional task-session reset
policy.
- Kept fresh-session boundaries for:
  - `issue_assigned`
  - `execution_approval_requested`
  - `execution_review_participant_recovery`
  - `heartbeat_timer`
  - explicit `forceFreshSession`
  - existing config/model/workspace/session freshness reset paths
- Updated heartbeat session-policy tests so execution handoffs are
resume-eligible by wake reason alone.
- Preserved PF-4 timer-wake behavior and its explicit reset reason.

## Verification

- `npx pnpm@9.15.4 exec vitest run
server/src/__tests__/heartbeat-workspace-session.test.ts
server/src/__tests__/heartbeat-timer-wake-session-reset-pf4.test.ts
server/src/__tests__/codex-local-execute.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
--reporter=verbose` — 220 tests passed.
- `npx pnpm@9.15.4 --filter @paperclipai/server typecheck` — passed.
- `git diff --check` — passed.
- `coderabbit review --agent -t committed --base origin/master` — 0
findings.

## Risks

- Moderate behavior change in session-boundary policy: normal
review/change-request handoffs may now reuse a saved per-task session
when the existing identity/freshness checks pass.
- Safety boundaries remain in place for new assignments, approval gates,
review-participant recovery, timer/discovery wakes, explicit
fresh-session requests, and config/model/workspace/session drift.
- If a saved session is stale or incompatible, existing freshness/resume
fallback behavior still handles reset/fresh execution.

> For core feature work, check 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.5. Tool use and local verification 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
- [x] My branch name describes the change and contains no internal
Paperclip ticket ID
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes — N/A,
server policy/test-only change
- [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: santastabber <184111696+santastabber@users.noreply.github.com>
2026-08-17 14:30:47 -07:00
ulisavoandUliana Savostenko b90da4d115 fix: keep task sessions across issue comments (#10111)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Local session adapters persist task sessions so later wakes can
resume the same conversation
> - Session reuse correctly resets when effective execution
configuration changes
> - The workspace fingerprint currently includes the issue row's
`updatedAt` timestamp
> - Adding a comment advances that timestamp even though workspace
configuration is unchanged
> - The next same-issue wake therefore discards a valid task session and
starts cold
> - This pull request excludes that volatile timestamp while retaining
actual workspace settings in the fingerprint
> - The benefit is reliable same-task continuation without weakening
configuration-freshness safety

## Linked Issues or Issue Description

No public issue exists. The inline report below follows the bug report
template.

### Pre-submission checklist

- [x] I searched existing open and closed issues and found no duplicate.
- [x] I reproduced the bug on the latest release and current `master`.
- [x] I confirmed the error originates in Paperclip core fingerprinting,
not an adapter, provider, or local configuration.

### What happened?

On Paperclip 2026.720.0 and current `master`, a comment on an issue
changes
`issues.updated_at`. Heartbeat session fingerprinting includes that
value under
`workspaceConfig.issueConfigRevisionAt`, so the next wake for the same
issue
reports a workspace-config change and refuses the saved task session.

### Expected behavior

Comment-only and other non-configuration issue updates should be
delivered as
wake deltas without invalidating the task session. Changes to the
execution
mode, issue workspace settings, project policy, environment,
instructions,
model, secrets, or other effective run configuration must still reset
it.

### Steps to reproduce

1. Complete a local session-adapter run for an issue and retain its task
session.
2. Add a comment to the issue without changing execution configuration.
3. Wake the same agent for the same issue.
4. Observe `changedCategories: ["workspaceConfig"]` and a fresh session.

### Paperclip version or commit

Reproduced on Paperclip 2026.720.0 and current `master`.

### Deployment mode

Self-hosted server.

### Installation method

npm global install; also reproduced from the current source tree.

### Agent adapter(s) involved

Codex exposed the symptom. The bug is in core fingerprint construction
and is
not adapter-specific.

### Database mode

External Postgres. The bug is not database-specific.

### Access context

Board comments trigger the timestamp change; the subsequent agent wake
exposes
the reset.

### Node.js version

Node.js 22.

### Operating system

Ubuntu 24.04.

### Relevant logs or output

The next run records `changedCategories: ["workspaceConfig"]` and starts
a
fresh session after a comment-only mutation.

### Relevant config

No unusual configuration is required.

### Additional context

The regression test exercises the fingerprint directly on current
`master`.

### Privacy checklist

- [x] I reviewed the report for PII, credentials, private paths, company
names, and instance-local identifiers.

## What Changed

- Copy and sanitize the session workspace-fingerprint input before
hashing.
- Exclude only `issueConfigRevisionAt`, which reflects general issue
mutation
  rather than workspace configuration.
- Add regression coverage proving comment timestamps preserve the
session while
  real workspace mode/settings changes still reset it.

## Verification

- `pnpm exec vitest run
server/src/__tests__/heartbeat-workspace-session.test.ts`
  - 120 tests passed.
- `pnpm --filter @paperclipai/server typecheck`
  - passed.
- `git diff --check`
  - passed.

## Risks

Low risk. A general issue update no longer rotates the adapter session
solely
because its row timestamp changed. The fingerprint still includes issue
workspace settings, issue adapter overrides, project workspace policy,
environment, instructions, runtime skills, secrets, model profile,
adapter
configuration, and agent runtime configuration, so actual
execution-config
drift continues to reset.

> 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`, context-window size not exposed, reasoning and
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
- [ ] 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: Uliana Savostenko <ulia@MacBook-Air.local>
2026-08-17 14:21:00 -07:00
7a4767017e fix(issues): base comment-wake decisions on post-insert issue state (#10068)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work; issues get commented on by both humans and agents, and the
assignee is woken to act on new comments.
> - #10050 added human-attributed issue comments for chat gateway
plugins, with the host waking the issue's assignee the same way a board
user's comment does.
> - Greptile's review on #10050 flagged that the wakeup guard in
`plugin-host-services.ts` decides whether to wake the assignee using the
issue snapshot fetched *before* the comment was inserted.
> - If another request closes, cancels, unassigns, or reassigns the
issue in the window between that fetch and the wakeup call, the guard
still acts on the stale snapshot — it can wake an agent for a
now-terminal issue, or wake the old assignee instead of the new one.
> - The PR discussion noted the HTTP add-comment route
(`routes/issues.ts`) has the identical pattern outside its
reopen/auto-approval branches, and deferred a fix to a follow-up
covering both call sites — this PR is that follow-up.
> - The fix re-fetches the issue immediately before the wake decision in
both places, so the decision reflects the latest committed state instead
of a pre-insert snapshot.

## Linked Issues or Issue Description

Refs #10050

**Problem or motivation**

Both the plugin-comment wakeup guard (`plugin-host-services.ts`) and the
HTTP add-comment route's wakeup guard (`routes/issues.ts`, outside its
reopen/auto-approval branches) decide whether to wake the issue's
assignee using the issue state fetched before the comment was inserted.
A concurrent close/unassign/reassign landing in that window is invisible
to the guard, so it can enqueue a wakeup for a stale assignee or an
issue that is no longer open.

**Proposed solution**

Re-fetch the issue immediately before the wake decision in both call
sites, and base the assignee/status checks on that fresh read instead of
the earlier snapshot. This shrinks the race window to essentially
nothing (the fetch happens right before the fire-and-forget wakeup
call), and any residual window is already covered by the
heartbeat/checkout machinery re-validating issue status and assignee
ownership when a woken run actually starts.

**Alternatives considered**

Wrap the whole comment-insert + wake-decision sequence in a single
serializable transaction with row locking (rejected for this change —
much larger blast radius across two already-complex handlers for a
wakeup that is explicitly best-effort; the woken run's own re-validation
already makes a stale wake degrade to a no-op rather than incorrect
work). Leaving the plugin path fixed but not the HTTP route (rejected —
that was the exact gap the original PR discussion flagged as needing a
follow-up covering both call sites).

**Roadmap alignment**

Bug fix / hardening follow-up to #10050; no change to planned core
roadmap items.

## What Changed

- `server/src/services/plugin-host-services.ts`:
`issues.createComment`'s assignee-wakeup guard now re-fetches the issue
after the comment is inserted and bases the assignee/status checks on
that fresh read, instead of the snapshot fetched before the insert.
- `server/src/routes/issues.ts`: the `POST /issues/:id/comments` route's
wakeup guard (outside the reopen/auto-approval branches, which already
use post-mutation state) now does the same re-fetch before deciding
whether — and whom — to wake.
- Adds regression coverage for both:
- `server/src/__tests__/plugin-orchestration-apis.test.ts`: a new
embedded-Postgres test holds a row lock on the issue to
deterministically force the race (comment-insert's internal update
blocks until a concurrent transaction commits a cancellation), then
asserts no wakeup is enqueued.
- `server/src/__tests__/issue-comment-reopen-routes.test.ts`: two new
mocked-service tests assert the route skips the wakeup when the fresh
re-fetch shows the issue cancelled, and wakes the freshly reassigned
agent (not the pre-insert snapshot's assignee) when the fresh re-fetch
shows a different assignee.

## Verification

- `pnpm --filter @paperclipai/server typecheck` — clean.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/plugin-orchestration-apis.test.ts` — 13/13 (1 new).
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/issue-comment-reopen-routes.test.ts` — 74/74 (2 new).
- `pnpm --filter @paperclipai/plugin-sdk exec vitest run
tests/host-client-factory.test.ts` — 14/14.
- Broader sweep of 38 `routes/issues.ts`-adjacent test files (447 tests)
— all passing, confirming the added re-fetch doesn't change behavior for
any existing
reopen/auto-approval/interrupt/scheduled-retry/dependency-wake scenario.

## Risks

Low risk. Both changes are additive guards around an existing
best-effort, fire-and-forget wakeup (failures already logged, not
thrown) — no change to the comment-write path itself, response shape, or
status codes. The HTTP route's fix only touches the plain (non-reopen,
non-auto-approval) wake-decision path; the reopen and auto-approval
branches already used post-mutation state for the reasons documented
inline and are unchanged. Adds one extra `SELECT` per comment on each
call site, negligible relative to the existing query volume in both
handlers.

## Model Used

Claude Sonnet 5 (`claude-sonnet-5`), extended thinking, tool use
enabled, 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 searched the GitHub PR list (open and recently closed) for
similar PRs; found no duplicate — this is a direct follow-up to the
review discussion on #10050
- [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
ticket id
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: anicca <annica@Michaels-Mac-Studio.local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 14:18:15 -07:00
SamandSami Rusani 14fd8aee36 fix(server): flag truncated issue descriptions (#4771)
## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies.
> - The issue list API is one of the surfaces API consumers use to
synchronize issue metadata.
> - The list endpoint intentionally returns a bounded `description`
preview so large descriptions do not bloat list responses.
> - Before this change, that preview looked like a complete field value
because the response did not say whether it had been shortened.
> - That made round-trip clients vulnerable to accidentally PATCHing a
preview back over the full description.
> - This pull request keeps the existing preview behavior but adds an
explicit `descriptionTruncated` flag.
> - The benefit is backwards-compatible visibility into truncated issue
descriptions, so clients can avoid data-loss workflows.

## Linked Issues or Issue Description

Fixes #4758.

Related PR: #4792 also targets #4758, but it includes unrelated logger
changes and currently has separate review/security concerns. This PR
keeps the fix scoped to the issue-list description truncation API
behavior.

## What Changed

- Added `descriptionTruncated` to the issue list projection when
`description` exceeds the existing 1200-character preview limit.
- Exposed `descriptionTruncated?: boolean` on the shared `Issue` type.
- Added service tests for truncated descriptions, exact-limit
descriptions, null descriptions, and multibyte-safe preview truncation.

## Verification

June 18, 2026 refresh after rebasing onto current `origin/master`:

- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm typecheck`
- `git diff --check origin/master...HEAD`
- GitHub PR checks are green on head `12e828e6`.

Earlier pre-review verification also included `pnpm test`.

## Risks

- Low risk. This is an additive API response field; existing clients can
ignore it.
- The list endpoint still returns the same bounded `description`
preview. Clients that need full text should continue fetching the issue
detail, but can now detect when that is necessary.
- No database migration or UI behavior change.

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

## Model Used

- OpenAI Codex based on GPT-5, via Codex desktop on April 29, June 15,
and June 18, 2026. Used tool-assisted repository inspection, code
editing, local test execution, GitHub CLI workflows, and PR review
follow-up. Exact context window size is not surfaced by the tool.

## 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 change)
- [x] I have updated relevant documentation to reflect my changes (N/A:
additive API field covered by tests)
- [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: Sami Rusani <sr@samirusani>
2026-08-17 14:02:01 -07:00
Nicky LeachandPaperclip c1c46f1e4e feat: Claude login on the new-agent page before agent creation (#11347)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Claude local adapter supports subscription login through a
sandbox
> - The new-agent page must show login before the user creates an agent
> - Test results must not expose raw sandbox diagnostics or secret
values
> - This pull request adds the login UI to both Test lanes and closes
the diagnostic boundary
> - The branch also adds durable cleanup recovery for failed sandbox
teardown
> - Reusable sandboxes must retain both their recorded teardown
configuration and a valid lifecycle path until destruction succeeds
> - The benefit is a usable login flow with fixed public checks,
redacted server logs, and recoverable sandbox cleanup

## Linked Issues or Issue Description

Related public work:
[#9488](https://github.com/paperclipai/paperclip/pull/9488) adds
first-class recognition for `CLAUDE_CODE_OAUTH_TOKEN` in headless and
remote runs. Related public issue:
[#2681](https://github.com/paperclipai/paperclip/issues/2681) requests
Claude Code subscription support. This pull request adds the login
transport and new-agent UI flow that those changes do not provide.

**Subsystem affected:** Claude local adapter, server login probes,
sandbox provider setup, cleanup recovery, and the new-agent UI.

**Problem or motivation:** The Test lanes did not show the sandbox login
panel in all supported cases. Test results also exposed raw probe
diagnostics, and JSON escapes could end secret redaction early.

**Proposed solution:** Surface the login capability through the bundled
provider manifest. Prepare the same probe runtime in the ACP lane. Send
diagnostics only to redacted server logs. Keep Test checks on fixed
public messages. Normalize login URL hints to allowlisted HTTPS Claude
and Anthropic hosts. Consume JSON escapes during redaction. Preserve
failed sandbox cleanup state across retries and restarts, and prevent
deletion from severing the lifecycle context of a live reusable sandbox.

**Alternatives considered:** Keep raw diagnostics in Test checks or
trust login URL text from the sandbox. Both choices increase information
exposure. Keep separate probe behavior in the ACP lane. That choice
would leave the two Test lanes inconsistent.

## What Changed

- Surface the sandbox login panel on both Test lanes.
- Reconcile the bundled Daytona plugin manifest so
`supportsSetupTokenLogin` reaches the UI capability gate.
- Prepare the ACP Test lane with the same probe runtime as the CLI Test
lane.
- Add the `claude_acp_login_probe_unavailable` warning when the ACP
probe cannot run.
- Send raw sandbox diagnostics only to redacted server logs.
- Keep Test checks on fixed public messages in the ACP, managed-config,
and CLI paths.
- Normalize login URL hints to allowlisted HTTPS Claude and Anthropic
hosts.
- Redact JSON and escaped-JSON secret values, including escaped quotes
and backslashes.
- Preserve orphan cleanup records across provider failures, restarts,
and unavailable plugins.
- Atomically block environment deletion while a live reusable sandbox
lease still depends on it.
- Verify pending cleanup destroys plugin sandboxes with the provider
configuration recorded on the lease, even after the current environment
configuration changes.

## Verification

- Head under review: `506b7fa2d83c36bfa5fd722ee9d95b0c7431c241`.
- Focused environment route/service/runtime coverage passes: 196 tests
across 3 files.
- `pnpm -r typecheck` passes.
- `pnpm build` passes.
- The full Vitest run completed with 4,754 passing and 28 failing tests.
All 23 source-test failures reproduce unchanged on parent head
`58cfe61a33191ce03d965d65085d26064b4888ba`; the other 5 are duplicate
executions from stale `server/dist` output. The failures are unrelated
macOS path/listener and scheduler-fixture failures, so there is no new
bad commit for bisect to localize.
- All required CI checks pass for the current head, including build,
typecheck/release registry, all server and workspace shards, serialized
server suites, canary, and e2e.
- A fresh Greptile review for `506b7fa2d83c36bfa5fd722ee9d95b0c7431c241`
reports 5/5, “safe to merge,” with no blocking failure remaining.

## Risks

- A probe or redaction change could hide useful server diagnostics.
- An allowlist change could reject a valid Claude login URL.
- Cleanup recovery changes could affect provider teardown ordering.
- An environment with a live reusable sandbox can no longer be deleted
until the owning issue or execution workspace completes teardown.
- The implementation keeps public Test messages fixed and sends detail
to redacted server logs.

## Model Used

OpenAI GPT-5 via Codex — exact model ID: GPT-5; tool use and code
execution enabled; extended reasoning enabled. The implementation author
used AI-assisted development.

## 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 documented the result
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation or confirmed no separate
documentation change is needed
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-17 13:42:51 -07:00
Nicky LeachandPaperclip 3061ce6901 feat(sandbox): stream session output by capability, drop three operator flags (#11557)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandboxed agents use provider capabilities to select safe execution
paths
> - Session output still depends on three operator flags that duplicate
capability data
> - Duplicate flags can drift from the verified sandbox capability
snapshot
> - This pull request makes the capability snapshot the only streaming
decision and removes the obsolete flags
> - The benefit is default streaming with a poll fallback when a
capability or stream fails

## Linked Issues or Issue Description

**What existing behavior does this improve?**

ACP sandbox session-output streaming and sandbox execution
configuration.

**Subsystem affected**

Cross-cutting (multiple of the above): server/, packages/shared/,
packages/adapter-utils/, and packages/plugins/.

**Current behavior**

Session-output streaming requires operator flags in the server and
Daytona plugin configuration. Saved configurations can retain a removed
key.

**Proposed behavior**

The verified capability snapshot selects streaming. The Daytona plugin
uses persistent sessions by default, keeps bypass commands one-shot, and
falls back from the log stream to polling. Removed configuration keys
become inert.

**Reason and benefit**

One capability source prevents configuration drift. The fallback keeps
output available when capability resolution or log streaming fails.

**Breaking changes**

The three operator flags no longer control session-output streaming.
Existing saved keys load but have no effect.

## What Changed

- Remove `useSessions` and `useLogStream` from the Daytona plugin
configuration and manifest.
- Remove `streamAgentSessionOutput` from server configuration, shared
types, and execution-target plumbing.
- Select streaming from `persistentProcessSessions` and
`independentControlCommands`.
- Keep poll fallback on capability resolution failure and stream
failure.
- Strip removed keys from strict fake-sandbox and catchall plugin
configuration.
- Update the sandbox capability documentation and focused tests.

## Verification

- `tsc --noEmit` passed in `packages/shared`, `packages/adapter-utils`,
`server`, and the Daytona plugin.
- Daytona `plugin.test.ts` passed 139 tests.
- Server capability, configuration, route, and runtime suites passed 160
tests.
- `packages/adapter-utils` `execution-target-sandbox.test.ts` passed 44
tests.
- The capability matrix covers stream, poll, and resolution-failure
paths.
- Removed-key tests cover strict fake-sandbox and catchall plugin
schemas.

## Risks

- A capability snapshot that lacks either required session capability
uses polling.
- A log stream failure uses polling and can increase request count.
- Existing removed configuration keys no longer change behavior.
- The isolated-worktree Daytona Vitest run has a pre-existing missing
`packages/adapters/droid-local` reference. CI and standard checkouts use
the committed configuration.

## Model Used

OpenAI Codex, GPT-5, tool use and code review assistance. The exact
runtime context window is managed by the Codex platform.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-17 13:25:30 -07:00
Nicky LeachandPaperclip e71ce9a9d3 feat: sandbox provider capability contract with fail-closed effective resolution (#11463)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip runs work through adapters and sandbox providers
> - Providers need a clear contract so the server can use only verified
capabilities
> - A declared capability must not grant a method that the live worker
did not verify
> - This pull request adds manifest declarations and fail-closed
effective capability resolution
> - The benefit is safe provider reuse across execution targets and run
lifecycles

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting (multiple of the above)

**Problem or motivation**

Sandbox providers expose different runtime methods. The server needs one
safe capability contract that accounts for provider declarations, worker
verification, and narrowing configuration.

**Proposed solution**

Add strict manifest validation for five sandbox capabilities. Resolve
effective capabilities as the subset of verified, declared, and narrowed
values. Store the result as a frozen execution-target snapshot.

**Alternatives considered**

Trusting the manifest alone could grant methods that the worker does not
support. Trusting only a fixed built-in list would reject valid
third-party providers. The intersection rule keeps the verified runtime
ceiling and supports both provider types.

**Roadmap alignment**

This change supports the ACP run lifecycle track and the sandbox
provider contract work in the current roadmap.

**Additional context**

The legacy `supportsReusableLeases` field remains supported. The nested
capability validator rejects unknown keys. Missing or unavailable
verification resolves all capabilities to `false`.

## What Changed

- Add strict `sandboxCapabilities` manifest validation with legacy
reusable-lease compatibility.
- Carry declarations through the ready-driver projection.
- Add fail-closed effective resolution from verified, declared, and
narrowed capabilities.
- Add narrowing for provider configuration, Kubernetes Job leases, and
Daytona sessions.
- Add a frozen read-only capability snapshot to execution targets.
- Add focused tests and keep existing characterization baselines
covered.
- Add and update sandbox provider capability documentation.

## Verification

- `npx vitest run packages/shared/src/validators/plugin.test.ts`
- `npx vitest run
server/src/__tests__/plugin-environment-driver-sandbox-capabilities.test.ts`
- `npx vitest run
server/src/__tests__/sandbox-capability-contract.test.ts`
- `npx vitest run
server/src/__tests__/environment-execution-target-capabilities.test.ts`
- `npx vitest run
packages/adapter-utils/src/acpx-engine/startup-characterization.test.ts
packages/adapter-utils/src/acpx-engine/turn-characterization.test.ts
packages/adapter-utils/src/acpx-engine/settlement-characterization.test.ts
packages/adapter-utils/src/acpx-engine/composed-run-characterization.test.ts`
- Package typechecks for shared, server, and adapter-utils pass.
- Stage-2 security review suites pass with 28 tests.

## Risks

The resolver fails closed when verification is absent or unavailable.
Providers that rely on undeclared capabilities may see narrower behavior
until they expose verified worker methods. The change does not alter the
existing native-sync guard.

## Model Used

OpenAI Codex, GPT-5, exact runtime model ID `gpt-5`, tool use and code
execution. The implementation author used this model to assist with the
change.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-17 10:55:40 -07:00
Dotta 1a17cbf232 fix(runtime-exposure): mediate leased app/HMR port pairs centrally (#11526)
<!-- Simplified Technical English (ASD-STE100). -->

> **Stacked pull request.** This targets #11525, which targets #11524.
Merge those first. Review only the last commit, `fix(runtime-exposure):
mediate leased app/HMR port pairs centrally`.

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip starts managed runtime services for execution workspaces,
and #11524 and #11525 make those services reachable over Tailscale HTTPS
on a loopback port pair
> - An HTTPS lane is only safe if one execution workspace holds its port
pair exclusively for the whole life of the lane
> - A managed start reused a pair that a stopped but still leased
workspace owned. Paperclip reported that workspace stopped and its
exposure removed, while the host listeners and the Serve mappings for
those ports were live and belonged to an unrelated workspace
> - The cause is that ownership was decided in more than one place, and
no single place saw persisted reservations, live listeners, and Serve
mappings together
> - This pull request adds one mediator that owns the decision, and
makes every mismatch fail closed while naming the conflicting workspace
> - The benefit is that a later start cannot collide with, adopt, or
interfere with another issue's service, and cannot produce security
evidence attributed to the wrong workspace

## Linked Issues or Issue Description

No public GitHub issue exists. The change follows the bug report
template.

**What happened**

A managed HTTPS start reused the loopback port pair of a stopped but
still exclusively leased execution workspace. The ports were then held
by an unrelated workspace. Paperclip continued to report the first
workspace's runtime as stopped and its exposure as removed, while the
host listeners and the `tailscale serve` mappings for those exact ports
were live and owned by the other workspace.

**Expected behavior**

An active execution-workspace lease reserves its app and HMR pair until
the lease is explicitly released or torn down. A start that finds the
pair held by a different workspace fails closed and names the conflict.
Paperclip never adopts a process or a Serve mapping across
execution-workspace ids.

**Root cause**

Three separate readers each had an incomplete view:

- `deprovisionExposure` replaces the exposure status with a fresh
`removed` status whose `listeners` array is empty. A later reader asking
"which ports did this row own?" gets no answer, so a stopped row's pair
looked free even while the row was leased.
- Startup reconciliation adopted a persisted service by `row.port`
alone, then terminated the local service when its health check failed.
Under the `project_primary` strategy, where workspaces share a working
directory, the containment check cannot separate two workspaces, so the
sweep could adopt and then kill an unrelated workspace's live service.
- Allocation checked live port availability but never checked which
pairs active leases still reserve.

**Impact**

Two workspaces can collide on one lane. A start can adopt or interfere
with another issue's service, and evidence about an exposure can be
attributed to the wrong workspace.

## What Changed

- Add `server/src/services/runtime-exposure/port-reservation.ts`, one
mediator that decides allocation and ownership from persisted
reservations plus live listener and Serve ownership together.
- Reserve a pair for as long as its execution workspace holds an active
lease, until the lease is explicitly released or torn down.
- Re-derive a row's pair from the `port` column and `deriveViteHmrPort`
instead of the status `listeners` array, so a `removed` status no longer
hides which ports a leased row still reserves.
- Refuse to adopt a process or a Serve mapping across
execution-workspace ids. A mismatch fails closed and names the
conflicting workspace and issue.
- Treat an unattributable holder as a conflict. A Serve mapping that is
present but cannot be attributed means the host has something there that
could not be named, so it fails closed instead of falling through to
"allowed".
- Make reconciliation surface a stopped or removed row whose reserved
ports are live or mapped by another workspace, instead of reporting
success.
- Leave manual and unknown Serve mappings alone on release and teardown.

## Verification

- `npx vitest run --root server src/services/runtime-exposure/
src/__tests__/workspace-runtime-exposure-reservation.test.ts
src/services/workspace-runtime-exposure-backfill.test.ts` — 8 files,
**112 tests pass**.
- `npx tsc --noEmit -p server/tsconfig.json` — **0 errors** with
`@paperclipai/plugin-sdk` built.
- `pnpm --filter @paperclipai/db typecheck` — migration numbering and
safety checks pass.
- `pnpm --filter @paperclipai/tailscale-https-broker test` — 87 tests
pass.

The five required regressions are covered by
`workspace-runtime-exposure-reservation.test.ts` and
`port-reservation.test.ts`:

1. Reuse of a stopped-but-leased pair is denied.
2. Cross-execution-workspace process adoption is denied.
3. A Serve mapping ownership mismatch is visible and fails closed.
4. Concurrent allocators return unique pairs.
5. Release and teardown make the pair reusable without harming manual or
unknown mappings.

Note for reviewers:
`server/src/services/workspace-runtime-exposure.test.ts` fails on a
development host that already runs an HTTPS canary holding ports 42000,
42001, 52000, and 52001, because that fixture stubs port availability
and then allocates into the occupied range. It is unaffected by this
change and is expected to pass in CI, where no such listener exists.
Please read the CI result rather than a local run on an exposing host.

## Risks

- The mediator is now the single decision point for allocation and
adoption, so a defect in it affects every managed start. This is
deliberate: the incident happened because the decision was spread across
three readers, and concentrating it is the fix.
- Behavior becomes stricter. A start that previously reused a pair now
fails closed with a named conflict. This is the intended change, and it
can surface pre-existing collisions that used to pass silently.
- The remediation path does not stop an unrelated service that already
holds a pair. It reports the conflict instead, so it cannot disturb
another issue's running lane.
- No migration runs in this pull request.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR.

## Model Used

Claude Opus 5 (`claude-opus-5`), 1M context window, extended thinking,
with tool use and code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] 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
2026-08-17 06:47:55 -04:00
Dotta 4c349fe6b7 feat(runtime): managed Tailscale HTTPS lifecycle, durable runtime leases, and bounded control recovery (#11525)
<!-- Simplified Technical English (ASD-STE100). -->

> **Stacked pull request.** This targets #11524. Merge #11524 first.
Review only the second commit, `feat(runtime): managed Tailscale HTTPS
lifecycle...`.

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip starts and supervises managed runtime services, so an
agent's branch can be previewed while the agent works
> - The previous pull request added the host broker, the shared
contract, and the database columns, but no code used them
> - A managed runtime can only be exposed over HTTPS if it holds a
stable loopback port pair for the whole life of the service. The current
control path cannot promise this: two controls can race the same
execution workspace, a stranded control can stay `running` forever, and
a start can adopt a port it does not own
> - This pull request adds the HTTPS lifecycle and the control-path
hardening that the lifecycle depends on
> - The benefit is that a managed preview becomes reachable from another
device, and a managed control now always reaches a terminal state

## Linked Issues or Issue Description

No public GitHub issue exists. The change follows the feature request
template.

**Subsystem affected**

Managed workspace runtime services, workspace operations, the execution
workspace routes, and the workspace runtime UI.

**Problem or motivation**

A managed runtime service is reachable only on loopback, so a preview
cannot be opened from a phone or a second computer. Exposing it safely
needs an exclusively held port pair. Three existing gaps block that.
Overlapping controls can race the same workspace. A control whose owner
dies stays `running` and blocks the lane forever. Port allocation does
not confirm that the process holding a port is the process Paperclip
spawned.

**Proposed solution**

Add the exposure lifecycle on top of the broker from #11524: reserve
before spawn, expose after readiness, validate the public URL, and
remove on stop. In the same change, make managed controls mutually
exclusive per workspace, give each control a durable issue-owned lease
and a terminal state, and verify port ownership before use.

**Alternatives considered**

- Add HTTPS exposure without the control hardening. This was rejected
because a raced or stranded control makes exposure point at the wrong
process.
- Guard the lane with an in-memory lock only. This was rejected because
the lock does not survive a server restart, so the lane can be lost or
double-claimed.
- Trust the requested bind address. This was rejected because a checkout
that predates managed HTTPS overwrites `PAPERCLIP_BIND` from its own
`--bind` argument, and then binds the wildcard address.

**Roadmap alignment**

This completes the managed workspace runtime capability that already
exists. It adds no new product surface beyond the HTTPS link.

**Additional context**

This is the second of three pull requests. The third adds central
mediation of leased port pairs.

## What Changed

Exposure lifecycle:

- Add the server-side broker client and the exposure lifecycle manager.
The manager reserves the mapping before spawn, exposes after backend
readiness, validates the public URL, and removes the mapping on stop.
- Default managed worktree runtimes to `tailscale_https`, read exposure
intent from legacy `expose` blocks, and backfill runtimes that are still
HTTP-only.
- Verify listener ownership for the app port and its Vite HMR companion
before the broker is asked to expose anything. An unrelated listener on
either port fails the start closed.
- Force the loopback bind through argv instead of environment hints.
Leave a non-Paperclip service's `--bind` argument alone.
- Probe loopback for readiness instead of the public URL, and give Vite
HMR its own loopback-bound server in middleware mode.
- Preserve operator-declared Serve mappings across the managed
lifecycle, so cleanup never removes a mapping that Paperclip did not
create.
- Name which listener predicate denied an expose, so an operator can act
on the message.

Control-path hardening:

- Make `start`, `stop`, `restart`, and job `run` mutually exclusive per
execution workspace. An overlap gets `409
workspace_runtime_control_in_progress`, and authorization is still
checked first.
- Take a durable exclusivity lease on the execution workspace, owned by
the controlling issue. A different issue gets `409
workspace_runtime_lease_conflict` before any operation is recorded.
Board and operator actions bypass the lease.
- Give every control a terminal state. Each control stamps its owning
process and pid, heartbeats while it runs, and has a wall-clock ceiling.
Recovery of a stranded control uses a compare-and-swap on `updated_at`,
so a live owner is never stolen.
- Bound readiness probes, verify allocated port ownership on POSIX and
Windows, harden sibling port allocation, and reconcile desired runtimes
on server startup.
- Surface exposure state and bounded runtime errors in the workspace
runtime UI.
- Record the new behavior in `doc/DEVELOPING.md`.

## Verification

Focused checks, all run on this branch:

- `npx tsc --noEmit -p server/tsconfig.json` — 139 errors, exactly the
count on `master`. All 139 come from the unbuilt
`@paperclipai/plugin-sdk` package.
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- Server suites, 177 tests pass across 9 files:
`workspace-runtime.test.ts`, `workspace-runtime-leases.test.ts`,
`workspace-runtime-control-recovery.test.ts`,
`execution-workspace-runtime-control-conflict.test.ts`,
`execution-workspace-runtime-lease-route.test.ts`,
`workspace-operations-reconciliation.test.ts`,
`workspace-runtime-start-terminality.test.ts`, `app-hmr-port.test.ts`,
and `workspace-runtime-ready-comment.test.ts`.
- Exposure unit suites, 77 tests pass: `src/services/runtime-exposure/`
and `workspace-runtime-exposure-backfill.test.ts`.
- UI: `WorkspaceRuntimeControls.test.tsx` and
`WorkspaceServiceControlBar.test.tsx` — 34 tests pass.

**One suite is red on the development host and is expected to be green
in CI.** `server/src/services/workspace-runtime-exposure.test.ts` has 10
failures on the machine used to write this branch. The cause is host
contamination, not the code. That machine already runs an HTTPS canary
that holds ports 42000, 42001, 52000, and 52001 on a tailnet address.
The suite allocates from the same range, so the new listener-ownership
check correctly reports:

```
listener_ownership_mismatch — port 42000 is bound to 100.123.243.20, 127.0.0.1,
fd7a:115c:a1e0:0:0:0:dd3a:f314 ... instead of loopback only
```

A CI runner has no listener on those ports, so the check sees loopback
only and the suite passes. Please confirm this from the CI result on
this pull request rather than from a local run on a host that already
exposes a managed runtime. This is a real weakness of the current test
fixture, and the third pull request in the series removes it by
allocating the pair through a central mediator instead of a stubbed
availability check.

`workspace-runtime-https-live-exercise.test.ts` needs a live `tailscale`
host and was not run locally.

## Risks

- This is the behavior-bearing pull request of the three, so it carries
the most risk.
- Two new `409` responses appear on managed control routes. A caller
that assumed a control always starts must handle a conflict. Board and
operator actions are deliberately exempt, so an agent lease cannot lock
an operator out.
- Managed worktree runtimes now default to `tailscale_https`. If the
host has no working broker, the start fails closed and reports the
exposure failure instead of silently serving plain HTTP. This is
intended, and it is the reason the failure message names the denying
predicate.
- Startup reconciliation touches persisted runtime rows. It is scoped to
desired state and does not resurrect a service that never came up.
- The lease has a 30-minute time to live and explicit release paths, so
a crashed owner cannot hold a lane forever.
- No migration runs in this pull request. The tables and columns land in
#11524.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR.

## Model Used

Claude Opus 5 (`claude-opus-5`), 1M context window, extended thinking,
with tool use and code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass, with the one
host-contaminated suite explained above
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-17 06:23:33 -04:00
Dotta f4802b1bbc feat(runtime-exposure): least-privilege Tailscale HTTPS broker, shared contract, and persisted exposure state (#11524)
<!-- Simplified Technical English (ASD-STE100). -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip starts and supervises managed runtime services for a
project's execution workspaces, so an agent's branch can be previewed
while it works
> - Those services only listen on plain loopback HTTP. A person on
another device, or on a phone, cannot open the preview
> - A Tailscale HTTPS mapping solves this, but `tailscale serve` needs
host privileges that the Paperclip server process must not hold
> - This pull request adds the foundation only: a separate
least-privilege host broker, the shared exposure contract, and the
database columns that hold exposure state
> - Nothing calls the broker yet, so there is no behavior change. The
benefit is that the privileged surface is small, reviewable, and
isolated before any lifecycle code depends on it

## Linked Issues or Issue Description

No public GitHub issue exists. The change follows the feature request
template.

**Subsystem affected**

Managed workspace runtime services, the shared type and validator
package, and the database schema.

**Problem or motivation**

A managed runtime service binds to loopback only. There is no supported
way to reach that preview from another device. Adding HTTPS directly to
the server would mean the server process runs `tailscale serve`, which
needs privileges far wider than the task requires. A compromised or
buggy server could then map any port to the tailnet.

**Proposed solution**

Split the privileged work into a separate broker process with a narrow
protocol, and define one shared contract that the server, the UI, the
runtime, and the broker all read. Land this foundation first, with no
caller, so the privileged code can be reviewed on its own.

**Alternatives considered**

- Call `tailscale serve` from the server process. This was rejected
because it gives the server unrestricted mapping authority.
- Use `sudo` for single `tailscale` commands. This was rejected because
the argument list is the only guard, and it is easy to widen by
accident.
- Use a generic reverse proxy. This was rejected because it does not
remove the need for a privileged Tailscale mapping step.

**Roadmap alignment**

This supports the existing managed workspace runtime capability. It adds
no new product surface on its own.

**Additional context**

The broker is the security boundary of the feature, so it is
deliberately the first slice. Three later pull requests build on it: the
server exposure lifecycle, the runtime lease and recovery integration,
and the leased-port mediator.

## What Changed

- Add the `@paperclipai/tailscale-https-broker` workspace package. The
broker listens on a unix socket, authorizes each peer with
`SO_PEERCRED`, and answers a small request protocol.
- Restrict what the broker will map. It accepts only same-number
HTTPS-to-loopback pairs inside the Paperclip port range, refuses
protected ports, and confirms that the loopback port belongs to a
Paperclip-owned listener.
- Parse every request with a strict JSON reader that rejects duplicate
keys, prototype keys, and unknown fields.
- Write an append-only audit record for each broker decision.
- Add the shared exposure contract in `@paperclipai/shared`: the
`RuntimeExposureConfig`, `RuntimeExposureState`, and
`RuntimeExposureStatus` types, their zod validators, the app and HMR
port rules, and the loopback-bind helpers.
- Persist exposure state on `workspace_runtime_services` with the new
`exposure` column, plus the server-private `exposure_handle` and
`backend_url` columns that are never serialized to API clients.
- Add the `execution_workspace_runtime_leases` table that the later
lease slice uses.
- Extend the runtime read-model test fixture for the three new columns.

## Verification

Focused checks, all run on this branch:

- `pnpm --filter @paperclipai/tailscale-https-broker test` — 12 files,
82 tests pass. This covers peer credentials, port policy, protected
ports, the serve config writer, the strict JSON reader, argv parsing,
and the socket server.
- `pnpm --filter @paperclipai/tailscale-https-broker typecheck` — clean.
- `npx vitest run --root packages/shared src/runtime-exposure
src/validators/runtime-exposure.test.ts` — 3 files, 40 tests pass.
- `pnpm --filter @paperclipai/db typecheck` — runs `check:migrations`
first. Migration numbering and migration safety both pass.
- `pnpm --filter @paperclipai/shared typecheck` — clean.
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `npx vitest run --root server
src/services/workspace-runtime-read-model.test.ts` — 3 tests pass.
- `npx tsc --noEmit -p server/tsconfig.json` — 139 errors, which is
exactly the count on `master` before this branch. All 139 come from the
unbuilt `@paperclipai/plugin-sdk` package.

To confirm the exposure state is inert, start a managed runtime service
as usual. The new columns stay null and the service behaves as it does
today.

## Risks

- Migration risk is low. Both migrations only add a table and three
nullable columns. No column is backfilled and no existing column
changes. The migration safety check passes.
- Behavior risk is low. No code path calls the broker in this pull
request, and the shared exposure fields are optional.
- The broker is privileged, so it is the real risk surface. It is
mitigated by peer-credential authorization, a fixed port range, a
protected-port deny list, same-number pair enforcement,
listener-ownership checks, strict JSON parsing, and an audit trail.
Reviewers should read
`packages/tailscale-https-broker/src/authorization.ts` and
`src/port-policy.ts` closely.
- The broker requires a `tailscale` version floor, which its README
records. An older host CLI makes the broker refuse to start rather than
map incorrectly.
- `pnpm-lock.yaml` changes because a new workspace package is added. The
diff is the new importer block, plus one duplicate `tinyexec` entry that
pnpm removed.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR.

## Model Used

Claude Opus 5 (`claude-opus-5`), 1M context window, extended thinking,
with tool use and code execution.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Issue interactions give agents and people a structured decision
record.
> - Resolver routes used different authorization rules.
> - Some routes blocked valid agents, including task watchdogs with
normal issue access.
> - The API did not show who could resolve a pending interaction.
> - This pull request gives every interaction kind one resolver policy
evaluator.
> - The benefit is a clear decision path with consistent governance and
company isolation.

## Linked Issues or Issue Description

Fixes: #8087

Refs: #7403

Related PR: #11082 proposes board-only confirmation rules. This change
keeps human-only review as an explicit policy.

**What happened?**

Agents could create issue interactions. Some resolver routes still
required board access.

This left valid agent confirmations pending. Task watchdogs could see
the same problem without board identity.

**Expected behavior**

Every interaction kind must use one resolver policy contract.

The contract must support `anyone`, `not_creator`, and `human_only`. It
must also apply all normal governance controls.

**Steps to reproduce**

1. Create a `request_confirmation` interaction as an agent.
2. Resolve it with another authorized agent.
3. Observe the board-only denial.

**Paperclip version or commit**

The problem exists on `master` before this change.

**Deployment mode**

Local development with `pnpm dev`.

## What Changed

- Add canonical policies for `anyone`, `not_creator`, and `human_only`.
- Use one server evaluator for every interaction kind.
- Apply named addressees, company limits, review rules, and task
watchdog scope.
- Charge cross-issue resolutions to the existing per-run action limit.
- Return the effective resolver audience in attention and interaction
data.
- Show the audience, governance choices, and denial reasons in the board
UI.
- Add telemetry, API documents, product documents, and regression
fixtures.
- Add migration provenance for safe legacy behavior.
- Make migration `0218` safe for complete replays and partial prior
runs.

## Product Rules

- An interaction records a response. It does not grant authority for the
next action.
- `anyone` lets any authorized issue participant respond.
- `not_creator` requires a responder other than the interaction creator.
- `human_only` requires an authorized person.
- A named addressee, company policy, or governed action can narrow the
audience.
- These controls cannot widen the audience.
- A task watchdog uses the same rules as an ordinary agent.
- A task watchdog does not receive board authority.
- An agent resolution on another issue uses the shared cross-issue
action limit.
- Legacy pending interactions keep their earlier restrictions.
- The UI shows the effective audience and a permanent denial reason.

## Verification

- `pnpm --filter @paperclipai/db check:migrations`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run
packages/db/src/issue-thread-interaction-resolver-policy-migration.test.ts`
- The focused PostgreSQL test applies migration `0218` twice.
- The test also completes a partial prior run and preserves existing
provenance.
- The latest GitHub head has 29 successful checks.
- The opt-in Storybook visual check skipped as expected.
- Greptile reports 5/5 with no open comments.

## Risks

- New interaction writes use `anyone` by default.
- Callers must select `not_creator` or `human_only` when they need
stricter review.
- Legacy pending interactions keep the old creator and human
restrictions.
- Migration `0218` fills only missing provenance fields during recovery.
- Cross-issue resolutions can reach the existing action limit.
- The shared evaluator affects every interaction kind.
- Route, service, database, shared contract, and UI tests cover these
rules.

> This work matches the Agent Reviews and Approvals direction in
`ROADMAP.md`. It does not duplicate a planned item.

## Model Used

OpenAI Codex, GPT-5. The runtime does not expose the exact deployment ID
or context window.

The agent used reasoning, repository tools, shell commands, and test
execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-16 13:46:50 -05:00
DottaandPaperclip 9e9f744f58 Show blocker links in the task chat (#11456)
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->

## Thinking Path

> - Paperclip helps operators supervise agent work through tasks and
task threads.
> - The redesigned task thread shows the current work and its state.
> - A blocked task did not show the dependency that prevented progress.
> - Operators had to leave the thread to find the direct and final
blockers.
> - This pull request adds compact blocker links at the top and bottom
of the task thread.
> - The benefit is that operators can identify and open the relevant
tasks without adding a large notice to the thread.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The redesigned task thread did not show which task directly blocked the
current task or which task ultimately blocked its dependency chain.

**Subsystem affected**

`server/`, `packages/shared/`, and `ui/` task-blocker presentation.

**Current behavior**

A blocked task can open in the redesigned thread without a visible
dependency link at the top or bottom of the conversation.

**Proposed behavior**

Show one compact amber row for the direct blocker. Show a second row for
the selected final blocker when one exists. Render the rows at both ends
of the thread.

**Reason and benefit**

Operators can see the reason for the blocked state and open the relevant
task from the conversation. The compact rows preserve thread density.

**Breaking changes**

None. The new blocker-attention fields are optional. Existing clients
remain compatible.

## What Changed

- Added a compact task-chat component for direct and selected final
blocker links.
- Added the blocker rows to the top and bottom of populated and empty
task threads.
- Added link-ready blocker-attention details so an intermediate selected
task stays on its correct direct chain.
- Included blocker-link changes in the thread content key so pinned
threads follow a newly added bottom row.
- Added component, scrolling, server contract, and Storybook coverage
for the new states.

## Verification

- `pnpm exec vitest run ui/src/components/TaskChatThread.test.tsx
server/src/__tests__/issue-blocker-attention.test.ts` (38 tests passed)
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`

## Risks

- Low risk. The rows only render while the task status is `blocked` and
an unresolved blocker is available.
- Long titles are truncated to keep each blocker on one line. The full
task label remains available in the link title.
- Older server payloads keep the original leaf-selection behavior
because the new sampled details are optional.

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

## Model Used

- OpenAI Codex with GPT-5. The run used tool-enabled reasoning and code
execution. The context-window size was not exposed to the run.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-16 13:09:17 -04:00
Nicky LeachandPaperclip cd501499a2 test: add ACPX run lifecycle characterization baselines (#11461)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The adapter runtime starts, turns, settles, and composes ACPX runs
> - Recent lifecycle corrections changed several order and cleanup rules
> - Those rules need regression coverage before the planned engine
refactor
> - This pull request adds characterization suites for the corrected
behavior
> - The benefit is a clear test baseline for the next refactor

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The ACPX adapter runtime and server heartbeat lifecycle need stable
regression coverage for their current corrected behavior.

**Subsystem affected**

Cross-cutting (multiple of the above): `packages/adapter-utils` and
`server` test suites.

**Current behavior**

The runtime has corrected rules for startup, turns, settlement, composed
results, and heartbeat terminalization. The repository lacks a single
characterization baseline for these rules.

**Proposed behavior**

Keep the current lifecycle rules pinned by five test suites. Let the
later engine refactor change behavior only when it updates these tests
with a clear reason.

**Reason and benefit**

The suites expose order, cleanup, transport, timeout, retry, result, and
lease-release changes during the refactor. They also record one known
latent defect as current behavior.

**Breaking changes**

None. This pull request adds tests only.

## What Changed

- Add startup characterization coverage for commands, launch values,
session fingerprints, sync order, bridge overlap, and cleanup paths.
- Add turn characterization coverage for inputs, events, transports,
timeout and cancel behavior, retry rules, errors, and usage.
- Add settlement characterization coverage for teardown, adapter
sync-back, workspace restore order, native sync, and error policy.
- Add composed-run characterization coverage for result forms,
finalization sets, and host-lane warm save and warm hit behavior.
- Add server coverage that checks run terminalization before environment
lease release.

## Verification

- Run `npx vitest run
packages/adapter-utils/src/acpx-engine/startup-characterization.test.ts
packages/adapter-utils/src/acpx-engine/turn-characterization.test.ts
packages/adapter-utils/src/acpx-engine/settlement-characterization.test.ts
packages/adapter-utils/src/acpx-engine/composed-run-characterization.test.ts
packages/adapter-utils/src/acpx-engine/execute.test.ts`.
- Run `npx vitest run
server/src/__tests__/heartbeat-run-terminalize-before-release.test.ts`.
- The adapter-utils run passes 178 tests, and the server run passes 4
tests.
- Check `pnpm --filter @paperclipai/adapter-utils typecheck`.
- Check `pnpm --filter @paperclipai/server typecheck`.

## Risks

Low risk. The change adds test files and does not change production
code. One known cold ensure-session cleanup defect remains pinned as
current behavior.

## Model Used

OpenAI Codex, GPT-5, with tool use and code execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-15 21:36:59 -07:00
Nicky LeachandPaperclip e52b8a343f fix: ACP run lifecycle corrections — failure settlement, workspace sync-back, lease cleanup (#11454)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent adapters run ACP sessions and manage runtime, workspace, and
lease resources.
> - Several failure paths left runtime bridges, staged workspaces, or
environment leases active after an error.
> - These leaks reduce run reliability and can leave later runs without
clean resources.
> - This pull request closes the failure paths, applies one teardown
policy, and adds regression tests.
> - The benefit is consistent failure settlement and safer reuse of
agent workspaces and leases.

## Linked Issues or Issue Description

**What happened?**

ACP runs could leave runtime bridges, staged workspaces, or environment
leases active after failures. Claude and Gemini ACP runs did not restore
the sandbox workspace on teardown. Lease release stopped when one lease
returned an error.

**Expected behavior**

Each ACP failure must return an error result and settle its resources.
Teardown must run each step, release leases independently, and restore
the host workspace when the sandbox ends. Pending cleanup leases must
receive bounded retry attempts.

**Steps to reproduce**

1. Run an ACP session that fails after runtime creation or during turn
preparation.
2. Run an ACP session that fails during a warm hit or staged runtime
handoff.
3. Run lease cleanup with more than one lease when the first release
returns an error.
4. Inspect the result phase, teardown calls, workspace state, and lease
metadata.
5. Run the regression suites listed in the Verification section.

## What Changed

- Settle every ACP failure after runtime creation with an error result
and one sandbox.startup span closure.
- Close the ACP runtime and remove warm entries after every pre-turn
failure.
- Run all teardown steps, record teardown errors, release staging leases
in finally, and prevent duplicate teardown.
- Dispose staged runtimes after seam failures and remove borrowed staged
entries with identity guards.
- Add fail-open workspace sync-back teardown for Claude and Gemini ACP
adapters.
- Isolate lease release errors and add bounded retry sweeps for stranded
pending_cleanup leases.
- Atomically claim pending_cleanup retries and clamp attempt readers to
keep the five-attempt bound.
- Default absent provider reusableLeases values to false and align the
fake provider with its runtime declaration.
- Add regression tests for engine, adapter, server, and shared
environment behavior.

## Verification

- [x] `npx vitest run
packages/adapter-utils/src/acpx-engine/execute.test.ts` — 124 tests
passed.
- [x] `npx vitest run
packages/adapters/codex-local/src/server/acp.test.ts
packages/adapters/claude-local/src/server/acp.test.ts
packages/adapters/gemini-local/src/server/acp.test.ts` — 61 tests
passed.
- [x] `npx vitest run server/src/__tests__/environment-runtime.test.ts
server/src/__tests__/heartbeat-pending-cleanup-sweep.test.ts
server/src/__tests__/reusable-leases-default.test.ts
server/src/__tests__/environment-routes.test.ts
packages/shared/src/environment-support.test.ts` — passed.
- [x] All listed suites ran from the repository root.
- [x] GitHub CI completed successfully for
`cfc349c9f232711433897915112a1c52c0e462ca`.
- [x] Greptile completed with a 5/5 confidence score and no blocking
finding.

## Risks

The engine changes affect failure settlement and teardown order across
ACP runs. The server changes add retry state to existing lease metadata
without a schema migration. The adapter changes restore workspaces after
sandbox execution. Regression tests cover the changed paths. GitHub CI
and Greptile passed for the current head.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. This change fixes runtime
reliability and does not duplicate a roadmap feature.

## Model Used

OpenAI GPT-5 Codex. The model used tool-based repository inspection,
GitHub operations, and code review support. The runtime does not expose
a context-window value.

## 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 (for example, `docs/...` or
`fix/...`) and contains no internal Paperclip ticket id or
instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-15 18:52:08 -07:00
Nicky LeachandPaperclip fdb9a4880d fix(security): route paperclipai CLI guidance through safe npx form (CWE-78) (#11400)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip provides CLI commands and guidance for operators and
agents
> - The `pnpm paperclipai` script can pass argument values through a
shell
> - Shell re-parsing can execute command substitutions inside quoted
values
> - This pull request routes guidance through inert-argv `npx
paperclipai` commands and adds regression coverage
> - The benefit is safer operator guidance across documentation and
runtime hints

## Linked Issues or Issue Description

This pull request fixes a command-injection-class defect in Paperclip
CLI guidance.

**What happened?**

The `pnpm paperclipai <sub> --flag "$VALUE"` form can re-parse argument
values through a shell. A command substitution inside a quoted value can
execute on the host.

**Expected behavior**

Paperclip guidance must pass CLI values as inert argument values.
Host-derived values must not appear in copyable commands.

**Steps to reproduce**

1. Run a Paperclip guidance command that uses the `pnpm paperclipai`
script.
2. Provide a quoted value that contains a command substitution.
3. Observe that the shell can evaluate the substitution before the CLI
starts.
4. Compare the result with the `npx paperclipai` form.

**Paperclip version or commit**

`5670984b75d109950c968542a0111ebb6967f4da`

**Deployment mode**

All deployment modes that show or use the affected CLI guidance.

**Installation method**

Built from source and installed CLI guidance.

**Agent adapter(s) involved**

Not adapter-specific (core bug).

**Database mode**

Not database-related.

**Access context**

Both.

**Additional context**

The earlier merged PR
[#11343](https://github.com/paperclipai/paperclip/pull/11343) used the
unsafe `pnpm exec paperclipai` form. This fresh PR replaces that
guidance with the safe `npx paperclipai` form.

## What Changed

- Standardize documentation and runtime hints on `npx paperclipai`.
- Remove the broken `pnpm exec paperclipai` guidance.
- Use a static `<host>` placeholder in private-hostname guidance.
- Add regression tests for unsafe forms, continued lines, static hosts,
and offline guidance.

## Verification

- `git diff --check
origin/master...origin/fix/paperclipai-cli-npx-safe-invocation` passes.
- The branch adds `server/src/__tests__/cli-invocation-safety.test.ts`
and updates private-hostname tests.
- CI must run the new tests, typecheck, lint, and build checks.
- Local Vitest execution was not available because this worktree has no
installed Vitest binary.

## Risks

- The change affects operator and agent documentation text.
- The runtime hints now show `<host>` instead of a request-derived host
value.
- No database schema or migration changes exist.
- CI will detect any missed unsafe invocation or type error.

## Model Used

OpenAI GPT-5, exact model ID `gpt-5`, with tool use and code-review
assistance. The model used repository inspection, Git operations, and PR
preparation.

## 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] CI ran the test suites and they pass; local test execution was
unavailable in this worktree
- [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 addressed all Greptile and reviewer comments before requesting
merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-14 22:11:16 -07:00
Opaque ea3a5ea7d2 fix(recovery): skip successful-run handoff for recovery-action-driven runs (#9010)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The recovery subsystem repairs issues stranded without a valid
disposition: `decideSuccessfulRunHandoff` queues one corrective wake per
successful-but-dispositionless run, and `source_scoped_recovery_action`
wakes a recovery owner for stranded issues
> - `decideSuccessfulRunHandoff` already refuses to treat corrective
handoff runs, issue-monitor runs, and comment-driven wakes as handoff
*sources* — but not runs woken by `source_scoped_recovery_action`
> - Because the handoff idempotency key includes `sourceRunId`, every
succeeding recovery run is a brand-new source: recovery run → handoff
wake → corrective run → new recovery action → recovery run → … with
`DEFAULT_MAX_SUCCESSFUL_RUN_HANDOFF_ATTEMPTS` never binding (it is
per-source-run) and the source-scoped recovery action created with
`maxAttempts: null`
> - The cycle is unbounded, each leg is a ~15s no-op "succeeded" run,
and the designed handoff-exhausted escalation (blocked + exhausted
notice) never engages
> - This PR adds recovery-action-driven runs to the existing skip list,
so recovery runs own their own follow-up path and the stranded-issue
escalation remains the exit when the disposition is still missing
> - The benefit is that missing-disposition recovery converges (one
handoff, then escalation) instead of ping-ponging wake volume
unboundedly

## Linked Issues or Issue Description

Refs #6523 — same wake-loop family (repeated
`source_scoped_recovery_action` wakes); this PR fixes the variant where
the loop partner is the successful-run handoff.

**Observed behavior:** in a 16-agent deployment, one agent produced 223
runs in 2 hours, every run `succeeded` with ~15s duration, with
`contextSnapshot.wakeReason` alternating exactly between
`source_scoped_recovery_action` (109) and
`finish_successful_run_handoff` (108). The source issue never reached
the exhausted escalation.

## What Changed

- `server/src/services/recovery/successful-run-handoff.ts`: new
`isRecoveryActionDrivenRun` predicate (matches
`contextSnapshot.wakeReason === "source_scoped_recovery_action"` or a
present `contextSnapshot.recoveryActionId`), consulted in
`decideSuccessfulRunHandoff` alongside the existing corrective-handoff /
issue-monitor / comment-driven skip guards.
- `server/src/services/recovery/successful-run-handoff.test.ts`: cases
asserting recovery-driven runs are skipped via both markers.

## Verification

- `pnpm -F @paperclipai/server exec vitest run
src/services/recovery/successful-run-handoff.test.ts` → 17 passed (16
existing unchanged + 1 new).
- Production validation (same logic deployed as a dist patch on
2026.626.0): the alternating recovery/handoff wake pattern stopped after
restart; ordinary successful-run handoffs (first corrective wake per
genuine source run) continue to queue.

## Risks

Low-to-moderate, scoped to one decision function. The behavioral shift:
a recovery-action run that succeeds without fixing the disposition no
longer gets a corrective handoff wake — instead the stranded-issue
detector escalates (blocked + recovery owner + exhausted notice), which
per the existing `escalateStrandedAssignedIssue` code is the designed
terminal path. Runs not woken by a recovery action are unaffected
(covered by the existing 16 tests, all green).

## Model Used

Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use
via Claude Code. Human-reviewed before submission.

## 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
2026-08-14 23:21:34 -05:00
DottaandCodex edb8083538 fix(server): serialize interaction review verdicts
Lock the issue before accepting or rejecting review confirmations, reauthorize against the current policy, and cover concurrent policy tightening.

Co-Authored-By: Codex <noreply@openai.com>
2026-08-15 01:53:26 +00:00
DottaandCodex 991f40bb2e fix(server): serialize review policy verdict authorization
Recheck terminal verdict and policy mutations under a row lock, and scope interaction verdict enforcement to the review confirmation itself.

Co-Authored-By: Codex <noreply@openai.com>
2026-08-15 01:25:17 +00:00
DottaandPaperclip 373b675f94 fix(server): prevent review policy verdict downgrade bypass
Authorize verdicts and policy changes against the stored restrictive review policy, remove downgrade guidance, and cover both restrictive policies with route regressions.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-15 01:12:34 +00:00
DottaandClaude Opus 5 8ee1fb21a6 feat(ui): badge the review policy when it constrains approval (#10938)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents move their work into review, and a reviewer must then give a
verdict on it
> - By default anyone with write access can give that verdict, including
the agent that did the work
> - The server can constrain that default per issue with a
`reviewPolicy` column, but no screen showed the value
> - A reviewer could therefore press Approve on a review that the server
refuses, and get a 403
> - This pull request shows the policy as a badge on the two surfaces
where a person gives a verdict
> - It also makes an agent verdict read as a verdict in the activity
timeline
> - The benefit is that a reviewer sees who can approve before they try

## Linked Issues or Issue Description

No public GitHub issue exists for this change. The description below
follows
`.github/ISSUE_TEMPLATE/enhancement.yml`.

**What existing behavior does this improve?**

The issue review flow. A reviewer cannot see the approval constraint on
an issue
before they give a verdict.

**Subsystem affected**

Web UI (`ui/`), with one supporting change in the server attention
service.

**Current behavior**

The server stores an optional approval constraint for each issue in a
`reviewPolicy` column. The column has three meaningful states: the
default
(`NULL` or `anyone`), `not_creator`, and `human_only`. The server
enforces the
constraint when it receives a verdict.

No screen shows the value. Two problems follow:

1. A reviewer presses Approve on a review that the server refuses. The
server
   answers 403, and the reason is not visible on the card.
2. An agent that accepts or rejects a review renders in the activity
timeline as
the raw action id, for example "issue thread interaction accepted". A
person
   who reads the timeline cannot tell that a verdict was given.

**Proposed behavior**

Show the constraint as a read-only badge on the two surfaces where a
person
gives a verdict. Show no pixels for the default state, because the
default is
what every issue already does. Make an agent verdict read as a verdict
in the
timeline.

Only agents set the column today, so this change adds no control to set
it.

**Reason and benefit**

A reviewer sees the constraint before they act. This prevents the 403,
and it
removes the need to explain the 403 afterwards. The timeline also
becomes
complete, because it now shows agent verdicts and human verdicts in the
same way.

**Breaking changes**

None. The change adds a badge and changes copy. It adds no column, no
endpoint,
and no request.

**Additional context**

The server-side column and the verdict enforcement landed earlier in
#10931.
This pull request is the user interface for that column. The default
state stays
unchanged on screen, so the badge appears on a small number of issues.

## What Changed

- **A read-only "Approvals" row** in the issue Execution properties. The
row
renders *only* for a constrained policy: "Anyone else" (`not_creator`)
or
"Human only" (`human_only`). A `NULL` or `anyone` column adds no row, so
the
  panel is untouched on the overwhelming majority of issues.
- **The same badge on the stalled-review card** in `/decisions`, above
the three
review verbs. A reviewer now sees the constraint before they press
Approve.
  The condition is the same, so the default card is unchanged.
- **Agent verdicts read as verdicts in the activity timeline.** An agent
that
accepted or rejected a review request previously rendered the raw action
id
("issue thread interaction accepted"). It now reads "approved the
request". A
  stalled-review decision names the verb that the actor chose.
- **A cleared policy reports as "anyone", not "none",** in the
field-change
receipt. The `reviewPolicy` column is nullable by default, so an absent
value
  is a real setting rather than a missing one.
- **All copy comes from `ui/src/lib/review-policy.ts`.** Its badge
lookup returns
`null` for the default. This makes "no pixels for the default" one
enforced
decision instead of a condition repeated at each call site. It also
keeps the
  badge, the activity line, and the receipt reading alike.
- **The server attention service carries the policy** on the review
attention
  subject, so the stalled-review card can read it.

## Verification

Automated tests:

- `ui/src/lib/review-policy.test.ts` — the default returns no badge,
however the
column spells it (`null`, `undefined`, `"anyone"`). An unrecognised
policy from
  the wire shows nothing rather than leaking an enum value.
- `ui/src/components/AttentionQueueRow.test.tsx` — no badge on the
default card,
and the verbs still render. Suppression of the badge must not suppress
the card.
- `ui/src/components/IssueProperties.test.tsx` — no Approvals row on the
default.
  The constrained row contains no `button`, so nothing there can PATCH.
- `server/src/__tests__/attention-service.test.ts` — the review
attention subject
carries the policy, and subjects built from narrower selects do not
claim one.

Run them with:

```sh
pnpm vitest run ui/src/lib/review-policy.test.ts \
  ui/src/components/AttentionQueueRow.test.tsx \
  ui/src/components/IssueProperties.test.tsx \
  server/src/__tests__/attention-service.test.ts
```

Manual steps:

1. Open an issue that has no `reviewPolicy`. Confirm that the Execution
   properties panel shows no Approvals row.
2. Set the column to `not_creator`. Reload the issue. Confirm that the
Approvals
   row reads "Anyone else", and that the row has no control.
3. Move that issue into review. Open `/decisions`. Confirm that the
stalled
   review card shows the same badge above the review verbs.
4. Let an agent approve the review. Confirm that the activity timeline
reads
   "approved the request" and not "issue thread interaction accepted".

Screenshots were captured at 1440x900 and 390x844, in light mode and
dark mode,
with the three policy states side by side. The leftmost column in each
capture is
the default. It carries no badge and no extra row.

## Risks

Low risk.

- The change is additive on screen. Every new surface is behind a
constrained
  policy, so the default path renders exactly as before.
- The badge is read-only. It has no control and sends no request, and a
test
  asserts that the row contains no `button`.
- An unknown policy value from the wire renders nothing. It does not
render the
  raw enum.
- No migration, no schema change, and no endpoint change.

## Model Used

Claude Opus 5 (Anthropic), model id `claude-opus-5[1m]`, 1M context
window,
with extended thinking and tool use enabled. Used through Claude Code
for the
implementation, the tests, and this description.

## Checklist

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 20:29:36 -04:00
Nicky LeachandPaperclip 69027cbaae fix(workspaces): reopen archived git worktree for managed_checkout projects (#11395)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Execution workspaces give agent tasks isolated Git worktrees
> - Archived isolated workspaces must reopen against a live project
checkout
> - A managed_checkout project has no project workspace directory in its
row
> - The reopen path used the removed archived worktree as the Git
working directory
> - This pull request resolves the live managed checkout and reports a
clear error when it is unavailable
> - The benefit is reliable workspace reopen behavior after archive
cleanup

## Linked Issues or Issue Description

Related public pull request:
[#6164](https://github.com/paperclipai/paperclip/pull/6164) clears
archive state during un-archive. This pull request fixes the separate
reopen failure that occurs after archive cleanup.

**What happened?**

An archived isolated `git_worktree` workspace under a `managed_checkout`
project failed to reopen after cleanup. The route attempted to run Git
in the removed archived worktree and returned a generic service error.

**Expected behavior**

The reopen path should use the live managed checkout as the Git base
directory and should return a clear error when that directory is
unavailable.

**Steps to reproduce**

1. Create a project with `managed_checkout` source control.
2. Create and archive an isolated `git_worktree` execution workspace.
3. Let archive cleanup remove the worktree.
4. Reopen the workspace for an issue.

**Paperclip version or commit**

`cab0c31dc61310106caef42ca244e9f7b0f19460`

**Deployment mode**

Local dev with the default embedded database.

**Agent adapter(s) involved**

Not adapter-specific. This issue affects core workspace handling.

## What Changed

- Resolve the live managed checkout when a managed project reopens an
archived Git worktree.
- Keep local-folder projects on their project workspace directory.
- Validate the Git base directory before `git rev-parse` and return a
scrubbed error.
- Add nine regression tests for workspace reopen behavior.

## Verification

- `server` TypeScript check passes with `tsc --noEmit`.
- `server/src/__tests__/execution-workspace-reopen.test.ts` passes with
9 tests.
- GitHub Actions must pass all required PR checks.

## Risks

Low risk. The change affects only archived isolated workspace reopen
behavior. It reuses the existing managed checkout and Git authentication
helpers. It adds no new credential path, endpoint, or telemetry.

## Model Used

OpenAI GPT-5 assisted with review and GitHub operations. The
implementation author supplied the code and test results.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-14 16:44:31 -07:00
Nicky LeachandPaperclip 5ca7b4c1fe fix(security): standardize paperclipai CLI guidance on safe npx path (#11343)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip provides CLI guidance to agents and operators through
documentation and runtime messages.
> - Content-bearing `pnpm paperclipai` examples send arguments through a
shell.
> - Shell evaluation can execute command substitutions in untrusted
argument content.
> - Runtime hostname guidance can also place request-derived content
inside a shell command.
> - This pull request uses `npx paperclipai` for content-bearing
guidance and uses a static hostname placeholder.
> - The benefit is safer copy-paste guidance for agents and operators.

## Linked Issues or Issue Description

**Issue type**
Incorrect information

**Where is the issue?**
CLI guidance in `doc/CLI.md`, `skills/paperclip/SKILL.md`,
documentation, and runtime-generated hints.

**What's wrong?**
Content-bearing `pnpm paperclipai` commands can pass argument text
through `/bin/sh`. Shell command substitution in an argument can execute
before the CLI receives the value.

**Suggested fix**
Use `npx paperclipai` for content-bearing commands. Use a static
`<host>` placeholder when runtime guidance displays the allowed-hostname
command.

## What Changed

- Replace content-bearing `pnpm paperclipai` examples with `npx
paperclipai` across the documentation and agent-facing guidance.
- Update runtime-generated CLI hints to use a static `<host>`
placeholder.
- Add safety notes to `doc/CLI.md` and `skills/paperclip/SKILL.md`.
- Add scans and regression tests for unsafe invocation and hostile
hostname headers.
- Keep fixed lifecycle commands and `pnpm --filter @paperclipai/*` build
commands unchanged.

## Verification

- Run `tsc --noEmit` for the changed server files.
- Run `cli-invocation-safety.test.ts`.
- Run `private-hostname-guard.test.ts`.
- Confirm that hostile hostname headers do not enter shown shell
commands.
- Confirm that the three commits contain the required Paperclip
co-author trailer.

## Risks

- This change updates documentation and diagnostic text across many
surfaces.
- Fixed lifecycle and setup commands remain unchanged.
- The tests fail if content-bearing `pnpm paperclipai` guidance returns.
- The change does not alter the CLI argument parser.

## Model Used

OpenAI Codex, GPT-5, tool use, code execution, and repository review
assistance.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-13 16:43:21 -07:00
Nicky LeachandPaperclip 05d58cd884 fix(tool-gateway): keep unsigned ask-first requests out of the review queue without cancelling them (#11338)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The tool gateway creates approval requests and the review queue
reads them
> - The gateway creates a request row before it adds the signature
> - A review-queue read can see the row during that short unsigned state
> - The old read path cancels the unsigned row, so approval returns `409
action_not_pending`
> - This pull request hides unsigned in-flight rows and keeps them
pending until signing finishes
> - The benefit is that approval succeeds while invalid signed requests
remain cancelled

## Linked Issues or Issue Description

**What happened?**

A review-queue read cancelled a pending tool action request when the
request had no signature yet. The next approval call returned `409
action_not_pending`.

**Expected behavior**

The review queue must hide an unsigned in-flight request and keep its
state as `pending`. A request with an invalid signature must remain
cancelled.

**Steps to reproduce**

1. Create a require-approval tool action request.
2. Read the review queue while the request signature is still null.
3. Approve the request after the creator adds the signature.
4. Observe that the old code cancels the request and the approval call
fails.

**Paperclip version or commit**

Commit `720aa0a494bbaa1711bc7a3d795f810765915bfe`.

**Deployment mode**

Local dev with the embedded PGlite database.

**Installation method**

Built from source with pnpm.

**Agent adapter(s) involved**

Not adapter-specific. This is a core tool access service bug.

**Database mode**

Embedded PGlite.

**Access context**

Board and agent tool approval flow.

## What Changed

- Keep a pending request with a null signature out of
`listActionRequests` results.
- Cancel a request when its non-null signature fails verification.
- Add a permanent regression test for the unsigned request transition.
- Update the contract test for unsigned and invalid-signature requests.

## Verification

- Run the tool access service, tool gateway service, tool gateway, and
tool access policy service tests.
- Confirm 227 tests pass.
- Run the `@mcp-runnable` Playwright end-to-end suite in CI.
- Run the US-9 loop 30 times in CI.

## Risks

The change alters review-queue filtering for unsigned requests. A null
signature now means that signing remains in progress. Invalid signed
requests keep the existing cancellation behavior. The change has no
database migration.

## Model Used

OpenAI Codex, GPT-5, with tool use and code execution. The model
reviewed the handoff, repository rules, and pull request state. The
implementation author supplied the code and tests.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-13 16:15:12 -07:00
scotttongandPaperclip eabecc6f77 feat(annotations): include issue document annotations in agent review context (#11332)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Reviewers annotate plans and issue documents with inline comments,
and assigned agents act on that feedback
> - The server already builds a bounded review context from open plan
annotations and includes it in agent wake payloads
> - Non-plan issue documents did not get the same treatment: their open
annotation threads never reached the agent, and the properties pane did
not surface their annotations
> - This pull request extends the review-context path and the
properties-pane UI to issue documents, at parity with plans
> - The benefit is that agent feedback on any issue document reaches the
assigned agent, not only feedback on the plan

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The review-context pipeline that delivers inline annotation feedback to
assigned agents, and the properties pane that surfaces those annotations
to reviewers.

**Subsystem affected**

The server review-context path
(`server/src/services/plan-review-context.ts`, wake payload assembly in
`server/src/services/heartbeat.ts`, `server/src/routes/issues.ts`),
shared wake-payload types (`packages/shared`, `packages/adapter-utils`),
and the issue properties pane (`ui/src/components/issue-properties/`).

**Current behavior**

A reviewer can annotate any issue document, not only the plan. The agent
wake payload includes open annotation threads for the plan document
only. Feedback left on other issue documents is invisible to the
assigned agent. In the properties pane, the Artifacts tab also gives no
way to see or open a document's annotations.

**Proposed behavior**

Add `buildDocumentReviewContext` beside the existing plan builder. It
collects open annotation threads for all non-plan issue documents,
applies the same thread, comment, and character budgets across
documents, and reports truncation. Include the result as a new
`documentReviewContext` field in agent wake payloads and in the issue
wake-context route. Keep the plan context on its legacy builder and
field so plan-only wakes stay byte-for-byte compatible. Render the new
context in the adapter wake-payload text, and surface annotation counts
and the annotation panel for documents in the properties pane's Plans
and Artifacts tabs.

**Reason and benefit**

The floating annotation popover and persistent highlight UI landed
earlier; this change completes the loop so agent feedback on any issue
document reaches the assigned agent, not only feedback on the plan.

**Breaking changes**

None. The wake payload gains a new optional `documentReviewContext`
field; the existing plan context field and its legacy builder are
unchanged, so plan-only wakes stay byte-for-byte compatible.

## What Changed

- Add `buildDocumentReviewContext` in
`server/src/services/plan-review-context.ts`: bounded review context
(shared thread/comment/character budgets, per-document legacy limits)
over all non-plan issue documents
- Include `documentReviewContext` in agent wake payloads
(`server/src/services/heartbeat.ts`) and in the issue wake-context
response (`server/src/routes/issues.ts`)
- Add shared `DocumentReviewContext` / `DocumentReviewContextDocument`
types in `packages/shared`
- Normalize and render the new context in adapter wake-payload text
(`packages/adapter-utils/src/server-utils.ts`), with tests
- Show a `DocumentAnnotationsCountChip` and the annotation panel for
documents in the properties pane Plans and Artifacts tabs, with tests
- Extend server document-annotations service tests to cover the new
context builder

## Verification

- Run `npx vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/document-annotations-service.test.ts` from the repo
root — 104 tests pass
- Run `TZ=UTC npx vitest run
ui/src/components/issue-properties/IssuePropertiesDocumentAnnotations.test.tsx
ui/src/components/IssueProperties.test.tsx
ui/src/components/IssueDocumentAnnotations.test.tsx
ui/src/components/DocumentAnnotationPopover.test.tsx` from the repo root
— 75 tests pass (one pre-existing monitor-row case asserts UTC
timestamps, so use `TZ=UTC` locally; CI runs in UTC)
- `pnpm run typecheck` in `server/` passes
- Manual: annotate a non-plan issue document, then wake the assigned
agent with a comment — the wake payload lists the open document
annotation threads; the Artifacts tab shows the annotation count chip
and opens the panel

## Risks

- The wake payload gains a new optional `documentReviewContext` field;
consumers that ignore unknown fields are unaffected, and the plan
context field is unchanged
- The context is new input to agent wakes; shared budgets (same limits
as the plan context) bound token cost across all documents
- Low UI risk: the properties-pane changes reuse the existing annotation
components

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

## Model Used

- Claude (Anthropic), model ID `claude-fable-5` (Claude Fable 5), with
extended thinking and agentic tool use (Claude Code harness)

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-13 14:23:09 -07:00
Nicky LeachandPaperclip 44694328a3 fix(issues): make DELETE /api/issues/:id succeed for issues with dependents (#11331)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The server provides issue APIs and the database stores issue child
rows
> - The issue delete endpoint removes the parent issue before dependent
rows
> - Several issue foreign keys had no delete policy, so PostgreSQL
returned a foreign-key error
> - This pull request adds safe cascade and set-null policies and a
clear conflict response
> - The benefit is reliable issue deletion with a useful error when a
restricted audit row still blocks deletion

## Linked Issues or Issue Description

Fixes #7728
Fixes #4660
Fixes #7991
Fixes #4627
Fixes #5086

**What happened?**

`DELETE /api/issues/:id` returned HTTP 500 when dependent comments,
thread interactions, read states, inbox archives, feedback votes, or
ledger rows referenced the issue. The database raised SQLSTATE 23503
because several foreign keys had no delete policy.

**Expected behavior**

The endpoint must remove dependent rows that have no meaning without the
issue. It must keep ledger rows with a null issue reference. It must
return HTTP 409 when a restricted decision audit row still references
the issue.

**Steps to reproduce**

1. Create an issue.
2. Add a comment or thread interaction that references the issue.
3. Send `DELETE /api/issues/:id`.
4. Observe the HTTP 500 response.

**Paperclip version or commit**

Commit `1f8f456f8340823fe2bd891ae8933d942f190b7b`.

**Deployment mode**

Local dev with embedded PGlite or external PostgreSQL.

## What Changed

- Add `CASCADE` to five issue child foreign keys.
- Add `SET NULL` to the finance and cost event issue foreign keys.
- Keep decision audit references restricted.
- Map SQLSTATE 23503 from the issue delete service to HTTP 409.
- Add migration 0217 for the seven changed tables.
- Add regression tests for cascade deletion and restricted decision
references.

## Verification

- Run `pnpm --filter @paperclipai/db typecheck`.
- Run `pnpm --filter @paperclipai/server typecheck`.
- Run `npx vitest run src/__tests__/issue-remove-cascade.test.ts` from
`server/`.
- The regression test applies migration 0217 to a fresh embedded
PostgreSQL database.

## Risks

- Migration 0217 changes only seven foreign keys that reference
`issues.id`.
- Cascade deletion removes child rows that cannot exist without the
parent issue.
- Set-null preserves finance and cost ledger rows.
- Decision audit rows remain protected, so the endpoint can return HTTP
409.

## Model Used

Codex, based on GPT-5, with tool use and code-review support. The
implementation author used an AI coding agent. This PR handoff uses the
same model family to validate the commit and manage the pull request.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-13 12:02:15 -07:00
Apolinario RatioandApolinarioRatio 7787106e5c fix board key issue writes across assignees (#9025)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Board users and board API keys coordinate agents by commenting on
and updating issues.
> - `issue:comment` and `issue:mutate` are intentionally null-mapped
authorization actions, so they need explicit same-company fallback
handling.
> - Same-company board-key writes worked for unassigned or same-actor
issues but failed for issues assigned to another agent.
> - That blocked cross-agent coordination because a board key could not
comment on or patch another agent's issue even inside the same company.
> - This pull request adds the missing board-member issue-write fallback
while keeping viewers denied and sparse service calls fail-closed.
> - The benefit is that non-viewer board members can coordinate agent
work across assignees without restoring broad instance-admin elevation.

## Linked Issues or Issue Description

No public GitHub issue exists. Duplicate search performed:

- `gh search prs --repo paperclipai/paperclip "board key issue mutate"`
returned only this PR.
- `gh search issues --repo paperclipai/paperclip "board key
authorization boundary"` returned no issues.

Bug description:

### What happened

Same-company board-key actors received `403 "Issue is outside this
actor's authorization boundary"` when posting comments or patching
issues assigned to another agent.

### Expected behavior

Active same-company non-viewer board members can comment on and mutate
issues in their company, regardless of agent assignee; viewer members
remain denied.

### Steps to reproduce

Authenticate as a board API key for an active non-viewer company member,
then `POST /api/issues/{id}/comments` or `PATCH /api/issues/{id}`
against an issue assigned to a different agent in the same company.

### Paperclip version or commit

Observed against the current published 2026.626.0 package line and fixed
against current `master`.

### Deployment mode

Authenticated/tailnet board-key access.

## What Changed

- Added a board-actor fallback for `issue:comment` and `issue:mutate` in
`server/src/services/authorization.ts`.
- Restricted that fallback to fully contextualized issue resources with
issue id, status, and explicit assignee fields so sparse service calls
still fail closed.
- Allowed active same-company non-viewer board memberships and denied
viewer memberships for these issue-write actions.
- Added regression coverage for non-viewer board-key comment/mutate on
an issue assigned to another agent.
- Added regression coverage for viewer denial on both `issue:comment`
and `issue:mutate`.

## Verification

- `pnpm exec vitest run
server/src/__tests__/authorization-service.test.ts` passed: 35/35.
- `pnpm --filter @paperclipai/server typecheck` passed.
- `git diff --check` passed.

## Risks

Low-to-moderate authorization risk because this changes issue-write
access. The scope is constrained to active same-company board
memberships, excludes viewers, and requires route-shaped issue context
before granting access. Cross-company access and sparse/null-mapped
calls continue to fail closed.

> 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 using GPT-5-class reasoning with local shell,
GitHub CLI, and test execution tools in an OpenClaw/Codex 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
- [ ] 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: ApolinarioRatio <ApolinarioRatio@users.noreply.github.com>
2026-08-13 10:14:05 -07:00
Nicky LeachandPaperclip d0d242e843 feat(server): reopen an archived isolated execution workspace in place (#11322)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Issue execution uses isolated workspaces that hold the issue
worktree
> - A terminal issue can leave its isolated workspace archived and
unable to resume
> - The existing closed-workspace guards returned a conflict and gave
the user no self-serve recovery
> - This pull request reopens the same isolated workspace row and
rebuilds its worktree
> - The benefit is that resume, checkout, and comment actions can
continue without a new workspace row

## Linked Issues or Issue Description

**Problem or motivation**

A terminal issue can point to an archived isolated execution workspace.
Resume, checkout, and comment actions then stop with a conflict.

**What happened?**

A terminal issue kept its issue-to-workspace link after the isolated
workspace reached a closed status. The guarded actions returned HTTP 409
instead of restoring access.

**Expected behavior**

The next authorized resume, checkout, or comment action reopens the same
isolated workspace row. The action rebuilds the worktree and then
continues.

**Steps to reproduce**

1. Create an issue that uses an isolated execution workspace.
2. Move the issue to a terminal state and let the workspace archive.
3. Try to resume the issue or add a comment.
4. Observe the closed-workspace conflict.

**Paperclip version or commit**

e6e79f458e

**Deployment mode**

Built from source with pnpm.

**Proposed solution**

Reopen the closed isolated workspace in place. Rebuild the worktree
before the route reports success.

**Alternatives considered**

Create a new workspace row. This would require repointing the issue link
and would not preserve access for issues that share the original row.

**Roadmap alignment**

ROADMAP.md has no matching reopen item.

## What Changed

- Reopen closed isolated workspace rows in place and rebuild their
worktrees.
- Use the reopen path from resume, checkout, and comment guards.
- Return a clear error when the rebuild fails and keep the workspace
closed.
- Fence terminal reaping and archive cleanup while a reopen is in
flight.
- Update the comment composer to allow the next action to reopen the
workspace.
- Add service and route tests for reopen, scope, failure, and lifecycle
races.

## Verification

- Server TypeScript check passes with the tsc --noEmit command.
- UI TypeScript check passes with the tsc --noEmit command.
- Seventy-two server tests pass across the affected test files.
- The isolated worktree UI test suite has a dependency mismatch and
fails before tests run with the error TypeError: act is not a function.
- CI confirms the UI test job after a clean dependency install.

## Risks

The reopen path changes behavior for closed isolated workspaces. A
rebuild failure returns an error and keeps the row closed. Lifecycle
locks and generation checks protect the worktree from stale cleanup. The
UI test mismatch needs CI confirmation.

## Model Used

OpenAI Codex, GPT-5, with tool use and code review assistance. The
runtime did not provide the context window size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-13 07:44:19 -07:00
f0e6c0f549 feat(server): receive and apply the Paperclip Cloud onboarding seed (#11098)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip Cloud provisions a dedicated tenant stack for each
customer. During signup it asks for a mission, a name and role for the
first agent, and a first task.
> - Cloud pushes those answers into the new stack at activation, as
`POST /api/companies/:companyId/onboarding-seed`.
> - No route served that path. The tenant answered 404, so Cloud
recorded the push as unacknowledged and retried on every portfolio
fetch.
> - The failure was soft. The answers stayed durable in Cloud and the
stack still activated. But the stack opened on the empty first-run
wizard, and it asked the customer again for what they had already given.
> - This pull request adds the receiving endpoint. It validates the
seed, applies it, and acknowledges it.
> - The benefit is that a seeded stack opens with the mission, the agent
and the first task already in place.

## Linked Issues or Issue Description

No public GitHub issue covers this. The problem is described in-PR,
following the feature template.

**Subsystem affected**

server/ — Express REST API and orchestration services. Also
`packages/db` (one new table) and `packages/shared` (one new validator).

**Problem or motivation**

Paperclip Cloud collects onboarding answers at signup and pushes them to
the tenant stack at activation. The tenant had no route for that
request. It answered 404. Cloud treats a non-2xx as "not yet applied",
so it kept the answers and retried, but the stack itself stayed
unseeded. A customer who had already named their mission, their first
agent and their first task arrived at an empty first-run wizard that
asked for all three again.

**Proposed solution**

Serve `POST /api/companies/:companyId/onboarding-seed`. Validate the
body, apply it to the company, then acknowledge it.

The seed is customer free text, so it is bounded and validated in
`packages/shared` and read from the JSON body only. It is never read
from an `x-paperclip-cloud-*` header. That header set is the trusted
identity envelope: every member is derived server-side from the host
plus verified domain records, and that is exactly what makes it
trustworthy. Mixing user content into it would remove the property. A
test plants a mission on a cloud header and asserts that the body value
wins.

Application reuses the shapes the first-run wizard already produces, so
a seeded stack and a manually onboarded one look the same afterwards:

- The mission becomes the company-level goal. A multi-line mission
splits into a title and a description, as the wizard does.
- The agent becomes the company's first hire. Its free-text role ("Chief
of Staff") lands on `title`. The structural `role` stays `ceo`, which is
what the org chart and the default-instructions lookup read.
- The first task becomes an issue in the Onboarding project, assigned to
that agent.

Cloud retries until it gets a 2xx, and it reads any 2xx as "the tenant
holds this content". So the endpoint is idempotent per `revision`. A new
`company_onboarding_seeds` table records the applied revision together
with the goal, the agent and the issue it produced. A replay of a
revision that already matches is a successful no-op. A later revision —
the customer edited their answers — updates those three rows in place
instead of creating a second agent and a second task. The record is
written last, after every other write has landed, so a partial
application cannot present itself as acknowledged.

Everything is applied before the 200 is sent. This is an ordering
guarantee, not eventual consistency. The tests read the database
immediately after the response, with no waiting and no polling, so a
lazy receiver fails them on a fast machine as well as a slow one. That
matters because the redirect into the tenant dashboard is gated on this
acknowledgement.

**Alternatives considered**

Store the seed and let the tenant UI apply it on first load. Rejected:
the dashboard redirect is gated on the acknowledgement, so a background
apply would let the dashboard open before the agent and the task exist.
The whole point is that it must not.

Reuse `POST /companies/:companyId/agents` and `POST
/companies/:companyId/issues` over HTTP from Cloud. Rejected: it needs
three round trips with no shared idempotency key, and it moves the "did
all of it land?" decision to the caller.

**Roadmap alignment**

This completes an existing Cloud-to-tenant contract. It does not add a
new user-facing surface.

## What Changed

- Add `POST /api/companies/:companyId/onboarding-seed` in
`server/src/routes/onboarding-seed.ts`. It authenticates exactly as
`POST /api/companies/:companyId/logo` does, through
`assertCompanyAccess`.
- Add `server/src/services/onboarding-seed.ts`. It applies the mission,
the agent and the first task, and records the applied revision last.
- Add the `company_onboarding_seeds` table: schema, migration `0216`,
and journal entry. It holds the applied revision and the ids of the
goal, agent and issue the seed produced.
- Add `applyOnboardingSeedSchema` in `packages/shared`. It bounds
mission to 2000, agent name to 80, agent role to 120, task title to 200,
and task details to 2000 — the same limits Cloud enforces before it
sends.
- Mount the router in `server/src/app.ts` and register the path in the
OpenAPI document.
- Add `server/src/__tests__/onboarding-seed-route.test.ts` with 13
tests.
- The seeded agent is created on `claude_local`. This mirrors the
teams-catalog default for agents created server-side, where no human
runs an environment test first. `PAPERCLIP_ONBOARDING_SEED_ADAPTER_TYPE`
overrides it.

## Verification

```sh
pnpm typecheck                      # whole workspace, passes
npx vitest run \
  server/src/__tests__/onboarding-seed-route.test.ts \
  server/src/__tests__/openapi-routes.test.ts        # 15 passed
```

The suite runs against embedded Postgres with migrations applied, so
migration `0216` is exercised by every test.

The route tests cover:

- the happy path — mission, agent and task all applied, read immediately
after the 200
- replay of the same revision — no second agent, no second task, no
second goal, no second project
- a later revision — the goal, agent and task are updated in place
- a multi-line mission splitting into a goal title and description
- a revision-only seed
- the activity log entry written once, and not again on a replay
- a caller without access to the company — 403, and nothing written
- a body with no revision — 400
- each field bound past its limit — 400
- a mission planted on an `x-paperclip-cloud-*` header — ignored, body
wins
- an existing Onboarding project — reused, not duplicated

Not verified here: the full Cloud-to-tenant walk against a live stack.
That needs a deployed Cloud and a provisioned tenant together, which is
separate staging work.

## Risks

Migration `0216` creates one new table. It adds no column to an existing
table, rewrites nothing, and backfills nothing, so it is safe to apply
online. The migration safety check passes.

The endpoint writes to a company. Access is enforced by
`assertCompanyAccess`, the same gate the company logo write uses, and a
test covers the denial.

Behavioral note for stacks that already hold data. If a company already
has a non-built-in `ceo` agent, a first seed updates that agent's name
and title rather than creating a second lead. Likewise a seed adopts an
existing company-level goal rather than adding a parallel one. This is
deliberate: the seed is the customer's own stated answer from signup,
and two competing missions or two leads would be worse than one updated
in place. In the intended case — a stack that Cloud has just activated —
none of these exist yet.

The seeded agent is created on `claude_local` with an empty adapter
config. It is idle and needs the usual credential setup before it runs.
Seeding it does not start it.

## Update — rebased onto master + review hardening

Master moved on after this PR was cut, so it was **rebased onto
`master`** and
the seed migration was **renumbered from `0212` to `0216`** (the merged
#11101
took `0212_onboarding_first_task_unique`); the drizzle journal was
re-stitched
and `check:migrations` passes.

Two things landed on top of the original receiver:

- **Mission-only walk contract (PAP-67 r17.4).** The tenant now owns the
first
agent and the first task via #11101's server-owned onboarding path,
which
stamps `ONBOARDING_FIRST_TASK_ORIGIN_KIND` and races safely on the
partial
unique index `issues_onboarding_first_task_uq`. A comment in the apply
path
documents why this receiver leaves the first task to that path on the
cloud
walk, and a paperclip-cloud `node:test`
(`src/onboarding/walk-seed.test.ts`)
asserts the walk's seed carries no `agent`/`firstTask`. The receiver
retains
the agent/first-task code for its documented body contract, kept inert
on the
  cloud path by the mission-only seed.
- **Three Greptile P1 fixes** (`95622fa37`): concurrent application is
now
  serialized under a per-company `pg_advisory_xact_lock` (no duplicate
goal/agent/project/task on overlapping pushes); a revised first task
carries
its resolved `assigneeAgentId`/`goalId`; and the
`company.onboarding_seed_applied`
  audit write is best-effort so a logging failure can't leave the entry
  permanently absent. Two new regression tests cover the first two.

## Model Used

Claude Opus 5 (`claude-opus-5`), 1M context window, extended thinking,
with tool use and code execution. Used for the original codebase
investigation, the implementation, and the tests. The rebase, migration
renumber, mission-only contract, and the three P1 fixes were done with
Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, with tool use
and code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] 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 5 (1M context) <noreply@anthropic.com>
2026-08-12 22:54:07 -07:00
Jannes Stubbemann 6d2eab742f fix(server): retry runs that hit a sandbox provider worker restart window instead of failing setup (#10212)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agent runs execute in sandbox environments acquired through provider
plugins (e.g. the Kubernetes sandbox provider)
> - Lease acquisition happens during run setup, before the adapter
executes
> - When a provider plugin's worker is momentarily unavailable (a server
or plugin restart window), lease acquisition throws "Sandbox provider
... is installed via plugin ..., but its worker is not running."
> - The heartbeat setup path records that as a terminal `setup_failed`:
no retry classifier matches the message, so the run dies instantly even
though the worker returns seconds later
> - This PR classifies that transient condition as retryable
infrastructure so the run is retried instead of being lost to a restart
blip
> - The benefit is that routine restarts no longer produce spurious
instant run failures

## Linked Issues or Issue Description

No public GitHub issue exists; describing inline following the bug
report template.

**What happened**

During a brief sandbox-provider-worker restart window, several runs
failed instantly with `setup_failed` ("... but its worker is not
running."), while runs on the same agent moments earlier and later
succeeded.

**Expected behavior**

A transient, self-healing worker-unavailable condition should schedule a
bounded retry, not terminally fail the run.

**Steps to reproduce**

Trigger a run while the sandbox provider plugin worker is momentarily
unavailable (a server or plugin restart). Lease acquisition throws the
worker-not-running error and the run is finalized as `setup_failed` with
no retry. The recovery test added here reproduces the classification
path.

**Deployment mode**

Cloud multi-tenant execution (Kubernetes sandbox provider plugin).

## What Changed

- Added a dedicated, readable predicate that recognizes the transient
sandbox-provider-worker-unavailable lease failure and treats it as
retryable infrastructure, so the heartbeat schedules a bounded
continuation retry instead of finalizing terminally
- The predicate is anchored to the full lease-failure phrasing (`is
installed via plugin ... but its worker is not running`) so it cannot
match the permanent "provider not installed" message emitted by config
validation
- Added tests proving the readiness poll already waits the full deadline
while the worker handle is absent or `starting` (registered-late
coverage); no poll behavior change was needed

## Verification

- `cd server && npx vitest run
src/__tests__/environment-runtime.test.ts` — poll exhaustion +
registered-late cases
- `npx vitest run src/__tests__/heartbeat-process-recovery.test.ts` —
worker-unavailable message schedules a retry; a non-matching permanent
provider failure still escalates terminally (negative case)

## Risks

Low risk. The retry is bounded by the existing
infrastructure-continuation attempt cap (max 3), the message match is
narrow enough to exclude the permanent provider-not-installed failure
(covered by a negative test), and no readiness-poll or lease-acquisition
behavior changed.
## Model Used

Claude (Anthropic) via Claude Code. Implementation and tests authored by
a Claude Sonnet-class model (`claude-sonnet-5`) dispatched as isolated
per-task implementer agents under a multi-agent orchestration workflow;
root-cause investigation, planning, and two-stage adversarial code
review performed by additional Claude agents. Extended thinking and tool
use enabled throughout.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents run on a heartbeat; when an issue-scoped run ends, the server
records the outcome on the issue's board thread.
> - Normally the agent posts its own summary comment via `POST
/comments`. When it doesn't, the server has a fallback that
auto-publishes a run summary so the board isn't left silent.
> - That fallback (`buildHeartbeatRunIssueComment` in
`server/src/services/heartbeat-run-summary.ts`) returns
`resultJson.summary` **verbatim**, with no length cap or shape check.
> - For runs that never produce a final `result`, `summary` is
concatenated **inter-tool narration** ("Let me check…", "I'll fetch…",
joined by the claude-local adapter's parser). The fallback then dumps
that raw transcript onto the public board thread.
> - In practice this produces long, confusing transcript comments that
mislead reviewers and other agents about what actually happened.
> - This PR gates the fallback so it publishes a clean summary or a
short stub, never raw transcript.
> - The benefit is that the board thread stays trustworthy: a missing
agent summary degrades to a one-line "no summary this run" note instead
of leaking internal narration.

## Linked Issues or Issue Description

No public GitHub issue exists for this; describing it here as a bug
report.

**What happened:** When an issue-scoped heartbeat run finishes without
the agent posting its own comment, the server's fallback publishes
`resultJson.summary` verbatim as the board comment. When the run
produced no final result, that value is concatenated inter-tool
narration, so raw transcript is posted to the issue thread.

**Expected behavior:** The fallback should post a concise summary when
one is available, and otherwise a short stub — never multi-hundred-line
raw narration.

**Steps to reproduce:**
1. Run an issue-scoped agent turn that ends without calling `POST
/comments` and without emitting a final `result` (only inter-tool
narration).
2. Observe the auto-published board comment: it is the full narration
transcript.

**Deployment mode:** self-hosted server
(`server/src/services/heartbeat.ts` fallback path).

**Prior attempt:** an earlier PR for this change was auto-closed when
its head branch was renamed to strip an internal ticket id from the
branch name; this PR supersedes it.

**Related PR:** #7505 (`fix(heartbeat): skip auto-mirror run-summary
comment on cross-owner wakes`) touches the same fallback area but
addresses a different case (cross-owner wakes); this PR is
complementary, gating the *content* of the fallback rather than *when*
it fires.

## What Changed

- `server/src/services/heartbeat-run-summary.ts`:
`buildHeartbeatRunIssueComment` now gates the fallback text. After
resolving `summary` → `result` → `message`, if the text opens with a
narration phrase (`let me`, `i'll`, `i need to`, `i can see`, `looking
at`, `fetching`, `checking`, `first,`) **or** exceeds
`MAX_FALLBACK_COMMENT_CHARS` (1200), it returns a fixed stub: *"Run
completed. Agent did not post a summary comment this run (transcript
withheld — see run log)."* Otherwise it returns the text unchanged.
- `server/src/__tests__/heartbeat-run-summary.test.ts`: added cases for
each narration opener, the length cap, the exact 1200-char boundary
(posts), and clean-summary passthrough.

Runs where the agent posts via the API are unaffected — the fallback
only fires when no agent comment is found for the run, and that call
site is unchanged.

## Verification

- `pnpm --filter @paperclip/server test heartbeat-run-summary` — 13/13
pass (new + existing cases).
- Manual reasoning: the gate is a pure function of the resolved text;
API-posted runs never reach it.
- **CI note:** at the time of opening, `pnpm install --frozen-lockfile`
fails on this branch's base commit with
`ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` (patchedDependencies drift). This
reproduces on every PR based on the current `master` tip (e.g. #10137)
and is unrelated to this two-file change; PRs cut from the prior master
(e.g. #10135) install cleanly. This should clear once the `Refresh
Lockfile` job lands a corrected lockfile on `master` and this branch is
rebased. Happy to rebase or fold in the lockfile fix if a maintainer
prefers.

## Risks

Low risk. The change is confined to one pure function and its tests,
touches no schema or migration, and only alters the *fallback* comment
path (never the normal API-posted path). Worst case is a legitimate
clean summary that happens to open with a gated phrase gets replaced by
the stub — the run log still holds the full detail.

## Model Used

Claude Opus 4.8 (`claude-opus-4-8`), 1M-token context window, extended
thinking, with tool use.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change
(`fix/gate-heartbeat-fallback-comment`) 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 (no
user-facing docs affected)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (blocked on a master-side
lockfile drift, see CI note)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending re-review on this PR)
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-12 16:43:13 -07:00
Constantineandcucurigoo 2f1c0e011e fix(hermes): surface silent nonzero exit failures (#10107)
## Thinking Path

- Followed a silent nonzero Hermes exit from child-process result
parsing through heartbeat run, runtime, task-session, and agent
finalization.
- Found two gaps: the adapter could return `errorMessage: null` for a
numeric nonzero exit, and heartbeat later reused the nullable adapter
field instead of its normalized fallback.
- Kept timeout, signal-cancellation, and specific parsed diagnostics
authoritative.

## Linked Issue(s) / Bug Report

Related to #9751 (stderr classification) and #9519 (exit-zero
finalization), but this is a separate failure mode.

Reproduction: run Hermes with a child result equivalent to `exitCode:
1`, `timedOut: false`, and no parsed diagnostic. The heartbeat row
derives `Adapter failed`, while runtime/task-session/agent finalization
can persist null diagnostics.

## What Changed

- Give silent numeric nonzero Hermes exits a stable fallback such as
`Hermes exited with code 1`.
- Preserve specific parsed errors and timeout/signal semantics.
- Reuse the normalized persisted run error for recovered runtime state,
task-session `lastError`, and agent `errorReason`.
- Add adapter-level and embedded-Postgres regressions.

## Verification

- Hermes adapter `execute.onspawn.test.ts` — 7 passed.
- Focused heartbeat normalized-error regression — 1 passed (91 skipped).
- `pnpm --filter @paperclipai/hermes-paperclip-adapter typecheck` —
passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `git diff --check origin/master...HEAD` — passed.

Independent review also ran the full recovery file: the changed
regression passed; one unrelated pre-existing timing-sensitive test
timed out.

## Risks / Rollout Notes

Low risk. Fallback text is used only when a numeric nonzero exit has no
better diagnostic. Existing timeout, signal, and parsed-error precedence
remains unchanged.

## Model Used

OpenAI Codex `gpt-5.6-sol` with repository inspection, test execution,
and independent read-only 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 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 (not
applicable: internal diagnostics only)
- [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: cucurigoo <cucurigoo@users.noreply.github.com>
2026-08-12 16:09:35 -07:00
Jonathan Reyes 676e20a894 fix(routines): reject HMAC webhook replays (#9994)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Routines allow external systems to start recurring work through
authenticated public webhooks
> - Timestamped HMAC authentication currently verifies authenticity and
age but does not remember an accepted delivery
> - An exact signed request can therefore be reused within its replay
window, including through simultaneous duplicate delivery
> - Replay rejection must be atomic with run creation so concurrent
copies cannot both succeed
> - This pull request derives a non-secret replay identity from each
valid timestamped HMAC delivery and claims it under the existing routine
transaction lock
> - The benefit is at-most-once acceptance of an exact HMAC delivery
without changing ordinary caller-supplied idempotency semantics

## Linked Issues or Issue Description

Fixes: #9993

## What Changed

- Derive a stable, non-secret idempotency key after a timestamped HMAC
signature has been validated.
- Reject a previously claimed HMAC delivery with a conflict while
preserving coalescing for existing non-HMAC idempotency keys.
- Apply the same atomic replay claim when automatic worktree execution
is suppressed.
- Add regression coverage for sequential, concurrent, and suppressed-run
replays.

## Verification

- `pnpm exec vitest run server/src/__tests__/routines-service.test.ts` —
59 tests passed.
- `pnpm typecheck` — all workspace packages passed.
- The sequential test was observed failing on unmodified `master`: the
second identical request resolved and a second run was created.
- The concurrent regression test verifies exactly one request succeeds
and only one routine run exists.

## Risks

- Low migration risk: no schema change is required; the existing
nullable routine-run idempotency field is reused.
- The routine row lock serializes replay claims, adding a small amount
of contention only while a routine run is being created.
- Replay rejection applies only to `hmac_sha256`, which carries the
timestamp needed for a bounded replay policy. Existing `github_hmac`,
bearer, and unauthenticated trigger semantics are unchanged.

> 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) with reasoning, repository inspection, shell
execution, and test 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 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] Documentation does not require an update because this restores the
documented replay-window security behavior without changing
configuration or APIs
- [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
2026-08-12 16:09:00 -07:00
Christian Lappin fc5c6ffed2 fix(server): return 404 instead of 500 for non-UUID company refs (#9959)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The board REST API is how operators and integrations read company
state; `GET /api/companies/:companyId` is one of its most basic reads
> - The route passes the raw path param into `companyService.getById`,
which queries the uuid-typed `companies.id` column directly
> - Any non-UUID ref — a slug, a typo, a stale bookmark — makes Postgres
throw `invalid input syntax for type uuid`, which surfaces as an HTTP
500 with a stack trace in the server log instead of a clean client error
> - A 500 for malformed client input is miscategorized: it pages
operators, pollutes error budgets, and hides the actual problem ("that
ref doesn't exist") from the caller
> - This pull request guards `getById` with a UUID check so non-UUID
refs resolve to `null` and the route returns its existing 404 path
> - The benefit is correct HTTP semantics for bad input, quieter logs,
and one less misleading 500 for self-hosters to chase

## Linked Issues or Issue Description

Fixes #9962 — `GET /api/companies/:companyId` returns 500 (`invalid
input syntax for type uuid`) for non-UUID refs instead of 404. Full
repro and log excerpt in the issue.

## What Changed

- `server/src/services/companies.ts`: `getById` returns `null` early for
non-UUID refs instead of passing them to the uuid-typed query.
- `server/src/__tests__/companies-service.test.ts`: regression test —
non-UUID refs (`"tumbly-haus-creative"`, `"not-a-uuid"`, `""`) resolve
to `null` without a query error.

## Verification

- `npx vitest run src/__tests__/companies-service.test.ts` — 12/12 pass
(new test included, embedded-postgres suite).
- Manual: `curl -i /api/companies/not-a-uuid` → 404 (was 500); `curl -i
/api/companies/<real-uuid>` → 200 unchanged.

## Risks

- Low. Pure input-validation guard on one read path; UUID lookups are
byte-for-byte unchanged. Only behavioral shift is 500→404 for refs that
could never have matched a row.

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — diagnosis
from server logs, patch, and test authored with extended thinking and
tool use; human-reviewed and submitted by @christianlappin.

## 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
- [x] My branch name describes the change and contains no internal
ticket id
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes (n/a —
no doc references this error path)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending first CI run)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending)
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-12 16:08:49 -07:00
dmndbrp-ossandCoder 0db8480b19 fix(SAG-2595): land updatedSince issues-list filter on master (#9050)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issues-list REST endpoint (`GET
/api/companies/:companyId/issues`) backs the digester and other pollers
that ask "what changed since last time".
> - The service layer supports rich filters, but there was no
`updatedSince` filter — so every routine fire re-read the full backlog
instead of just the delta.
> - A prior commit added this filter, but it was never merged to
`master`; it only ran in production because a feature branch happened to
be the live checkout, and the behavior vanished when that directory was
repurposed.
> - This pull request re-lands just the `updatedSince` filter (route
param parse + validation, service `IssueFilters` field, and the
`updatedAt` predicate) as a single-purpose change.
> - The benefit is that pollers can request only issues updated after a
timestamp, and the fix now lives durably on `master` instead of a
transient checkout.

## Linked Issues or Issue Description

No public GitHub issue exists; describing inline per the bug report
template.

**What happened**

`GET /api/companies/:companyId/issues` ignores an `updatedSince` query
parameter, so consumers (e.g. the digester and other pollers) cannot
request only the delta since a prior poll and must re-read the whole
backlog on every fire.

**Expected behavior**

Passing `updatedSince=<ISO 8601 timestamp>` returns only issues whose
`updatedAt` is strictly after that timestamp; a malformed value returns
`400`.

**Steps to reproduce**

1. Call `GET /api/companies/:companyId/issues?updatedSince=<a future ISO
8601 timestamp>`.
2. Observe the endpoint returns the full backlog instead of an empty
list (the parameter is silently ignored).

## What Changed

- `server/src/routes/issues.ts`: parse the `updatedSince` query param,
return `400` for a non-parseable timestamp, and pass it into
`svc.list()`.
- `server/src/services/issues.ts`: add `updatedSince?: string` to
`IssueFilters` and, when present and valid, add a `gt(issues.updatedAt,
since)` condition to the list query.
- `server/src/__tests__/issue-list-updatedsince-filter-routes.test.ts`:
new route+service coverage — future timestamp returns 0 issues, a past
timestamp returns only the delta, and a malformed timestamp returns 400.

## Verification

- `pnpm vitest run
src/__tests__/issue-list-updatedsince-filter-routes.test.ts` — 3/3 pass.
- `pnpm vitest run
src/__tests__/issue-list-assignee-filter-routes.test.ts` — 5/5 pass
(regression check on the sibling filter path).
- `tsc --noEmit` on `server/` — no new errors introduced (pre-existing
unrelated `plugin-sdk` build errors on `master` are untouched).

## Risks

Low risk. Purely additive: the new filter only takes effect when
`updatedSince` is supplied, so existing callers that omit it are
unaffected. Invalid timestamps fail fast with `400` rather than silently
returning all rows.

## Model Used

Claude — `claude-sonnet-4-6` (implementation) with `claude-opus-4-8`
review/merge-gate; tool use + code execution 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] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots (N/A — no UI change)
- [x] I have 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
(in progress)
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Coder (Claude) <coder-claude@paperclip.ing>
2026-08-12 16:08:40 -07:00
c5574599b1 fix(routines): exclude assignee configuration from detail responses (#9818)
## Thinking Path

> - Paperclip is the open-source control plane people use to manage AI
agents for work.
> - Routines are the subsystem that schedules recurring work and returns
routine detail to authorized company actors.
> - Routine detail embedded the complete assignee database row even
though its shared contract requires only assignee identity.
> - That full row can contain protected adapter and runtime
configuration, including environment bindings.
> - The service boundary should project only the fields the routine
contract actually needs.
> - This pull request replaces the full-row query with a company-scoped
identity projection and adds sentinel-based regression coverage.
> - The benefit is useful routine detail without exposing protected
assignee configuration.

## Linked Issues or Issue Description

No public issue exactly tracks this service-level exposure.

- Related prior PR: Refs #4967, an older route-level redaction approach
with broader changes and no focused routine serialization test.
- Related closed PR: Refs #5144, an unmerged prior implementation of the
same identity-projection approach.
- Related agent-route hardening: Refs #8779; that work covers direct
agent responses, while this PR removes protected fields from the routine
embed itself.

Bug details:

- Actual behavior: `GET /api/routines/{routineId}` could serialize the
complete assignee row, including protected adapter/runtime
configuration.
- Expected behavior: routine detail exposes only the assignee identity
required by `RoutineDetail`, including its derived `urlKey`.
- Reproduction: assign an agent with sentinel-only protected
configuration to a routine, retrieve routine detail, and inspect key
presence or serialize the response; no production value is needed or
recorded.
- Version/commit reproduced: upstream `master` immediately before this
PR.
- Deployment mode: service-level embedded Postgres test; the vulnerable
serializer is shared by supported deployments.

## What Changed

- Added a company-scoped assignee summary query in
`server/src/services/routines.ts` that selects only `id`, `name`,
`role`, and `title`, then derives the non-sensitive `urlKey` from the
name.
- Updated `getDetail()` to use that projection instead of selecting the
complete agent row.
- Added focused negative and positive identity assertions, including the
derived `urlKey`, in `server/src/__tests__/routines-service.test.ts`.
- Audited routine list/detail serialization and broader embedded-agent
query sites; routine list exposes only `assigneeAgentId`, while other
agent embeds use explicit projections or authorized agent endpoints.

## Verification

- `pnpm exec vitest run server/src/__tests__/routines-service.test.ts` —
57/57 passed.
- Focused sentinel regression test — passed.
- `pnpm -r typecheck` — passed.
- Server, UI, and CLI builds — passed; UI gzip-size completion used a
4096 MB Node heap.
- `git diff --check` — passed.
- Full `pnpm test:run` — 2,699 passed, 1 skipped, 9 failed in untouched
tests. The failures reproduce outside this change and are limited to
local-adapter `nohup`/PTY behavior, macOS `/tmp` versus `/private/tmp`
normalization, and one workspace-runtime auto-port fixture.

## Risks

- Low compatibility risk: the returned shape now matches the existing
shared `RoutineDetail` contract.
- A consumer relying on undocumented protected agent fields inside
routine detail will stop receiving them.
- No schema, migration, deployment, credential, or production-secret
changes are included.
- Rollback is a single commit revert, but reverting would restore the
exposure.

> This is security hardening for the already-shipped routines subsystem;
`ROADMAP.md` marks Scheduled Routines complete, and this PR does not add
or duplicate roadmap feature work.

## Model Used

- OpenAI GPT-5 via Codex, with repository search, local code execution,
tests, TypeScript typechecking, builds, Git, and GitHub API use. The
runtime does not expose a more granular snapshot ID or context-window
size.

## Checklist

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

---------

Co-authored-by: ClawdeBot <clawdebot@Mac-mini-de-ClawdeBot.local>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-12 16:08:31 -07:00
e31951a17d feat: Claude agent setup-token login in a sandbox (#11286)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Claude agents that run in a remote sandbox need a safe in-product
login path
> - The existing host login route cannot open a pseudo-terminal inside
that sandbox
> - The login flow must protect the browser code, the login URL, and the
OAuth token at every step
> - This pull request adds the parser, the runner, a Daytona
pseudo-terminal transport, and a guarded, owner-bound session route
behind an injectable transport
> - The route stays inert in the default build and fails closed until a
sandbox provider binds the live transport
> - The benefit is a company-scoped setup-token flow with one-time
secret delivery, redaction, and fail-closed transport checks, ready for
a later staged production rollout

## Linked Issues or Issue Description

**Agent or provider**

Claude Code setup-token login for sandbox agents.

**Why this adapter is useful**

Sandbox agents need a supported way to sign in without host credentials.
An authorized owner completes the browser step and receives the token
one time.

**How the agent is invoked**

When a sandbox provider binds the injectable transport, the server
starts `claude setup-token` through a sandbox pseudo-terminal, sends the
browser code to the matched prompt, and returns the token through the
guarded session route. The default build does not bind the transport. In
that state the start route fails closed with a fixed no-secret `503`. It
does not start a process and it does not hold a sandbox lease.

**Additional context**

The transport is injectable, so each sandbox provider binds its own
pseudo-terminal. This pull request adds the Daytona transport but does
not bind it in the production server. A production wiring needs a lease
manager, a live pseudo-terminal factory, a durable token store, and its
own security review. The route keeps secrets out of logs, activity
details, errors, telemetry, and non-owner responses.

## What Changed

- Add strict parsers for the setup-token URL, the prompt, and the
success token.
- Add a login runner that drives the `claude setup-token` command
through a pseudo-terminal.
- Add the Daytona pseudo-terminal transport and the sandbox plugin
wiring.
- Add a company-scoped, owner-bound login session service with rate
limits, a reaper, cleanup, and one-time token delivery.
- Add the guarded session routes at
`/agents/:id/setup-token-login-sessions/*` behind an injectable
transport. The routes become the live login path only when a provider
binds the transport.
- Keep the start route fail-closed in the default build. It returns a
fixed no-secret `503` and it does not bind `setupTokenLogin`.
- Keep the existing host route `POST /agents/:id/claude-login` in place.
This pull request does not replace it.
- Keep confidential responses behind a fail-closed TLS transport guard
with `Cache-Control: no-store`, and extend redaction for the new fields.
- Export the parser and the runner from the Claude local server entry,
and document the new session routes in the OpenAPI spec.

## Verification

- `pnpm --filter @paperclipai/server exec vitest run setup-token-route
setup-token-session`
- `pnpm --filter @paperclipai/adapter-claude-local exec vitest run`
- `pnpm --filter @paperclipai/server run typecheck`
- Confirm that the pull request checks pass on GitHub.

## Risks

- Low user-facing risk on merge. The default build does not bind the
transport, so the production start route stays fail-closed with a `503`.
The merge does not change the production login behavior.
- When a provider later binds the transport, the flow starts a live
sandbox process and holds a short-lived in-memory secret. Cleanup must
stop the child before it releases the sandbox lease.
- The transport guard fails closed when the deployment does not provide
a trusted TLS path. A wrong proxy allowlist can block a valid request.
- The production wiring is out of scope. It needs a lease manager, a
live pseudo-terminal factory, a durable token store, and its own
security review before the server binds `setupTokenLogin`.

## Model Used

Anthropic Claude Opus 4.8 assisted the implementation. It used extended
reasoning, code execution, repository tool use, and a 200,000-token
context window.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` /
`Closes: #` / `Refs: #` OR (b) described the issue in-PR following the
relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes (the
OpenAPI spec covers the new session routes; no user-facing documentation
needs 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>
2026-08-12 13:02:49 -07:00
Nicky LeachandPaperclip f9bd0438e1 fix(server): stop terminal workspace reaper starving on oldest candidates (#11238)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The server runs a scheduled reaper that archives terminal workspaces
after it checks their state.
> - The reaper reads candidates in `updatedAt` order and skips
candidates that do not qualify for archive.
> - The fixed page kept the same skipped candidates at the front, so the
reaper did not inspect later eligible workspaces.
> - This pull request adds a keyset cursor and a throttled log for
sweeps that archive no workspace.
> - The benefit is that the reaper inspects all candidates over time and
reports an inert sweep.

## Linked Issues or Issue Description

**What happened?**

The terminal workspace reaper inspected a fixed page of old candidates.
Ineligible candidates stayed in that page, so the reaper skipped later
eligible workspaces on every sweep.

**Expected behavior**

The reaper must inspect each candidate over time and archive every
eligible terminal workspace.

**Steps to reproduce**

1. Create more than 50 terminal workspace candidates.
2. Keep the oldest page ineligible for archive.
3. Place an eligible workspace after that page.
4. Run repeated reaper sweeps.
5. Observe that the later eligible workspace remains unarchived.

**Paperclip version or commit**

Commit `3efdf555e6e14a46747c796c3c554438bfc03261`.

**Deployment mode**

Built from source with the server test suite.

**Installation method**

Built from source.

**Agent adapter(s) involved**

Not adapter-specific (core bug).

**Database mode**

Not database-related.

## What Changed

- Add a keyset cursor that uses `(updatedAt, id)` order across reaper
pages.
- Reset the cursor at the end of the candidate set so the next sweep
starts at the beginning.
- Add a throttled log when a sweep inspects candidates but archives
none.
- Add regression tests for archive delivery and starvation.

## Verification

- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/execution-workspaces-service.test.ts` — 45 tests pass.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/server-startup-feedback-export.test.ts` — 16 tests pass.
- `pnpm --filter @paperclipai/server typecheck` — clean.

## Risks

Low risk. The change affects only candidate paging and the related
reaper log. The cursor resets after the candidate set, so the sweep
remains periodic.

## Model Used

Codex, OpenAI GPT-5, extended reasoning, tool use, and code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (no exact duplicate found; related scheduler PR #10911 is
distinct)
- [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 (no
documentation applies; this is an internal reaper behavior change)
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-12 10:21:00 -07:00
Nicky LeachandPaperclip e5a7fd7038 Add sandbox device-login for the Codex adapter (#11237)
## Thinking Path

> - Paperclip helps people manage AI agents for work.
> - Agent adapters connect Paperclip to tools such as the Codex command
line tool.
> - A sandboxed Codex agent may start without a credential.
> - The operator needs a safe sign-in flow that does not expose
credentials to the shared package or the sandbox.
> - This pull request adds a company-scoped device-login flow with a
temporary Daytona sandbox.
> - The flow promotes the credential only after readiness checks pass
and removes the temporary sandbox after use.
> - The result lets an operator sign in to a sandboxed Codex agent from
the agent form.

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting (multiple of the above)

**Problem or motivation**

A Codex adapter that runs in a sandbox cannot authenticate when the
company has no pre-provisioned Codex credential.

**Proposed solution**

Add a company-scoped device-login session. Start a temporary sandbox,
run `codex login --device-auth`, stream the code and URL, verify
readiness, promote the credential, and delete the sandbox.

**Alternatives considered**

Pre-provisioning a credential does not support first-time sandbox login.
Keeping the credential in the login sandbox does not provide a durable
company credential.

**Roadmap alignment**

This supports the roadmap item for cloud and sandbox agents.

**Additional context**

The flow uses a five-minute cleanup reaper, compare-and-set status
changes, and a PostgreSQL advisory lock to protect promotion and
cleanup.

## What Changed

- Add the adapter login-session contract, database table, and migration.
- Add company-scoped server routes and a service for sandbox device
login.
- Add credential promotion, readiness checks, and cleanup after login.
- Add restart-safe cleanup for abandoned login sandboxes.
- Add sandbox login controls to the agent creation and edit forms.
- Keep device-login and vendor identifiers out of public shared and
adapter UI symbols.

## Verification

- `pnpm --filter @paperclipai/adapter-codex-local exec vitest run`
passed with 310 tests at the submitted commit.
- The server login route, service, and reaper tests passed with 45 tests
at the submitted commit.
- The agent form render tests passed with 26 tests at the submitted
commit.
- The public-symbol leak check passed at the submitted commit.
- A live Daytona sign-in flow still requires confirmation by a user with
a live sandbox.

## Risks

The migration adds a new company-scoped table. A promotion or cleanup
race could remove a credential or leave a sandbox active, so the service
uses claims, compare-and-set transitions, and an advisory lock. The live
Daytona flow needs operator confirmation because local tests do not
provide a real browser sign-in.

## Model Used

OpenAI Codex, GPT-5, tool use and code execution, extended reasoning.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-12 08:58:25 -07:00
Devin Foley d5bb396518 fix: pass sandbox provider credential env vars to plugin workers; hide Local default under managed-sandbox-only (#11244)
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Environments give each agent run an execution target, and sandbox
providers (Daytona, E2B, Novita, exe.dev) run as plugin workers
> - A managed deployment provisions one platform-managed sandbox row
with no credential in config; the provider is documented to fall back to
its process env var (for example `DAYTONA_API_KEY`)
> - Plugin workers spawn with a scrubbed environment, so that fallback
never sees the host env var — probe and lease acquisition fail with
"require an API key in config or DAYTONA_API_KEY" even when the
deployment sets the var
> - Separately, the managed-sandbox-only mode hides local rows from
every list, but the instance Default picker renders a hardcoded
synthetic "Local" option that no filter touches
> - This pull request forwards each bundled provider's documented
credential env var to its own plugin worker, and gates the synthetic
Local option on the flag
> - The benefit is that the documented host-env credential fallback
works for plugin-backed providers, and managed-sandbox-only instances no
longer offer Local anywhere

## Linked Issues or Issue Description

**Subsystem affected**

Plugin worker environment construction
(`server/src/services/plugin-loader.ts`) and the environments UI
(instance Default picker, agent form inherited-environment label).

**Problem or motivation**

Two follow-ups to the managed-sandbox-only mode (#11200), both found on
a live managed deployment:

1. The deployment sets `DAYTONA_API_KEY` as a server env var and the
managed sandbox row omits `config.apiKey` by contract. "Test Connection"
fails with `Sandbox environment probe failed for provider "daytona".
Daytona sandbox environments require an API key in config or
DAYTONA_API_KEY.` A real agent run fails the same way at lease
acquisition. The cause: sandbox providers run as plugin workers, and
`buildPluginWorkerEnv` passes only model-provider keys and in-cluster
Kubernetes vars. The provider's own documented credential env var never
reaches the worker, so the in-plugin `process.env` fallback reads
nothing. The self-hosted path has the same gap: the Daytona plugin
README documents `DAYTONA_API_KEY` as a host-level fallback, and it does
not work today.

2. With `enableManagedSandboxOnly` on, the instance Default environment
picker still shows "Local". The server filters local *rows* out of the
list, and the client filter mirrors that for cached lists, but this
option is a hardcoded `<option value="">Local</option>` — not a list row
— so no filter removes it. Selecting it writes a null default, which run
selection then rejects fail-closed.

**Proposed solution**

Forward each bundled sandbox provider's documented credential env var
into its plugin worker, keyed by the manifest's declared
`environmentDrivers[].driverKey` so a worker only receives its own
provider's credential (daytona → `DAYTONA_API_KEY`, e2b → `E2B_API_KEY`,
exe-dev → `EXE_API_KEY`, novita → `NOVITA_API_KEY`). Keep the existing
gate: only plugins that declare `environment.drivers.register` receive
any passthrough. In the UI, render the synthetic Local option only when
managed-sandbox-only is off; under the flag show a disabled "Select
environment" placeholder only while no default is stamped yet, and stop
the agent form's inherited label from reading "Local".

**Alternatives considered**

Adding `DAYTONA_API_KEY` to the existing `ADAPTER_ENV_PASSTHROUGH` list
was rejected: that list goes to every environment-driver plugin, so each
provider would receive every other provider's credential. A manifest
schema field for declared credential env vars was rejected as heavier
than needed: the bundled providers are known, and the mapping lives next
to the two existing passthrough lists.

## What Changed

- `server/src/services/plugin-loader.ts`: new
`SANDBOX_PROVIDER_CREDENTIAL_ENV_PASSTHROUGH` map (driverKey →
documented credential env vars). `buildPluginWorkerEnv` reads the
manifest's `environmentDrivers` and forwards only the matching vars,
after the existing `environment.drivers.register` gate. Blank values
stay excluded.
- `server/src/__tests__/plugin-database.test.ts`: the daytona worker
receives `DAYTONA_API_KEY` and not another provider's key; a plugin
whose drivers have no mapping (kubernetes) receives no credential var.
- `ui/src/pages/CompanyEnvironments.tsx`: the Default picker's synthetic
Local option renders only when managed-sandbox-only is off. Under the
flag, a disabled "Select environment" placeholder renders only while the
default is unset.
- `ui/src/pages/CompanyEnvironments.test.tsx`: the Local option is
present by default and absent under the flag; saved non-local
environments stay selectable.
- `ui/src/components/AgentConfigForm.tsx`: the inherited-environment
label falls back to "Managed sandbox" instead of "Local" under the flag.

## Verification

- `server`: `npx vitest run src/__tests__/plugin-database.test.ts -t
buildPluginWorkerEnv` — 5 passed (3 existing, 2 new).
- `ui`: `npx vitest run src/pages/CompanyEnvironments.test.tsx` — 22
passed (2 new); `npx vitest run
src/components/AgentConfigForm.render.test.tsx` — 10 passed.
- `tsc --noEmit` clean in `server` and `ui`.
- Live managed deployment: confirmed the tenant service env carries
`DAYTONA_API_KEY` while the probe fails with the exact message above,
which pins the root cause to the worker env, not delivery.

## Risks

- The worker env grows by exactly one var per matching bundled provider,
only when the deployment sets it and only for plugins that declare a
matching environment driver. Plugins without a mapping see no change.
- Self-hosted behavioral shift is the fix itself: a host-level
`DAYTONA_API_KEY` (or E2B/EXE/NOVITA equivalent) now reaches the
provider as its README documents. Deployments that set the var but
expected it to stay inert had no working configuration to preserve — the
provider errored on every keyless probe and run.
- UI change is inert unless `enableManagedSandboxOnly` is on (default
false everywhere).

## Model Used

Claude Fable 5 (`claude-fable-5`) via Claude Code — extended thinking,
tool use, parallel read-only subagents for the two root-cause traces.

## 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
2026-08-11 19:10:45 -07:00