## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The JavaScript workspace depends on a frozen pnpm lockfile so CI and
installs resolve the same dependency graph everywhere.
> - PR #8557 updated the React package manifests and overrides to
`^19.2.7`, but master kept a stale lockfile.
> - That left master unable to run `pnpm install --frozen-lockfile`
because the lockfile override metadata no longer matched `package.json`.
> - This pull request refreshes only `pnpm-lock.yaml` from current
master so the dependency graph matches the already-merged manifests.
> - The benefit is that master CI can install dependencies again and
React/React DOM consistently resolve to `19.2.7`.
## Linked Issues or Issue Description
Bug fix: PR #8557 merged React/React DOM manifest and override
alignment, but the resulting master branch retained a stale
`pnpm-lock.yaml`. Running `CI=true pnpm install --frozen-lockfile` on
master failed with `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH`, and the lockfile
still resolved React packages through `19.2.4` entries instead of
`19.2.7`.
Related public PR: #8557.
## What Changed
- Refreshed `pnpm-lock.yaml` from current master.
- Kept the diff lockfile-only.
- Brought `react` and `react-dom` lockfile resolution to `19.2.7`.
## Verification
- `CI=true pnpm install --frozen-lockfile`
- `git diff --name-status origin/master..HEAD` shows only
`pnpm-lock.yaml`.
- Lockfile inspection shows `react@19.2.7` and `react-dom@19.2.7`
entries.
## Risks
Low risk. This is a generated lockfile-only refresh. The main risk is
merge-time lockfile drift if master changes dependencies before this
lands; if that happens, regenerate the lockfile instead of taking the
stale master side.
## Model Used
OpenAI GPT-5 Codex via Codex CLI, with repository tool use and command
execution.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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: lockfile-bot <lockfile-bot@users.noreply.github.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI, plugin examples, and plugin SDK all share the same
React dependency graph.
> - Dependabot PR #8471 raised `react` and `@types/react`, but left
`react-dom` and `@types/react-dom` on older 19.x ranges.
> - That let dependency resolution install incompatible React runtime
versions, which makes React refuse to run and breaks UI-oriented test
suites.
> - Current PR policy keeps `pnpm-lock.yaml` owned by CI for
non-dependabot authors, so this PR updates manifests and lets CI
regenerate the lockfile artifact.
> - This pull request replaces #8471 with a complete React toolchain
alignment across the package manifests it touched.
> - The benefit is a single React 19.2.7 runtime graph that keeps
dependency consumers from deduping onto a stale `react-dom` patch.
## Linked Issues or Issue Description
Refs #8471
This PR replaces #8471, which updated `react` and `@types/react` but
left `react-dom` and `@types/react-dom` behind. The resulting dependency
graph can install mismatched `react` and `react-dom` versions, producing
React's incompatible-version runtime error in UI tests.
## What Changed
- Aligned `react-dom` package ranges to `^19.2.7` anywhere the React
dependency set is present.
- Aligned `@types/react-dom` package ranges to the 19.2 line.
- Kept the `react@^19.2.7` and `@types/react@^19.2.17` bumps from #8471.
- Added root pnpm overrides for `react` and `react-dom` so peer-only
consumers resolve to the same 19.2.7 runtime instead of a stale patch.
- Left `pnpm-lock.yaml` out of the PR, per the PR workflow policy; CI
regenerates and shares the lockfile artifact when manifests change.
## Verification
- `cd ui && NODE_ENV=test npx vitest run` passed locally before handoff:
243 files / 1739 tests.
- `NODE_ENV=development pnpm install --lockfile-only --ignore-scripts
--no-frozen-lockfile` passed locally on the rebased branch.
- Confirmed regenerated `pnpm-lock.yaml` resolves `react` and
`react-dom` to 19.2.7 and contains no `react@19.2.4` /
`react-dom@19.2.4` entries.
- GitHub Actions are green on this PR, including `policy`, `Typecheck +
Release Registry`, all general test shards, `Build`, `e2e`, and
`verify`.
- Greptile completed at 5/5 with zero unresolved threads.
## Risks
Low risk. This is a dependency-version alignment only, but React
dependency changes can expose package-manager resolution issues. The
root overrides intentionally constrain the React runtime pair to the
same patch version to avoid that class of failure.
## Model Used
OpenAI Codex based on GPT-5, using command-line tool execution and
GitHub CLI operations in a Paperclip-managed workspace.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
[//]: # (dependabot-start)
⚠️ **Dependabot is rebasing this PR** ⚠️
Rebasing might not happen immediately, so don't worry if this takes some
time.
Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.
---
[//]: # (dependabot-end)
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.28.0 to 0.28.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/evanw/esbuild/releases">esbuild's
releases</a>.</em></p>
<blockquote>
<h2>v0.28.1</h2>
<ul>
<li>
<p>Disallow <code>\</code> in local development server HTTP requests (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p>
<p>This release fixes a security issue where HTTP requests to esbuild's
local development server could traverse outside of the serve directory
on Windows using a <code>\</code> backslash character. It happened due
to the use of Go's <code>path.Clean()</code> function, which only
handles Unix-style <code>/</code> characters. HTTP requests with paths
containing <code>\</code> are no longer allowed.</p>
<p>Thanks to <a
href="https://github.com/dellalibera"><code>@dellalibera</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Add integrity checks to the Deno API (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p>
<p>The previous release of esbuild added integrity checks to esbuild's
npm install script. This release also adds integrity checks to esbuild's
Deno install script. Now esbuild's Deno API will also fail with an error
if the downloaded esbuild binary contains something other than the
expected content.</p>
<p>Note that esbuild's Deno API installs from
<code>registry.npmjs.org</code> by default, but allows the
<code>NPM_CONFIG_REGISTRY</code> environment variable to override this
with a custom package registry. This change means that the esbuild
executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the
expected content.</p>
<p>Thanks to <a
href="https://github.com/sondt99"><code>@sondt99</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Avoid inlining <code>using</code> and <code>await using</code>
declarations (<a
href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p>
<p>Previously esbuild's minifier sometimes incorrectly inlined
<code>using</code> and <code>await using</code> declarations into
subsequent uses of that declaration, which then fails to dispose of the
resource correctly. This bug happened because inlining was done for
<code>let</code> and <code>const</code> declarations by avoiding doing
it for <code>var</code> declarations, which no longer worked when more
declaration types were added. Here's an example:</p>
<pre lang="js"><code>// Original code
{
using x = new Resource()
x.activate()
}
<p>// Old output (with --minify)<br />
new Resource().activate();</p>
<p>// New output (with --minify)<br />
{using e=new Resource;e.activate()}<br />
</code></pre></p>
</li>
<li>
<p>Fix module evaluation when an error is thrown (<a
href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>,
<a
href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p>
<p>If an error is thrown during module evaluation, esbuild previously
didn't preserve the state of the module for subsequent module
references. This was observable if <code>import()</code> or
<code>require()</code> is used to import a module multiple times. The
thrown error is supposed to be thrown by every call to
<code>import()</code> or <code>require()</code>, not just the first.
With this release, esbuild will now throw the same error every time you
call <code>import()</code> or <code>require()</code> on a module that
throws during its evaluation.</p>
</li>
<li>
<p>Fix some edge cases around the <code>new</code> operator (<a
href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p>
<p>Previously esbuild incorrectly printed certain edge cases involving
complex expressions inside the target of a <code>new</code> expression
(specifically an optional chain and/or a tagged template literal). The
generated code for the <code>new</code> target was not correctly wrapped
with parentheses, and either contained a syntax error or had different
semantics. These edge cases have been fixed so that they now correctly
wrap the <code>new</code> target in parentheses. Here is an example of
some affected code:</p>
<pre lang="js"><code>// Original code
new (foo()`bar`)()
new (foo()?.bar)()
<p>// Old output<br />
new foo()<code>bar</code>();<br />
new (foo())?.bar();</p>
<p></code></pre></p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/evanw/esbuild/blob/main/CHANGELOG.md">esbuild's
changelog</a>.</em></p>
<blockquote>
<h2>0.28.1</h2>
<ul>
<li>
<p>Disallow <code>\</code> in local development server HTTP requests (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p>
<p>This release fixes a security issue where HTTP requests to esbuild's
local development server could traverse outside of the serve directory
on Windows using a <code>\</code> backslash character. It happened due
to the use of Go's <code>path.Clean()</code> function, which only
handles Unix-style <code>/</code> characters. HTTP requests with paths
containing <code>\</code> are no longer allowed.</p>
<p>Thanks to <a
href="https://github.com/dellalibera"><code>@dellalibera</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Add integrity checks to the Deno API (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p>
<p>The previous release of esbuild added integrity checks to esbuild's
npm install script. This release also adds integrity checks to esbuild's
Deno install script. Now esbuild's Deno API will also fail with an error
if the downloaded esbuild binary contains something other than the
expected content.</p>
<p>Note that esbuild's Deno API installs from
<code>registry.npmjs.org</code> by default, but allows the
<code>NPM_CONFIG_REGISTRY</code> environment variable to override this
with a custom package registry. This change means that the esbuild
executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the
expected content.</p>
<p>Thanks to <a
href="https://github.com/sondt99"><code>@sondt99</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Avoid inlining <code>using</code> and <code>await using</code>
declarations (<a
href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p>
<p>Previously esbuild's minifier sometimes incorrectly inlined
<code>using</code> and <code>await using</code> declarations into
subsequent uses of that declaration, which then fails to dispose of the
resource correctly. This bug happened because inlining was done for
<code>let</code> and <code>const</code> declarations by avoiding doing
it for <code>var</code> declarations, which no longer worked when more
declaration types were added. Here's an example:</p>
<pre lang="js"><code>// Original code
{
using x = new Resource()
x.activate()
}
<p>// Old output (with --minify)<br />
new Resource().activate();</p>
<p>// New output (with --minify)<br />
{using e=new Resource;e.activate()}<br />
</code></pre></p>
</li>
<li>
<p>Fix module evaluation when an error is thrown (<a
href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>,
<a
href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p>
<p>If an error is thrown during module evaluation, esbuild previously
didn't preserve the state of the module for subsequent module
references. This was observable if <code>import()</code> or
<code>require()</code> is used to import a module multiple times. The
thrown error is supposed to be thrown by every call to
<code>import()</code> or <code>require()</code>, not just the first.
With this release, esbuild will now throw the same error every time you
call <code>import()</code> or <code>require()</code> on a module that
throws during its evaluation.</p>
</li>
<li>
<p>Fix some edge cases around the <code>new</code> operator (<a
href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p>
<p>Previously esbuild incorrectly printed certain edge cases involving
complex expressions inside the target of a <code>new</code> expression
(specifically an optional chain and/or a tagged template literal). The
generated code for the <code>new</code> target was not correctly wrapped
with parentheses, and either contained a syntax error or had different
semantics. These edge cases have been fixed so that they now correctly
wrap the <code>new</code> target in parentheses. Here is an example of
some affected code:</p>
<pre lang="js"><code>// Original code
new (foo()`bar`)()
new (foo()?.bar)()
<p>// Old output<br />
new foo()<code>bar</code>();<br />
new (foo())?.bar();<br />
</code></pre></p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="bb9db84c02"><code>bb9db84</code></a>
publish 0.28.1 to npm</li>
<li><a
href="9ff053e53b"><code>9ff053e</code></a>
security: add integrity checks to the Deno API</li>
<li><a
href="0a9bf2135b"><code>0a9bf21</code></a>
enforce non-negative size in gzip parser</li>
<li><a
href="e2a1a71320"><code>e2a1a71</code></a>
security: forbid <code>\\</code> in local dev server requests</li>
<li><a
href="83a2cbfc35"><code>83a2cbf</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>:
don't inline <code>using</code> declarations</li>
<li><a
href="308ad745d8"><code>308ad74</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4471">#4471</a>:
renaming of nested <code>var</code> declarations</li>
<li><a
href="f013f5f99a"><code>f013f5f</code></a>
fix some typos</li>
<li><a
href="aafd6e48b1"><code>aafd6e4</code></a>
chore: fix some minor issues in comments (<a
href="https://redirect.github.com/evanw/esbuild/issues/4462">#4462</a>)</li>
<li><a
href="15300c30b5"><code>15300c3</code></a>
follow up: cjs evaluation fixes</li>
<li><a
href="1bda0c31d7"><code>1bda0c3</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>,
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4467">#4467</a>:
esm evaluation fixes</li>
<li>Additional commits viewable in <a
href="https://github.com/evanw/esbuild/compare/v0.28.0...v0.28.1">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@cursor/sdk](https://github.com/cursor/cursor) from 1.0.18 to
1.0.19.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/cursor/cursor/commits">compare view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@codemirror/view](https://github.com/codemirror/view) from
6.39.15 to 6.43.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/codemirror/view/blob/main/CHANGELOG.md">@codemirror/view's
changelog</a>.</em></p>
<blockquote>
<h2>6.41.0 (2026-04-01)</h2>
<h3>Bug fixes</h3>
<p>Fix an issue where <code>EditorView.posAtCoords</code> could
incorrectly return a position near a higher element on the line, in
mixed-font-size lines.</p>
<p>Expand the workaround for the Webkit bug that causes nonexistent
selections to stay visible to be active on non-Safari Webkit
browsers.</p>
<h3>New features</h3>
<p>The new <code>EditorView.cursorScrollMargin</code> facet can now be
used to configure the extra space used when scrolling the cursor into
view.</p>
<h2>6.40.0 (2026-03-12)</h2>
<h3>Bug fixes</h3>
<p>Fix a bug that caused Shift-Enter/Backspace/Delete on iOS to lose the
shift modifier when delivered to key event handlers.</p>
<p>Fix an issue where <code>EditorView.moveVertically</code> could move
to the wrong place in wrapped lines with a large line height.</p>
<p>Make sure the selection head associativity is properly set for mouse
selections made with shift held down.</p>
<h3>New features</h3>
<p><code>WidgetType.updateDOM</code> is now called with the previous
widget value as third argument.</p>
<h2>6.39.17 (2026-03-10)</h2>
<h3>Bug fixes</h3>
<p>Improve touch tap-selection on line wrapping boundaries.</p>
<p>Make <code>drawSelection</code> draw our own selection handles on
iOS.</p>
<p>Fix an issue where <code>posAtCoords</code>, when querying line
wrapping points, got confused by extra empty client rectangles produced
by Safari.</p>
<h2>6.39.16 (2026-03-02)</h2>
<h3>Bug fixes</h3>
<p>Perform scroll stabilization on the document or wrapping scrollable
elements, when the user scrolls the editor.</p>
<p>Fix an issue where changing decorations right before a composition
could end up corrupting the visible DOM.</p>
<p>Fix an issue where some types of text input over a selection would be
read as happening in wrong position.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/codemirror/view/commits">compare view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps
[hermes-paperclip-adapter](https://github.com/NousResearch/hermes-paperclip-adapter)
from 0.2.0 to 0.3.0.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/NousResearch/hermes-paperclip-adapter/commits">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI-agent
companies.
> - The control plane coordinates issues, workspaces, documents,
routines, and board review flows across company-scoped data.
> - The local source branch contained related schema, service, and UI
changes for workspace issue scoping and document/routine annotations.
> - These changes need to move together because db schema, shared types,
server services, and UI consumers form one contract.
> - This pull request extracts the migration-bearing control-plane work
from the source branch onto `origin/master`.
> - The benefit is a standalone branch with deterministic migration
order and focused review for the highest-risk part of the split.
## Linked Issues or Issue Description
No GitHub issue exists for this branch split. Internal source task:
[PAP-11234](/PAP/issues/PAP-11234).
Problem/motivation:
- Workspace operations need explicit issue scoping so readiness and
blocker handling can be derived correctly.
- Document annotations need reliable live updates, save failure
surfacing, normalized activity keys, and better comment panel behavior.
- Routine descriptions need the same annotation contract as issue
documents so operators can discuss and edit routine text without
special-case infrastructure.
Proposed solution:
- Add the workspace-operation `issueId` migration and readiness scoping.
- Add routine document/annotation schema, shared types, services,
routes, and UI editing support.
- Keep the related migrations in one PR so the renumbered `0106` and
`0107` migrations land in a deterministic order after current `master`.
Alternatives considered:
- Split migrations into separate PRs, rejected because that would create
migration-numbering conflicts and make each branch less standalone.
- Merge this with UI polish, rejected because this branch needs deeper
server/db review.
Roadmap alignment:
- Checked `ROADMAP.md`; the roadmap mentions future recurring routine
capabilities generally, but no duplicate implementation PR for these
annotation/workspace changes was found.
## What Changed
- Added `0106_workspace_operations_issue_id.sql` and
`0107_routine_description_annotations.sql`, plus schema exports.
- Scoped workspace readiness to blocker issues and attached workspace
operation issue ids.
- Scoped issue-thread interaction accept finalization to the source run.
- Added routine document annotation contracts across
db/shared/server/UI.
- Improved document annotation live updates, activity-key normalization,
save failure surfacing, and comment panel behavior.
- Added issue workspace property controls and compact
blocked-by/quick-control UI updates.
- Added focused server and UI regression tests for the new contracts.
## Verification
- `CI=true NODE_ENV=development pnpm install --frozen-lockfile
--prefer-offline`
- `NODE_ENV=test pnpm exec vitest
server/src/__tests__/document-annotation-routes.test.ts
server/src/__tests__/issue-thread-interactions-service.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/routine-document-annotation-routes.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/workspace-runtime.test.ts
ui/src/components/IssueDocumentAnnotations.test.tsx
ui/src/components/IssueProperties.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/context/LiveUpdatesProvider.test.ts --run` — 10 files, 273 tests
passed.
- `NODE_ENV=test pnpm -r --filter @paperclipai/db --filter
@paperclipai/shared --filter @paperclipai/server --filter
@paperclipai/ui typecheck` — passed, including db migration numbering
check.
## Risks
- Migration-bearing PR; merge this branch before any later PR that adds
migrations with higher numbers.
- Cross-layer contract risk across db/shared/server/ui, mitigated with
targeted tests and affected-package typecheck.
- Review should pay special attention to company scoping in new
routine/document annotation paths.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex via Paperclip `codex_local` / CodexCoder, GPT-5-class
coding model with tool use and shell execution. Exact runtime snapshot
and context-window setting were not exposed by the Paperclip run
context.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details available from the run context)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots (N/A per source task: do not add screenshots/images unless
specifically part of the work)
- [x] I have updated relevant documentation to reflect my changes (N/A;
no public docs changed)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - This change touches several already-shipped subsystems —
document-comment annotations, the selected-agent Conference Room chat
surface, routine description annotations, workspace-operation tracking,
and the board polling/inbox UI
> - A batch of incremental fixes and two small backend additions had
accumulated on a local mainline and were deployed to a live instance,
but never landed upstream — so each `origin/master` sync kept
re-diverging
> - Leaving them un-upstreamed means the same delta has to be re-merged
on every sync and risks being lost or silently reverted
> - This pull request rebases that delta cleanly on top of current
`origin/master` (preserving recent upstream work such as reusable
sandbox leases and the relation-list collapse controls) and brings it up
for review
> - The benefit is that mainline and the deployed instance converge, and
these fixes/additions get normal review + CI + Greptile coverage
## Linked Issues or Issue Description
No single GitHub issue tracks this; it is a bundle of bug fixes and two
small feature additions. Following the issue-template fields:
**Bug fixes (what was wrong → what this does):**
- Document comments rendered out of document order, didn't live-update
across clients, swallowed save failures, and lost the markdown text
selection on re-render. Now: doc-order sort, live updates, surfaced
save-failure state, stable selection across re-renders.
- The board polling hot path returned oversized payloads on every poll.
Now: an opt-in `summary` projection trims the heartbeat-run list
payload.
- Assorted UI fixes: sidebar nav peek/streamlining edge cases, markdown
file-viewer re-mount/line-height issues on iOS Safari, inbox badge/skill
deep-link tab selection, and `⌘.` work-mode cycling on iOS.
**Feature additions:**
- `workspace_operations.issue_id` — associate a workspace operation with
the issue that triggered it (new migration `0106`, schema, service,
shared type).
- Routine **description annotations** — comment threads on a routine's
description document, mirroring issue document annotations (new
migration `0107`, `routine_documents` schema, routes/service,
editable-sections UI).
- Selected-agent **Conference Room chat** surface wiring and live
issue-thread updates.
**Related PR:** #8229 (`feat(control-plane): add annotation and
workspace controls`, draft) covers overlapping
annotation/workspace-control territory — flagging it so a reviewer can
reconcile the two rather than double-merging.
## What Changed
- `feat(workspace)`: `workspace_operations.issue_id` migration +
schema/service/type; issue workspace property controls reconciled with
upstream's evolved "Service" row.
- `feat(routines)`: routine description annotations —
`routine_documents` schema, migration `0107`, routes/service,
`editable-sections` UI.
- `fix(document-comments)`: doc-order sort, live updates, save-failure
surfacing, stable markdown selection (+ storybook story, rerender test).
- `feat(chat)`: selected-agent Conference Room chat surface and live
issue-thread updates (`LiveUpdatesProvider`, `issue-chat-messages`,
interactions service).
- `perf(board)`: opt-in `summary` projection for the board
polling/heartbeat-run list payload, plus assorted sidebar / file-viewer
/ inbox / IssueProperties UI fixes.
Organized into 5 logical commits. Migrations are numbered incrementally
after upstream's latest (`0105`) — `0106` then `0107`, no journal
collision.
## Verification
- Built by 3-way merging the deployed delta onto current
`origin/master`; the only merge conflict (`IssueProperties.test.tsx`,
two adjacent test blocks) was resolved in favor of upstream's evolved
"green service link above the workspace row" layout, which matches the
merged component's rendered output.
- Confirmed recent upstream work is preserved post-merge: reusable
sandbox lease teardown (`#8513`), the IssueProperties relation-list
collapse controls, and the sidebar streamlined-nav default.
- Confirmed the net diff vs `origin/master` is exactly the intended
feature delta (65 files) and that overlapping server files (`issues.ts`,
`agents.ts`, `heartbeat.ts`) only add feature code without disturbing
upstream logic.
- This delta is already running on a live deployed instance.
- Full typecheck/test suite + Greptile to run in CI (see checklist).
## Risks
- Two new migrations (`0106`, `0107`). Both are additive (new table /
new nullable column) and ordered after upstream's `0105`; no data
backfill, low risk. If another migration-bearing PR merges first,
renumber before merge.
- Largest blast radius is in the merged overlapping UI/service files;
covered by the existing test suites for those files plus CI.
- Overlaps thematically with draft PR #8229 — reviewers should reconcile
rather than merge both blindly.
## Model Used
Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, with tool use
(git, shell). Agentic coding workflow.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to coordinate
AI agents, issues, approvals, comments, and work products.
> - The involved subsystem is issue context: markdown links, issue
properties, related work, lists, filters, inbox/sidebar status, and
plugin-provided external context.
> - The gap is that URLs to external systems currently remain mostly
plain links, so humans and agents must manually open them to understand
status, identity, and liveness.
> - This matters because external work objects such as GitHub issues and
pull requests are part of the operational state of a Paperclip company.
> - The implementation keeps core provider-neutral: shared contracts,
storage, sync, routes, and UI surfaces live in core while providers can
contribute detection and status resolution.
> - This pull request adds the external object reference foundation,
GitHub provider support, issue-surface rendering, filters,
sidebar/list/inbox signals, and test/story coverage.
> - The benefit is that linked external work becomes inspectable
Paperclip context without hardcoding every provider directly into the
UI.
## Linked Issues or Issue Description
No public GitHub issue exists for this work.
Feature request:
- Problem: URLs in Paperclip issues, comments, documents, and related
surfaces do not expose provider status or object identity inline.
- Proposed behavior: detect supported external object URLs, persist
normalized references, refresh provider status, and render concise
status-aware links across issue surfaces.
- Users affected: board users, agents, and maintainers who triage issues
containing external work links.
- Acceptance: external object references are company-scoped,
provider-extensible, visible in key issue surfaces, filterable where
relevant, and covered by focused shared/server/UI tests.
Related PR search:
- No open duplicate PRs found for `external object references`.
- Closed related prior attempt: #4556.
## What Changed
- Added shared external-object contracts, validators, status/liveness
helpers, and plugin protocol declarations.
- Added database schema and additive migrations for external objects,
source mentions, and display metadata.
- Added server services/routes for detecting, syncing, summarizing,
refreshing, and resolving external objects across issues, documents,
comments, projects, and plugins.
- Added a GitHub external-object provider plus plugin SDK authoring
docs.
- Wired UI presentation across markdown links, comments, issue chat,
documents, properties, related work, issue rows, filters, inbox/sidebar
badges, and Storybook stories.
- Rebasing cleanup: moved the branch onto current `master`, repaired
stale worktree provision config, hardened environment-sensitive
tests/mocks, and removed committed screenshot artifacts from the PR
branch to keep the reviewable file set below tool limits.
## Verification
- `pnpm exec vitest run packages/shared/src/external-objects.test.ts
server/src/__tests__/external-object-routes.test.ts
server/src/__tests__/external-objects-service.test.ts
ui/src/components/ExternalObjectPill.test.tsx
ui/src/lib/external-objects.test.ts` passed after rebasing: 5 files, 56
tests.
- Historical branch verification before this PR creation included `pnpm
test:run`, `pnpm -r typecheck`, and `pnpm build`; this PR body does not
claim those were rerun after the final rebase.
## Risks
- Medium: this adds a new cross-surface sync path on
issue/document/comment writes. The implementation uses safe sync
wrappers so external-object failures warn instead of blocking core
mutations.
- Medium: the migrations introduce new tables and indexes. They are
additive and company-scoped.
- Medium: provider-specific URL parsing can miss or misclassify edge
cases. Shared canonicalization tests and provider tests cover current
GitHub shapes.
- Low: UI badge/filter behavior could add visual noise for object-heavy
issues; component tests and Storybook stories cover the intended
surfaces.
> Roadmap checked: `ROADMAP.md` references the plugin system as the
current extension path and does not list a duplicate core feature.
Related long-range docs discuss external references, work products,
preview URLs, and plugin extension points; this PR implements the scoped
external-object reference foundation.
## Model Used
OpenAI Codex, GPT-5 coding-agent runtime, with shell and GitHub CLI tool
use. Reasoning mode: medium. Exact deployed runtime model ID and context
window were not exposed in the environment.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issue page is one of the main surfaces where operators read
agent updates and manage task relationships.
> - On narrow mobile viewports, agent chat bubbles kept the desktop
`max-w-[85%]` cap, leaving the right side overly indented.
> - The same issue sidebar can also become noisy when many blockers or
sub-tasks are attached to a task.
> - This pull request adjusts the mobile chat bubble width while
preserving the existing desktop cap.
> - It also limits long issue-relation previews until the operator
expands them, then allows collapsing them again.
> - The benefit is a denser, more readable mobile issue page without
changing the underlying issue data model.
## Linked Issues or Issue Description
No public GitHub issue exists for this internal report.
Bug report:
- On mobile issue pages, agent chat bubbles are unnecessarily narrow
because the desktop max-width applies at all breakpoints.
- Long blocked-by and sub-task relation lists can dominate the issue
properties area before the operator needs the full list.
- Related prior work: #4861 improved issue-thread scale and markdown
polish, but did not address this mobile bubble width regression.
## What Changed
- Added a responsive agent-comment bubble width class so mobile bubbles
use `max-w-[calc(100%-0.5rem)]` and desktop keeps `sm:max-w-[85%]`.
- Added test coverage for the responsive agent bubble classes.
- Added a five-item preview limit for blocked-by and sub-task relation
pills in issue properties.
- Added an inline `and N more...` / `show less` toggle for long
blocked-by and sub-task relation lists.
- Reset relation preview expansion when the `IssueProperties` panel
receives a different `issue.id`.
- Added component test coverage for collapsed, expanded, re-collapsed,
and issue-switch reset states.
## Verification
- Passed: `pnpm exec vitest run
ui/src/components/IssueProperties.test.tsx` (`27 passed`).
- Attempted: `pnpm exec vitest run
ui/src/components/IssueChatThread.test.tsx`.
- Result: failed before reaching the changed assertion because the
existing suite imports `act` from `react`, which resolves to a
non-function in this local Vitest environment.
- Scope: 50 render tests fail with `TypeError: act is not a function`; 8
non-render tests pass.
- Attempted: `pnpm --dir ui exec vitest run
src/components/IssueChatThread.test.tsx` with the same `act is not a
function` result.
- Passed: Greptile rerun reported Confidence Score 5/5 on commit
`e0f9c40b7faedcfb877d4f4b0e9f2c77a6593c16`.
- Passed: all latest-head PR checks/statuses are green, including
Typecheck + Release Registry, Build, general tests, serialized server
suites, e2e, canary dry run, policy, security review, Socket, Snyk, and
Greptile.
## Risks
Low risk:
- The chat change only alters max-width classes on agent comment bubbles
and preserves the desktop cap.
- The relation-list change is presentational; hidden blockers and
sub-tasks remain in memory and can expand/collapse in place.
- Expansion state now resets when navigating between issues in a reused
panel instance.
- Main residual risk is visual polish on real mobile devices because
local browser screenshot verification was not run in this heartbeat;
screenshots were not added because the source task explicitly requested
not adding screenshots/images unless specifically part of the work.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent based on GPT-5, with repository tool use,
shell execution, GitHub connector access, local Vitest verification,
Greptile follow-up, and PR check monitoring.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent configuration is where operators set identity, adapter
behavior, and execution environment defaults.
> - The environment override control should only appear when there is a
meaningful choice beyond the always-available local environment, or when
an existing saved override must remain visible so it can be inspected or
cleared.
> - The previous UI used an "Execution" header, explanatory helper copy,
and verbose inherited-default wording that made the agent config feel
noisier than necessary.
> - This pull request narrows the selector visibility to real
alternatives and forced Kubernetes mode, then updates the visible copy
to match the environment-focused mental model.
> - The benefit is a cleaner agent configuration surface that only asks
operators to make an environment choice when that choice exists.
## Linked Issues or Issue Description
No public GitHub issue exists for this change. Public GitHub searches
for related issues and PRs returned no matches:
- `agent config environment selector`
- `execution environment agent config`
Bug-style issue description:
What happened?
The agent config form could surface an environment override section even
when the operator had no meaningful non-local execution environment
choice. The section was labeled "Execution", included helper/inheritance
text, and described the inherited local default as "Inherit instance
default (Local)".
Expected behavior:
The selector should stay hidden unless there is more than one configured
environment choice, the section should be labeled "Environment", helper
text should be removed, and the inherited default option should read
like `Default: Local`. Existing saved non-local overrides should remain
visible even if the target environment is no longer runnable, so
operators can inspect or clear the stale selection.
Steps to reproduce:
1. Run Paperclip from `master` with environments enabled and only the
implicit local default, or Local plus one runnable non-local
environment.
2. Open an agent configuration form.
3. Inspect the environment override section visibility and copy.
Paperclip version or commit: `0b945f449` / current `master` before this
branch.
Deployment mode: Local dev (`pnpm dev`).
Installation method: Built from source.
Agent adapters involved: Not adapter-specific.
Database mode: Not database-related.
Access context: Board operator UI.
## What Changed
- Shows the environment override selector only for forced Kubernetes
mode, at least one runnable non-local environment, or an existing saved
non-local override.
- Keeps Local out of the runnable environment count so Local-only setups
do not show a redundant selector.
- Preserves stale saved non-local overrides in the selector options so
operators can see and clear them.
- Renames the section header from "Execution" to "Environment".
- Removes the helper/inheritance text above the selector.
- Changes the inherited option copy from `Inherit instance default
(...)` to `Default: ...`.
- Adds focused render tests for Local-only hiding, Local plus one
runnable non-local environment showing the concise selector copy, and
stale non-runnable override recovery.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/AgentConfigForm.render.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `git diff --check origin/master...HEAD`
- Checked `ROADMAP.md` for overlapping planned core work.
- Searched public GitHub issues and PRs for duplicate or related work;
no matches found.
## Screenshots
Before, when the selector was visible:
```text
Execution
Environment override
Inheriting the instance default: Local.
[Inherit instance default (Local)]
```
After, when Local plus a runnable non-local environment exists:
```text
Environment
Environment override
[Default: Local]
[E2B · sandbox]
```
After, when only Local is configured:
```text
(no Environment section is rendered)
```
## Risks
Low risk. This is isolated to the agent config UI. The main behavioral
risk is hiding the selector in an environment edge case; the current
branch keeps forced Kubernetes mode, runnable non-local environments,
and pre-existing saved non-local overrides visible. The added render
tests cover the Local-only, single non-local alternative, and stale
override cases.
## Model Used
OpenAI Codex local adapter, GPT-5-based Codex coding session. The exact
hosted backend model ID is not exposed in the runtime. Tool use and
local shell 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>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI has an experimental streamlined left navigation mode
that changes how projects and agents appear in the sidebar.
> - Today that mode is opt-in, so users keep seeing the classic
navigation unless they find and enable the experiment.
> - The requested product behavior is to make streamlined navigation the
default experience while keeping an explicit experiments opt-out.
> - This pull request flips the shared/server default, updates UI
consumers to treat only explicit `false` as classic mode, and covers
both the default-on path and opt-out behavior in tests.
> - The benefit is that new and legacy settings get the streamlined
sidebar by default without removing the classic-sidebar escape hatch.
## Linked Issues or Issue Description
Refs #7645
Related: #8430 takes the broader route of removing the classic sidebar.
This PR intentionally keeps the opt-out path.
## What Changed
- Default `enableStreamlinedLeftNavigation` to `true` in shared
validation and server-side normalization.
- Preserve explicit stored `false` as the experiments opt-out for the
classic sidebar.
- Render the sidebar and experimental settings toggle as streamlined-on
unless the setting is explicitly `false`.
- Add regression coverage for loading/default streamlined sidebar
behavior and the opt-out patch from the experiments page.
- Remove internal issue identifiers from newly touched source comments
before publishing.
## Verification
- `git diff --check origin/master...HEAD` — passed.
- `pnpm -r typecheck` — passed.
- `pnpm exec vitest run
server/src/__tests__/instance-settings-service.test.ts
ui/src/components/Sidebar.test.tsx
ui/src/pages/InstanceExperimentalSettings.test.tsx` — passed, 3 files /
27 tests.
- `pnpm build` — passed, with existing Vite/CSS/chunk-size warnings.
- `pnpm test:run` — failed in unrelated
`server/src/__tests__/workspace-runtime.test.ts`: the test `auto-detects
the default branch via symbolic-ref when origin/HEAD is set` creates a
temp repo on `main` then runs `git push -u origin main master`; `master`
does not exist in that temp repo. Summary: 1 failed, 214 passed, 1780
tests passed, 1 skipped.
## Risks
Low-to-medium behavioral risk: the default sidebar changes for users who
never explicitly set the experiment. Explicit `false` remains respected,
so users can still opt out via experimental settings.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 via Codex local adapter, with tool use and code execution.
Exact context window was not surfaced in the runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board issue thread composer is the subsystem involved here.
> - The composer can show both the mention coaching warning and the
handoff preview row near the send controls.
> - The handoff preview row sat flush against nearby composer UI, making
the warning and send-control area feel crowded.
> - This pull request adds vertical spacing around the visible handoff
preview row and avoids rendering a spacer when there is no preview.
> - The benefit is a cleaner, less cramped composer layout without
changing handoff behavior.
## Linked Issues or Issue Description
Refs: PAP-11281
Refs: PAP-11598
No public GitHub issue exists for this Paperclip task, so the issue is
described inline using the bug report template fields.
### What happened?
In the issue chat composer, the handoff preview row could render flush
against the mention coach warning above it and the send controls below
it.
### Expected behavior
When the handoff preview is visible, it should have a small amount of
vertical breathing room. When there is no preview, no empty wrapper
should add phantom margin.
### Steps to reproduce
1. Open an issue thread.
2. Type a reply that produces an active handoff preview near the mention
warning/send controls.
3. Inspect the composer spacing around the handoff preview row.
### Paperclip version or commit
Reproduced on the PAP-11281 work branch before this fix.
### Deployment mode
Local dev worktree.
## What Changed
- Added a `my-2` wrapper around `ComposerHandoffPreviewRow` in
`IssueChatThread`.
- Guarded the wrapper behind a named
`shouldRenderComposerHandoffPreview` helper so empty previews do not add
phantom vertical margin.
- Added a focused UI unit test covering the empty-preview and
visible-preview spacing guard.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/IssueChatComposerHandoffPreview.test.ts` passed locally.
- `pnpm --filter @paperclipai/ui typecheck` passed locally.
- `git diff --check` passed before the follow-up commit.
- Reviewed the one-file UI diff and focused test diff.
Screenshots: intentionally omitted for this tiny spacing-only branch
because PAP-11598 explicitly says not to add design screenshots or
images to this PR unless they are specifically part of the work. No
screenshot or wireframe image files are committed.
## Risks
Low risk. This is a narrowly scoped layout-only change in the composer.
The main risk is slightly different vertical rhythm around the handoff
preview row.
> 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, GPT-5-class model in tool-use/code-execution
mode. Exact hosted deployment identifier and context window were not
exposed in the runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Notes for draft status: remote CI and Greptile are re-running after the
follow-up push. The checklist records the required PR-ready criteria;
this PR should remain draft until GitHub reports those checks green.
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Companies are the primary tenant boundary for agents, board users,
settings, and portability operations.
> - CEO agents need limited same-company management powers, but those
powers must not cross into another company.
> - The affected company routes mixed route-level access checks with
validation middleware and repeated CEO checks, which left adjacent
mutation and portability surfaces easy to drift.
> - This pull request centralizes same-company CEO-or-board
authorization for those company-scoped surfaces.
> - It also adds regression coverage proving a CEO agent from one
company cannot read, mutate, archive, delete, export, or import against
another company.
> - The benefit is tighter company isolation without removing legitimate
same-company CEO operations.
## Linked Issues or Issue Description
Internal task: PAP-11205 / PAP-11347.
Bug report:
- What happened: same-company CEO authorization for company settings,
branding, and portability routes was implemented per-route, making it
possible for adjacent surfaces to drift and risking company-boundary
mistakes.
- Expected behavior: an agent API key must only manage the company that
owns the authenticated agent, and only CEO agents should get the limited
same-company management permissions.
- Steps to reproduce: authenticate as a CEO agent from Company A and
call Company B company routes such as `PATCH /api/companies/:companyId`,
`PATCH /api/companies/:companyId/branding`, export/preview export, safe
import preview/apply, archive, delete, or read.
- Version/commit: fixed on this branch at `45edaccb8` on top of
`public/master` `ddc193c2b`.
- Deployment mode: server API behavior; no UI change.
Related search results reviewed, not duplicates of this exact route
hardening:
- Refs #2212
- Refs #1083
- Refs #8053
## What Changed
- Added a shared `assertSameCompanyCeoAgentOrBoard` company route guard
and reused it for company settings, branding, export, and safe import
endpoints.
- Moved request parsing after authorization on sensitive company
mutation/export/import routes so unauthorized cross-company callers are
rejected before route body validation side effects or service calls.
- Tightened archive and delete ordering to assert route company access
before board-only mutation authorization.
- Added a cross-company company-route authorization regression suite
covering read, settings, branding, archive, delete, export, export
preview, import preview, and import apply paths.
- Extended adjacent route/service tests to prove non-CEO and
cross-company agent keys are rejected on the relevant company-scoped
surfaces.
- Addressed Greptile follow-ups by removing a redundant board assertion
and inlining the portability authorization wrapper.
## Verification
Local focused verification on rebased head `45edaccb8`:
- `pnpm exec vitest run
server/src/__tests__/companies-route-cross-company-authz.test.ts
server/src/__tests__/company-branding-route.test.ts
server/src/__tests__/company-portability-routes.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/authorization-service.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts`
- 6 test files passed
- 127 tests passed
Remote PR checks on rebased head `45edaccb8`:
- Paperclip PR workflow: green, including policy, typecheck, build, e2e,
canary dry run, workspace tests, server general tests, and all
serialized server shards.
- Greptile Review: success with 5/5 confidence on `45edaccb8`.
- Greptile review threads: all resolved.
## Risks
Low to moderate risk. This intentionally tightens company route
authorization and changes whether some unauthorized requests fail at the
authz layer before schema validation. Legitimate board users and
same-company CEO agents remain covered by tests, but callers that
depended on validation errors from unauthorized company routes will now
receive authorization errors first.
No migrations. No `pnpm-lock.yaml` changes. No `.github/workflows`
changes. No screenshots or design images added.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5.5-based coding agent with tool use, terminal
execution, repository inspection, and GitHub connector access.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots (N/A: no UI change)
- [x] I have updated relevant documentation to reflect my changes (N/A:
no docs needed beyond this PR description)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip planning skills turn approved plans into executable issue
graphs for agents
> - The executor starts assigned tasks that have no open blockers
> - Parent/child nesting gives structure, but it does not create
execution dependencies
> - Planning guidance that does not make this distinction can let
dependent tasks run too early
> - This pull request clarifies that hard dependencies must be encoded
as `blockedByIssueIds`
> - The benefit is that agents converting plans into tasks have a
clearer checklist for creating and verifying runnable task graphs
## Linked Issues or Issue Description
No public GitHub issue was found for this documentation update.
Problem:
- The planning guidance could be read as if hierarchy, phase order, or
prose blocker notes were enough to sequence execution.
- In Paperclip, only explicit blockers prevent an otherwise-ready
assigned task from starting.
Expected behavior:
- Skill guidance should tell planners to encode each hard dependency as
`blockedByIssueIds` on the dependent issue.
- It should also tell planners to verify the created issue graph before
closing the source planning issue.
## What Changed
- Clarified that parent/child issue nesting is structural and not an
execution blocker.
- Added guidance to build a compact task matrix with task, owner,
initial status, and blockers when converting accepted plans into tasks.
- Added verification guidance to re-fetch or otherwise inspect created
issues before marking the source planning issue done.
- Updated the bundled task-planning skill handoff text to point at the
same dependency expectations without duplicating the full companion
skill text.
## Verification
- Ran `git diff --check origin/master...HEAD` locally.
- Rebased cleanly onto the latest `master` from `paperclipai/paperclip`
before pushing.
- Greptile completed at 5/5 on the latest head; the previous P2 wording
thread was fixed and resolved.
- GitHub PR checks are green on latest head
`2b07db9aef22ca32c65fa66fcca2db86c5b01220`, including policy,
commitperclip review, typecheck/release registry, general test shards,
build, serialized server suites, e2e, canary dry run, verify, Socket,
security-review, Snyk, and Greptile.
- No additional local unit test command was run because this is a
documentation/skill-text-only change and the remote PR suite covered the
repository gates.
## Risks
Low risk. This changes skill guidance only; it does not alter runtime
behavior, database schema, migrations, workflows, or UI code.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5-based coding agent in the Paperclip local adapter
runtime. Exact backend model ID and context window were not exposed by
the runtime. Used shell, git, GitHub CLI, and the GitHub connector for
repository inspection and PR creation.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Fixes#7578
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Blocked issue health is surfaced through blockerAttention so
operators can see whether blocked work has a live path.
> - The blockerAttention graph uses both explicit blockedBy edges and
direct child issue edges.
> - Done children were already ignored, but cancelled direct children
still appeared as unresolved blockers.
> - Explicit cancelled dependencies should remain visible as dependency
problems, but terminal direct children should not inflate a parent
blocker count.
> - This pull request narrows child-edge traversal to non-terminal
children and adds a regression test for the observed case.
> - The benefit is that cancelled child issues no longer make blocked
parents look like they have extra unresolved blocker attention.
## What Changed
- Added terminal child status filtering for blockerAttention
parent-child traversal so cancelled direct children are ignored with
done children.
- Added a server regression test where a blocked parent has active
explicit blockers plus a cancelled direct child; the cancelled child no
longer increases unresolved counts or becomes the sample blocker.
## Verification
- `perl -e 'alarm shift; exec @ARGV' 300 pnpm exec vitest run
server/src/__tests__/issue-blocker-attention.test.ts` -> 19 tests
passed.
## Risks
- Low risk: the change only affects implicit direct-child
blockerAttention edges.
- Explicit blockedBy edges to cancelled issues are intentionally
unchanged and remain represented as attention-required dependency
problems.
## Model Used
- OpenAI GPT-5, Codex coding agent in tool-enabled CLI environment;
reasoning and code execution used for repository inspection, patching,
git operations, and targeted test verification.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots (not applicable; no UI change)
- [x] I have updated relevant documentation to reflect my changes (not
applicable; bugfix covered by regression test)
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The `codex_local` adapter spawns the Codex CLI for an agent, and
`server/src/routes/agents.ts` normalizes each agent's
`adapterConfig.env` on create/hire/update
> - PR #8272 added an isolation guard that, on every codex_local agent,
force-set a per-agent `CODEX_HOME` and injected `OPENAI_API_KEY = ""`,
and rejected any "shared" home
> - This broke the common case: operators who deleted `CODEX_HOME` /
`OPENAI_API_KEY` in the UI saw them silently re-appear on save, and
every agent was forced into an isolated home instead of sharing the
device's existing Codex login (`~/.codex` / `$CODEX_HOME`)
> - This pull request replaces the always-on isolation guard with
key-scoped isolation: a keyless agent gets no env overrides and inherits
the host Codex login at runtime; we only carve out an isolated per-agent
`CODEX_HOME` when the agent sets its own `OPENAI_API_KEY`
> - The benefit is that env-var deletion now persists, agents on one
device share the host login by default, and per-account isolation is
still available by setting a per-agent key
## Linked Issues or Issue Description
No public GitHub issue exists. Bug report (following `bug_report.yml`):
**What happened?** In a `codex_local` agent's configuration, removing
the `CODEX_HOME` and `OPENAI_API_KEY` env vars via the UI (clicking the
X) appears to work, but on save they instantly re-appear. The persisted
config never loses the slots.
**Root cause.** `applyCodexLocalIsolationGuard` in
`server/src/routes/agents.ts` (added in #8272) re-injected `CODEX_HOME`
(defaulting to a per-agent home) and `OPENAI_API_KEY = ""` on every
create/hire/update, and rejected shared homes outright. So a PATCH that
omitted those keys had them written back server-side.
**Expected behavior.** Deleting these env vars should persist. A
codex_local agent with no key should inherit whatever Codex login
already exists on the device.
**Steps to reproduce.**
1. Open a `codex_local` agent's configuration with `CODEX_HOME` and
`OPENAI_API_KEY` set.
2. Remove both env vars and save.
3. Re-open the config — both slots are back.
Related (different approach): Refs #8399 (keeps per-agent isolation,
fixes only the empty `OPENAI_API_KEY` slot), Refs #8272 (introduced the
guard), Refs #8403 (managed-auth seeding into isolated homes).
## What Changed
- `server/src/routes/agents.ts` — replaced
`applyCodexLocalIsolationGuard` (+ `assertCodexLocalHomeIsNotShared` /
`normalizeCodexLocalHomePath`) with `applyCodexLocalKeyIsolation`. A
codex_local agent now receives **no** env overrides unless it explicitly
sets `OPENAI_API_KEY`; only then is an isolated per-agent `CODEX_HOME`
injected (and only if the agent has not set its own `CODEX_HOME`).
Keyless agents fall back to the host Codex login at runtime. Dropped the
now-unused `node:os` import and the shared-home rejection.
- `server/src/__tests__/agent-adapter-validation-routes.test.ts` —
updated the validation-route tests to assert env-var deletion persists,
keyless agents get no overrides, and key-bearing agents still get an
isolated `CODEX_HOME`.
## Verification
```bash
cd server && npx vitest run src/__tests__/agent-adapter-validation-routes.test.ts
```
All 6 tests pass locally. Manually verified in the live UI: removing
`CODEX_HOME` and `OPENAI_API_KEY` from a codex_local agent and saving
now persists the deletion (slots no longer re-appear on reload).
## Risks
- **Host-key leak for keyless agents on a host with `OPENAI_API_KEY`
set.** Low/intended: the product direction here is that codex_local
agents inherit the host's Codex login by default; per-account isolation
is opt-in via a per-agent `OPENAI_API_KEY`, which then gets its own
`CODEX_HOME`.
- **Divergence from #8399.** That PR retains the per-agent isolation
default and only stops injecting the empty key, so it does not address
the `CODEX_HOME` re-injection half of this bug. This PR intentionally
changes the default to host-login inheritance. Reviewers should pick one
direction.
- **No migration.** Existing agents that already store these slots are
not auto-cleaned, but operators can now delete them and the deletion
sticks.
## Model Used
- Provider: Anthropic (Claude)
- Model ID: `claude-opus-4-8`
- Capabilities: tool use, code execution, extended reasoning, agentic
workflow
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [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
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandbox and SSH runtimes need to preserve agent work across isolated
execution environments
> - Git-backed workspaces were being copied mostly as filesystem
archives, which breaks when `.git` points outside the mounted workspace
and makes sandbox agents unable to publish their own branches
> - Large ignored dependency trees could also be swept into the sandbox
overlay, causing multi-GB transfers and max-string failures in some
sandbox clients
> - This pull request makes sandbox runtime setup use a git-backed HEAD
sync plus a small dirty/untracked overlay, and bounds sandbox file
transfers so large archives do not need one huge string
> - The benefit is that sandbox agents can commit and push from a usable
git checkout without uploading dependency trees such as `node_modules`
## Linked Issues or Issue Description
Refs #8395
No public duplicate issue or PR was found after searches for `sandbox
git workspace`, `git push sandbox`, and `node_modules sandbox upload`.
Bug report:
- What happened: sandbox-backed agent workspaces could receive a `.git`
file that pointed at host-only git state, leaving the sandbox unable to
run normal git workflows. The sandbox overlay upload could also include
ignored dependency directories, creating very large transfers.
- Expected behavior: sandbox and remote runtimes should prepare a usable
git-backed workspace, copy only the necessary workspace overlay, and
restore git history plus file changes without depending on a host-only
`.git` path.
- Steps to reproduce:
1. Run an agent in a sandbox-backed workspace whose local git checkout
is a worktree.
2. Ask the agent to complete a GitHub workflow that requires commit/push
access.
3. Observe that git operations can fail inside the sandbox, and ignored
dependency trees can be uploaded as part of the workspace overlay.
- Paperclip version or commit: reproduced against `master` before this
PR, base `7aa212296eb1`.
- Deployment mode: local dev / sandbox-backed runtime.
- Installation method: built from source.
- Agent adapters involved: local adapters using shared adapter-utils
runtime preparation.
- Database mode: not database-related.
- Access context: agent runtime.
- Local verification environment: Node.js v25.6.1, pnpm 9.15.4, macOS
arm64.
- Privacy checklist: all pasted output was reviewed for secrets, private
hostnames, local usernames, and internal instance links.
## What Changed
- Added a GitHub workflow push preflight so agent runs can detect
missing push credentials when a workflow explicitly needs GitHub
publishing.
- Added shared git workspace sync helpers for shallow HEAD import/export
and dirty/untracked overlay tracking.
- Updated sandbox managed runtime setup to use git history plus a
selected overlay instead of uploading the full local workspace for
git-backed workspaces.
- Bounded sandbox archive upload/download paths so large payloads stream
or chunk instead of materializing one oversized string.
- Excluded `.git` and ignored dependency trees from sandbox upload,
download, and restore baselines while preserving local ignored
directories during sync-back.
- Added focused tests for git workspace sync, sandbox overlay selection,
transfer chunking, and heartbeat push-preflight behavior.
## Verification
- `pnpm exec vitest run
packages/adapter-utils/src/git-workspace-sync.test.ts
packages/adapter-utils/src/sandbox-managed-runtime.test.ts
packages/adapter-utils/src/command-managed-runtime.test.ts
server/src/__tests__/heartbeat-project-env.test.ts
server/src/__tests__/heartbeat-workspace-session.test.ts`
- `pnpm --filter @paperclipai/adapter-utils typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm build`
- Public-hygiene scan of the PR diff and commit messages for internal
issue ids, local paths, private hostnames, and obvious token patterns.
## Risks
- Medium risk: this changes sandbox runtime synchronization semantics
for git-backed workspaces, especially around dirty tracked files,
untracked files, deleted paths, and ignored files.
- The main mitigation is focused test coverage for upload contents,
restore exclusions, and git round-trip behavior.
- The SSH runtime keeps the current bundle-based implementation from
`master`; this PR only aligns shared excludes and sandbox behavior with
that model.
## Model Used
OpenAI Codex, GPT-5-based coding agent, tool-enabled shell/git/GitHub
workflow, with code execution and repository inspection.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Local adapters (Claude, Codex, Cursor, Gemini, Grok, OpenCode, Pi,
ACPX) ship bundled "skills" — opinionated Markdown prompt bundles
materialized into the agent's runtime
> - One of those bundled skills, `paperclip-dev`, existed to let agents
develop Paperclip itself; it has now moved to its own external repo and
no longer belongs in the core tree
> - The adapter skill model also carried a `required` / `requiredReason`
attribute plus a `paperclip_required` `AdapterSkillOrigin` variant, all
of which only existed to mark bundled skills as non-optional in the UI
and adapter sync logic
> - With `paperclip-dev` gone, no bundled skill is "required" anymore,
and the type / runtime surface for `required` is dead weight — but it is
computed at request time and never persisted, so a clean removal is safe
(no compatibility shim needed)
> - This pull request deletes `skills/paperclip-dev/` and removes every
trace of the `required` / `requiredReason` field and the
`paperclip_required` origin across shared types, validators,
adapter-utils, all eight local adapters, server routes, the
company-skills service, the UI, the storybook fixtures, and the test
suite
> - The benefit is a smaller, simpler adapter-skill surface: one origin
(`company_managed`) for managed bundled skills,
`resolvePaperclipDesiredSkillNames` collapses to "just the configured
desired set", and the AgentDetail skills tab no longer renders a
"Required by Paperclip" section that no longer applies
## Linked Issues or Issue Description
<!-- No existing public GitHub issue; describing the underlying work
inline (feature_request template fields). -->
**Summary**
Remove the bundled `paperclip-dev` skill (now maintained in its own
external repo) and retire the `required` / `requiredReason` skill
attribute and the `paperclip_required` skill origin, which only existed
to support it.
**Problem or motivation**
`paperclip-dev` is the only bundled skill that was ever marked
"required". Now that it lives in a separate repository, shipping it
inside the core tree is wrong, and the entire `required` surface (a type
field, a validator field, a synthesized `paperclip_required` origin, UI
"Required by Paperclip" section, and required-skill merging in the
desired-skills calculation) becomes dead weight. The `required` value is
computed at request time and never persisted, so it can be removed
cleanly without a migration or compatibility shim.
**Proposed solution**
Delete `skills/paperclip-dev/`, drop the `required` / `requiredReason`
fields and `paperclip_required` origin everywhere they are produced or
consumed, collapse managed-skill origin to a single `company_managed`
value, and simplify `resolvePaperclipDesiredSkillNames` to return only
the configured desired set.
**Alternatives considered**
Keeping the `required` attribute as a no-op for forward compatibility —
rejected because it is request-time only (nothing persists it), so
leaving it in place is pure dead surface area with no callers.
**Roadmap alignment**
Internal cleanup / dead-code removal that simplifies the adapter-skill
surface; it does not introduce or duplicate any planned core feature in
ROADMAP.md.
## What Changed
- Deleted bundled `skills/paperclip-dev/` (moved to a separate repo).
- Dropped `required`, `requiredReason`, and the `paperclip_required`
origin from `packages/shared/src/types/adapter-skills.ts`,
`packages/shared/src/validators/adapter-skills.ts`, and
`packages/adapter-utils/src/types.ts`.
- In `packages/adapter-utils/src/server-utils.ts`: removed
`readSkillRequired()`; dropped `required`/`requiredReason` from
`listPaperclipSkillEntries()`,
`normalizeConfiguredPaperclipRuntimeSkills()`,
`buildPersistentSkillSnapshot()`, and `PaperclipSkillEntry`; collapsed
`buildManagedSkillOrigin()` to always return `company_managed`;
simplified `resolvePaperclipDesiredSkillNames()` to return only the
configured desired set (signature preserved so adapter call sites are
untouched).
- Walked all eight local adapters (`acpx-local`, `claude-local`,
`codex-local`, `cursor-local`, `gemini-local`, `grok-local`,
`opencode-local`, `pi-local`) and removed every remaining
`requiredReason` / `paperclip_required` reference.
- `server/src/services/company-skills.ts`: dropped the `required =
sourceKind === "paperclip_bundled"` synthesis when listing runtime skill
entries.
- `server/src/routes/agents.ts`: removed required-skill merging from the
desired-skills calculation in the persist-config path and the
unsupported-snapshot path (keeping the current version-aware
`desiredSkillEntries` structure).
- `ui/src/pages/AgentDetail.tsx`: dropped required-based filters, the
required tooltip, and the entire "Required by Paperclip" section from
the agent skills tab; storybook fixtures in
`ui/storybook/stories/acpx-local.stories.tsx` cleaned up to match.
- Tests: deleted the `required: false` case in
`paperclip-skill-utils.test.ts` and the "keeps required bundled skills
installed" case in every `*-local-skill-sync.test.ts`;
`acpx-local-execute.test.ts`, `cursor-local-execute.test.ts`,
`cursor-local-skill-sync.test.ts`, `agent-skills-routes.test.ts`, and
`packages/adapter-utils/src/server-utils.test.ts` were updated to drop
removed fields and map `origin: "paperclip_required"` →
`"company_managed"`.
- `server/src/adapters/registry.ts`: two `as unknown as
ServerAdapterModule["..."]` casts on `hermesListSkills` /
`hermesSyncSkills` (matching the existing `executeHermesLocal` pattern).
`hermes-paperclip-adapter@0.2.0` still depends on the published
`@paperclipai/adapter-utils` which keeps the retired
`paperclip_required` variant; the cast bridges the
workspace-vs-published type mismatch at the registry seam and can drop
once hermes upgrades.
## Verification
Run from the workspace root:
```sh
grep -rn "skills/paperclip-dev" .
grep -rn "paperclip_required" --include="*.ts" --include="*.tsx" .
grep -rn "requiredReason" --include="*.ts" --include="*.tsx" .
pnpm -w typecheck
pnpm --filter @paperclipai/server exec vitest run paperclip-skill-utils
pnpm --filter @paperclipai/server exec vitest run skill-sync
```
The first three greps return only the explanatory comment in
`server/src/adapters/registry.ts` (no live `paperclip_required` /
`requiredReason` usage) and zero `skills/paperclip-dev` source hits.
Locally:
- `pnpm -w typecheck` → all packages this PR touches pass
(adapter-utils, shared, server, ui, cli, and the
cursor/gemini/opencode/pi adapters).
- Affected vitest suites pass: `paperclip-skill-utils`, `server-utils`,
all eight `*-local-skill-sync`, `agent-skills-routes`, and the
`acpx`/`cursor`/`pi` execute suites.
## Risks
- Behavioral shift in the agent skills UI: the "Required by Paperclip"
section disappears. No bundled skill is required anymore, so this only
affects environments that previously surfaced `paperclip-dev` as a
forced-on row; those installs will see the skill move into the regular
"company-managed" list (and be uninstalled on next sync unless
explicitly listed as desired).
- Existing agents may still have the string `"paperclip-dev"` in their
persisted `desiredSkills`. That entry is inert (no source for it to
install from); a one-time DB cleanup is out of scope. Low risk.
- Hermes adapter type bridge: two casts in `registry.ts` paper over a
type-only divergence between the workspace `@paperclipai/adapter-utils`
and the published version still pinned by
`hermes-paperclip-adapter@0.2.0`. Runtime behavior is unaffected because
the retired `paperclip_required` value is no longer produced by anything
in this tree. The casts can be removed once hermes upgrades its
dependency.
## Model Used
- Provider: Anthropic
- Model: Claude Opus 4.7 (`claude-opus-4-7`)
- Capability: agent tool use via Paperclip's `claude_local` adapter
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The `codex_local` adapter runs `codex exec` as a child process and
streams JSONL events from its stdout
> - NEE-79 caught a real-world `codex_local` orphan: codex sat in
`read()` on stdin for 1h+, no JSON events emitted past startup, no LLM
call in flight. The adapter had no inactivity timer; the only safety net
was the platform-level 1h silent-run detector
> - This is precisely the failure shape NEE-80 scoped: an
adapter-detected fault that should be killed *by the adapter* and
surfaced as `failed`, not waited out for an hour
> - This pull request adds an output-inactivity watchdog inside the
codex-local adapter that resets on every parsed JSONL event from stdout,
kills the child via SIGTERM → 5s grace → SIGKILL when it fires, and
resolves the run with a structured watchdog failure
> - The benefit is that NEE-79-class hangs shorter than 1h stop reaching
the platform-level safety net — they fail fast and visibly at the
adapter layer, with diagnostic logs that don't require host shell access
## Linked Issues or Issue Description
No existing GitHub issue covers this; describing the underlying bug
in-PR per the bug report template
(`.github/ISSUE_TEMPLATE/bug_report.yml`):
- **What happened?** A `codex_local` run sat in `read()` on stdin for
over an hour: codex emitted its startup JSONL events, then nothing — no
further events, no LLM call in flight, no exit. The adapter kept the
child alive indefinitely; the run was only reaped by the platform-level
1h silent-run safety net, an hour after it had effectively died.
- **Expected behavior:** The adapter should detect that its child has
stopped producing output long before the platform-level safety net, kill
it, and surface the run as `failed` with a diagnostic that explains what
happened.
- **Steps to reproduce:** Run any `codex_local` issue where `codex exec`
hangs after startup (e.g. codex blocks reading stdin and never emits
another JSONL event). Observe the run stays alive until the 1h platform
safety net fires.
- **Paperclip version or commit:** reproduced on master prior to this
branch.
- **Agent adapter(s) involved:** codex_local.
Related PRs found while searching for duplicates (none implement an
event-aware inactivity watchdog inside `codex_local`):
- #4004 — generic idle + wall watchdogs for `runChildProcess` in
adapter-utils; complementary, operates below the JSONL parse layer and
is not codex-event-aware
- #4742 — fail-fast on codex-local *startup* hang; this PR covers the
post-startup hang class
- #6861 — zombie-run termination for codex-local; reaping after exit,
not inactivity detection
- #7811 — the analogous output-idle timeout for grok-local
## What Changed
- `packages/adapters/codex-local/src/server/watchdog.ts` *(new)* — pure
watchdog primitive with injectable timers/clock:
`resolveCodexInactivityTimeout`, `createCodexInactivityWatchdog`,
`formatWatchdogErrorMessage`. Default `7 * 60_000` ms; honors `null` as
the disabled escape hatch
- `packages/adapters/codex-local/src/server/execute.ts` — `runAttempt`
now wraps `onSpawn` to capture `pid`/`processGroupId`, feeds stdout
chunks through `noteStdoutChunk`, and on watchdog fire sends SIGTERM to
the process group, schedules SIGKILL after 5s, and returns an
`AdapterExecutionResult` with `exitCode: null`, `signal:
SIGTERM|SIGKILL`, `errorMessage: "watchdog: no codex output for {N}m
{S}s"`, `errorCode: "codex_output_inactivity_watchdog"`. With
`errorMessage` set and `timedOut: false`, `heartbeat.ts:5860` maps the
run to `outcome === "failed"` (not `cancelled`)
- `packages/adapters/codex-local/src/index.ts` — `agentConfigurationDoc`
documents `outputInactivityTimeoutMs` (number ms; `null` disables;
non-positive falls back to default with a warning log at spawn)
- `packages/adapters/codex-local/src/server/watchdog.test.ts` *(new)* —
13 tests covering acceptance criteria 2 and 3 plus supporting cases:
fires after silence, no-fire across 12× (threshold − 1s) cycles,
multi-event chunks, non-JSON ignoring, single-fire idempotency,
formatter shape, full resolution table, `null` → disabled
-
`packages/adapters/codex-local/src/server/watchdog.integration.test.ts`
*(new)* — real Node subprocess that prints one JSONL event then sleeps;
`runChildProcess` reaps it within `threshold + 6s`; signal is SIGTERM or
SIGKILL; `parsedEventCount === 1` (acceptance criteria 1 and 4)
## Verification
```
$ pnpm --filter @paperclipai/adapter-codex-local typecheck
> tsc --noEmit
# clean
$ pnpm --filter @paperclipai/adapter-codex-local exec vitest run
✓ src/server/quota-spawn-error.test.ts (1 test)
✓ src/server/codex-home.test.ts (3 tests)
✓ src/server/codex-args.test.ts (3 tests)
✓ src/server/watchdog.test.ts (13 tests)
✓ src/server/parse.test.ts (9 tests)
✓ src/server/execute.remote.test.ts (4 tests)
✓ src/ui/parse-stdout.test.ts (3 tests)
✓ src/ui/build-config.test.ts (1 test)
✓ src/server/watchdog.integration.test.ts (1 test)
Test Files 9 passed (9)
Tests 38 passed (38)
```
Acceptance criteria check:
1. ✅ Simulated child emits one event then sleeps → killed at threshold;
result `errorMessage` matches `watchdog: no codex output for {N}m {S}s`
(`watchdog.integration.test.ts`)
2. ✅ Child emits events every (threshold − 1s) → not killed
(`watchdog.test.ts: "does not fire when events arrive every (threshold -
1s)"`)
3. ✅ `outputInactivityTimeoutMs: null` disables the watchdog
(`resolveCodexInactivityTimeout` returns `disabled`; `execute.ts` skips
watchdog construction and logs a startup warning)
4. ✅ Real subprocess reaped well within `threshold + 6s` — 250 ms
threshold, 290 ms wall clock in CI
5. ✅ Adapter-level fault → `outcome === "failed"` per
`heartbeat.ts:5860`. NEE-79-class hangs <1h get caught at the adapter,
not the platform-level safety net
Post-rebase verification (head `2a8703fab`, rebased onto master
`69a368ed5`):
```
$ pnpm --filter @paperclipai/adapter-codex-local typecheck # clean
$ pnpm --filter @paperclipai/adapter-codex-local exec vitest run
Test Files 11 passed (11)
Tests 64 passed (64)
```
## Risks
- Low risk. Behind a default-on watchdog that only fires after 7m of
zero parsed JSON events. Operators can disable it with
`outputInactivityTimeoutMs: null` for known-slow tasks
- The kill path reuses the same `process.kill(-pgid, signal)` pattern
that `runChildProcess` already uses for its terminal-result cleanup, so
signal semantics match the existing code path
- `timedOut: false` is preserved on watchdog fire — the platform-level
timeout outcome is unchanged, only the `failed`-vs-success
classification flips. No behavioral shift for already-failing runs
- Sandbox/SSH execution targets: the watchdog fires and emits the
structured log, but the kill is best-effort because remote pids aren't
owned by this process. The platform-level 1h safety net still applies.
Out of scope for NEE-81 by design
## Model Used
- Provider: Anthropic Claude
- Model: claude-opus-4-7
- Mode: Claude Code (extended thinking, tool use)
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots (N/A — no UI changes)
- [x] I have updated relevant documentation to reflect my changes
(`agentConfigurationDoc` for `outputInactivityTimeoutMs`)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (re-running on the rebased head)
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(P2 addressed; review threads resolved)
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Neeraj Kumar Singh <b.nirajkumarsingh@hotmail.com>
Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agent work runs inside git worktrees created by the workspace
runtime, each branched from a configured base ref (typically the repo's
default branch)
> - When the local `master` is stale or ahead of `origin/master`
(committed but unpushed work, or local-only commits), a freshly created
worktree inherits that divergence — so an unrelated task branch silently
carries commits it never intended to touch
> - This surfaced as a docs-only task whose PR accidentally pulled in
unrelated changes from a diverged local master
> - The base for a fresh worktree should be resolved authoritatively to
the remote-tracking ref (`origin/<branch>`), and an idle/unstarted
reused worktree should be safely fast-forwarded — without ever
destroying in-progress work
> - This pull request makes both behaviors explicit in the workspace
runtime
> - The benefit is that task branches start from a clean, authoritative
base, eliminating accidental inclusion of unrelated local changes
## Linked Issues or Issue Description
This is a bug fix. No public GitHub issue exists, so describing it
inline following the Bug Report template:
**What happened**
A task intended to change only docs produced a PR that also contained
unrelated changes pulled in from `master`.
The root cause: when a new worktree is created from a configured local
branch (e.g. `master`), the worktree inherits whatever that local branch
points at. If the local `master` has committed divergence from
`origin/master` (unpushed or local-only commits), that divergence leaks
into the new task branch. The leak comes from committed
local-vs-`origin/master` ref drift, not uncommitted working-tree changes
(each worktree has its own working tree).
**Expected behavior**
A fresh worktree should be based on the authoritative `origin/master`
head so unrelated local commits never seed a task branch.
**Steps to reproduce**
1. Have a local `master` that is ahead of `origin/master` (committed but
unpushed work).
2. Create a new worktree/task branched from `master` via the workspace
runtime.
3. Open a PR from that branch — it carries the unrelated local commits.
**Deployment mode**
Self-hosted / local workspace runtime.
## What Changed
- Fresh worktrees now resolve their base ref authoritatively: a
configured local branch (e.g. `"master"`) is mapped to its
`origin/<branch>` remote-tracking counterpart so unpushed/ahead local
commits can never seed a task branch. Remote-tracking refs, SHAs, and
tags are used verbatim; an unset/`HEAD` base falls back to the detected
default branch. The resolved ref is recorded (`repoRef`) so downstream
drift checks stay accurate.
- If a configured local branch has no matching `origin/<branch>`, the
runtime warns and falls back to the local ref rather than failing.
- On reuse, a *provably unstarted* worktree (no commits past base +
clean tree including untracked files) is fast-forwarded to the latest
`origin/master`. Started or dirty worktrees keep the prior warn-only
behavior, so in-progress work is never reset. Only remote-tracking bases
are eligible for the refresh.
## Verification
- `cd server && npx vitest run src/__tests__/workspace-runtime.test.ts`
- 5 new tests cover: local-branch→`origin/<branch>` mapping, no-remote
fallback warning, unstarted-reuse fast-forward, and that started/dirty
worktrees are left untouched.
- Result: 65 passed. 1 pre-existing failure (`auto-detects the default
branch via symbolic-ref when origin/HEAD is set`) is unrelated to this
change and fails only due to the test host's git default-branch config
(test setup runs `git push -u origin main master` but the local default
branch is `main`); it also fails on `master`.
## Risks
- Low risk. The refresh path is intentionally conservative: it only
fast-forwards worktrees that are provably unstarted (zero commits past
base and a fully clean tree, including untracked files) and only when
the base is a remote-tracking ref. Started or dirty worktrees fall
through to the existing warn-only drift behavior, so no in-progress work
can be destroyed.
- Behavioral shift: fresh worktrees configured against a local branch
will now base on `origin/<branch>` instead of the local ref. This is the
intended fix; the only case it changes is when local and remote have
diverged.
## Model Used
Claude — `claude-opus-4-8` (extended thinking, tool use / code execution
via Claude Code).
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots (N/A — no UI changes)
- [x] I have updated relevant documentation to reflect my changes (N/A —
no doc changes needed)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending CI run)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Environments are configured through a JSON-schema-driven form
(`JsonSchemaForm`), and each sandbox provider (here, Daytona) supplies a
manifest describing its fields
> - In the Daytona environment config form, the "memory" field rendered
as a free-text input; on save the value round-tripped to `0`, producing
a server-side "number must be greater than zero" error even when the
user typed a valid number like `2`
> - Rather than patch the free-text input, memory is now a true dropdown
of the sandbox sizes Daytona actually supports, so an invalid value can
no longer be typed or coerced
> - The dropdown leads with a blank "None" row that is selected by
default (meaning "not configured — use Daytona's defaults"); `0` is
never offered because it is not a valid configuration
> - The benefit is that operators pick a valid memory size from a
constrained list, it saves correctly as an integer, and leaving it unset
cleanly omits the field instead of submitting `0`
## Linked Issues or Issue Description
No public GitHub issue exists; describing the bug inline per the bug
report template.
### What happened?
In the Daytona environment configuration form, the memory field is a
free-text input labelled "gigabytes of RAM". Entering a value such as
`2` and saving coerced the value to `0`, and the server rejected the
save with "the number must be greater than zero". There was no way to
enter a valid memory size through the form.
### Expected behavior
Selecting a valid memory size (e.g. `2`) keeps that value and saves
cleanly as an integer. Leaving memory unset is valid and submits no
value (Daytona defaults apply). `0` is never selectable.
### Steps to reproduce
1. Open the environment configuration form for a Daytona sandbox
provider.
2. In the memory field, type `2`.
3. Click Save.
4. Observe the value becomes `0` and the form errors with "the number
must be greater than zero".
## What Changed
- Daytona manifest: `memory` is now an `enum` of the supported sandbox
sizes `[1, 2, 4, 8]` (GiB). It stays optional, so "not configured"
remains valid. `0` is not in the list.
- `JsonSchemaForm` `EnumField`: optional enums now render a leading
blank **None** row that is selected by default when no value is set,
letting the user express "not configured" and clear a previous selection
(Radix `Select` forbids an empty-string item value, so the unset state
maps to a sentinel that translates back to `undefined`).
- `JsonSchemaForm` `EnumField`: when every enum option is numeric, the
selected value is coerced back to a number on change so the payload
keeps the schema's integer type (a stringified `"2"` would otherwise
fail server-side integer validation — this is what fixes the original
bug for the dropdown path).
- Tests: added `EnumField` coverage in `JsonSchemaForm.test.tsx` (blank
row present, no `0`, blank selected by default, numeric coercion, blank
→ unset) and Daytona manifest coverage in the plugin test (memory enum
is `[1,2,4,8]`, excludes `0`, stays optional).
## Verification
- `cd ui && npx vitest run src/components/JsonSchemaForm.test.tsx
src/pages/CompanyEnvironments.test.tsx` → 16/16 passing (14 + 2).
- `vitest run` in `packages/plugins/sandbox-providers/daytona` → 19/19
passing (incl. 3 new manifest tests).
- `cd ui && npx tsc --noEmit` → clean.
- Behavior: the memory field now renders as a dropdown showing **None /
1 / 2 / 4 / 8**, with **None** selected by default. Picking `2` stores
the integer `2`; picking **None** clears the field so it is omitted from
the payload (no `0`, no "must be greater than zero" error).
## Risks
- Low risk. The blank-row + numeric-coercion changes live in
`EnumField`. The blank row is only added for **optional** enums
(required enums are unaffected); numeric coercion only triggers when
every option is numeric, so existing string enums (`egressMode`,
`backend`, `sessionStrategy`) are unchanged. The manifest change is
scoped to the Daytona provider.
## Model Used
Claude (Anthropic), Opus-class model, used via the Paperclip agent
workflow (author + reviewer agents) with tool use and code execution.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (none found)
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Contributors follow `CONTRIBUTING.md` and the PR template when
opening pull requests
> - Those docs still tell contributors to manually capture and attach
before/after screenshots for UI changes
> - That requirement is now redundant: the `cutter.sh` bot automatically
captures and posts before/after UI screenshots to every PR
> - Asking contributors to also do it by hand adds friction and produces
duplicate screenshots
> - This pull request removes the manual screenshot obligation from
`CONTRIBUTING.md` and the PR template, and documents that `cutter.sh`
handles it
> - The benefit is a clearer, lower-friction contribution flow that
matches how screenshots actually get posted today
## Linked Issues or Issue Description
No public GitHub issue. Underlying change:
Our contributor docs and PR template instructed contributors to attach
before/after screenshots for any UI change. We now run a bot
(`cutter.sh`) that automatically captures and posts before/after UI
screenshots to the PR, so the manual step is no longer needed. This
change removes the manual requirement and instead documents that the bot
handles screenshots, asking contributors to simply describe the visible
change.
## What Changed
- `CONTRIBUTING.md`: removed the "Before / After screenshots"
requirement from the Path 2 PR checklist and from the "Writing a Good PR
message" section; both now explain that the `cutter.sh` bot posts UI
screenshots automatically and ask contributors to describe the visible
change instead.
- `.github/PULL_REQUEST_TEMPLATE.md`: updated the Verification guidance
to note that `cutter.sh` posts before/after screenshots automatically,
and removed the "include before/after screenshots" item from the
checklist.
- Issue templates were intentionally left unchanged — they only offer
screenshots as an optional "if helpful" field, never a requirement.
## Verification
- Docs-only change; no code paths affected.
- `grep -rin "screenshot" CONTRIBUTING.md .github/` confirms no
screenshot *requirement* remains — only the new `cutter.sh` wording and
the optional "if helpful" fields in issue templates.
- Render `CONTRIBUTING.md` and the PR template to confirm the wording
reads cleanly.
## Risks
Low risk — documentation-only change. No build, runtime, or migration
impact.
## Model Used
Claude (Anthropic), model `claude-opus-4-8`, extended thinking with tool
use.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass (docs-only; no tests apply)
- [ ] I have added or updated tests where applicable (N/A — docs-only)
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending CI)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The `codex_local` adapter isolates local Codex runs by assigning
managed `CODEX_HOME` state per company and per agent
> - PR #8272 tightened that isolation, but it left a gap: once
`CODEX_HOME` became explicit, the adapter treated it like a user-managed
override and skipped auth seeding
> - That meant newly isolated agents could launch with no usable
`auth.json`, hit OpenAI unauthenticated, and fail with `401 Missing
bearer`
> - Users who had already persisted one of those broken managed homes
could remain stranded even after config changes unless Paperclip
repaired the home itself
> - This pull request teaches Paperclip to seed managed homes correctly,
backfill already-stranded managed homes on startup, and reject
credential-less managed homes before they reach the provider
> - The benefit is that affected managed `codex_local` agents recover
automatically after upgrade and restart, without manual `CODEX_HOME`
surgery
## Linked Issues or Issue Description
- Fixes#497
- Refs #5028
- Related PR: #8272
- Related PR: #8399
## What Changed
- Distinguished Paperclip-managed `CODEX_HOME` paths from genuine
external overrides and always seeded auth into managed homes, even when
`CODEX_HOME` is explicit in config.
- Wrote API-key-backed `auth.json` files for managed homes when
`OPENAI_API_KEY` is configured, otherwise symlinked the shared Codex
auth for subscription/OAuth flows.
- Added a startup reconciliation pass that backfills already-isolated
managed homes created by the broken release so upgrade plus restart
repairs stranded agents automatically.
- Preserved previously resolved API-key auth when the stored
`OPENAI_API_KEY` binding is secret-backed and startup cannot resolve the
secret value directly.
- Hardened the managed-home preflight to require a credential-bearing
`auth.json`, not just file presence, and documented the recovery
behavior.
- Added regression tests covering managed-home seeding, fail-fast
behavior, and server-side startup reconciliation.
## Verification
```bash
pnpm exec vitest run packages/adapters/codex-local/src/server/codex-home.test.ts packages/adapters/codex-local/src/server/execute.auth.test.ts server/src/__tests__/codex-auth-reconciliation.test.ts server/src/__tests__/codex-local-execute.test.ts server/src/__tests__/server-startup-feedback-export.test.ts
pnpm --filter @paperclipai/adapter-codex-local typecheck
pnpm --filter @paperclipai/server typecheck
pnpm check:tokens
pnpm exec vitest run server/src/__tests__/server-startup-feedback-export.test.ts
```
GitHub Actions `PR` workflow is green on latest head `e5b1e08d4`,
including policy, typecheck, test shards, build, e2e, serialized server
suites, and canary dry run. Greptile Review is green on latest head with
0 comments added. No UI changes.
## Risks
- Startup reconciliation now mutates persisted managed Codex homes at
boot. Risk is low because it only touches Paperclip-managed
company/agent home paths and no-ops when a home already has usable auth.
- Genuine external `CODEX_HOME` overrides remain intentionally
self-managed, so those users still own repair steps inside their custom
home.
- Hosts with neither shared Codex auth nor an explicit per-agent API key
now fail earlier with a clearer adapter error instead of surfacing a
downstream `401`, which changes timing but not capability.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in a local Codex session; exact
served model ID/context window were not exposed to the session. Tool use
and code execution were enabled.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent adapter configuration is where operators edit the runtime
settings that control how each agent is invoked.
> - The adapter form includes a test action so operators can validate
the in-progress configuration before saving it.
> - In edit mode, that test action changed into `Save + Test` when the
form was dirty, which mixed validation with persistence.
> - Operators already have an explicit Save action for committing
adapter changes.
> - This pull request makes the adapter test action test-only so
validation never implicitly saves a dirty draft.
> - The benefit is clearer operator control: Test validates the current
draft, Save persists it.
## Linked Issues or Issue Description
No matching public GitHub issue or in-flight PR was found, so this PR
describes the bug inline.
### Pre-submission checklist
- I searched existing open and closed issues and did not find a
duplicate.
- I can reproduce this behavior on current `master` before this branch.
- I confirmed this originates in the Paperclip UI, not in a specific
local agent CLI or provider configuration.
### What happened?
When editing an existing agent adapter configuration, changing an
adapter setting made the adapter environment test button change from
`Test` to `Save + Test`. Clicking it saved dirty edits before running
the environment test.
### Expected behavior
The test action should always be a test action. Dirty draft values
should be validated without persisting them, and the explicit Save
button should be the only save path.
### Steps to reproduce
1. Open an existing agent adapter configuration.
2. Edit an adapter setting so the form becomes dirty.
3. Observe the adapter environment test action label and behavior.
### Paperclip version or commit
Current `master` before this branch, `c0743482b`.
### Deployment mode
Local dev/private operator UI.
### Installation method
Built from source with pnpm.
### Agent adapter(s) involved
Not adapter-specific. The behavior lives in the shared agent adapter
config form.
### Database mode
Not database-related.
### Access context
Board operator UI.
### Additional context
Public searches performed before opening this PR:
- `adapter test save`
- `Save + Test`
- `AgentConfigForm testEnvironment`
- `adapter configuration test button`
No logs or local config are needed for this UI-only behavior report.
## What Changed
- Removed the helper that converted dirty edit-mode adapter tests into
`Save + Test`.
- Removed the save-before-test helper path so clicking Test only invokes
the adapter environment test mutation.
- Kept draft testing behavior by continuing to build the test payload
from the current in-progress adapter config.
- Dropped unit tests that asserted the old save-and-test behavior.
## Verification
- `pnpm vitest run ui/src/components/AgentConfigForm.test.ts` -> 6/6
passed.
- `pnpm --filter @paperclipai/ui typecheck` was attempted locally and
failed on the existing unrelated
`packages/plugins/sdk/src/ui/components.ts` React module-resolution
error before reaching this change.
- Static review: `Save + Test`, `getAgentConfigTestActionLabel`, and
`runAgentConfigEnvironmentTest` no longer appear in `ui/src`.
- Before/after visual state for the dirty edit-mode adapter test action:
- Before: `Save + Test`
- After: `Test`
- PR CI passed: policy, commitperclip review,
typecheck/release-registry, build, general tests, serialized server
suites, e2e, canary dry run, aggregate verify, and security scans.
- Greptile passed with 5/5 confidence. The remaining
screenshot/documentation thread was answered and resolved because this
is a text-only button-label state already captured above.
## Risks
Low risk. This is a narrow UI behavior change in the adapter config
form. The main behavior change is intentional: testing a dirty adapter
draft no longer persists it. If an operator expected Test to save edits,
they must now use the explicit Save button.
## Model Used
- Anthropic Claude via Paperclip `claude_local` produced the initial
implementation commit.
- OpenAI GPT-5 Codex via Paperclip `codex_local` reviewed the change,
amended public commit metadata, pushed the branch, and prepared this
pull request. The adapter did not expose an exact context-window value
in the run payload; tool use and local shell execution were used.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the control plane for managing AI-agent work, and the
UI needs to stay legible because operators use it to steer execution.
> - This change sits in the Company Environments screen inside the
Paperclip app UI.
> - The screen carried redundant headings and several overlapping helper
sentences that added clutter without adding meaning.
> - The request was presentation-only: remove redundant copy and
simplify labels, with no change to environment-selection behavior.
> - The safest path was a single-file edit in `CompanyEnvironments.tsx`
that preserved every control and only removed/renamed text.
> - This is a `style:` change (UI copy/presentation), so no behavior is
altered and no new test is warranted.
## Linked Issues or Issue Description
No public GitHub issue exists for this small UI cleanup, so the problem
is described here instead.
- The Company Environments screen had redundant headings and overlapping
helper copy.
- The default environment control used a verbose label and a Local
option label that added noise without adding meaning.
- Requested outcome: simplify the copy so the screen is easier to scan
while preserving the same behavior.
Related public PRs reviewed for overlap:
- Refs #8380
- Refs #8391
- Refs #8398
- Refs #4902
## What Changed
- Removed the extra top-level `Environments` header inside the screen
content (both enabled and disabled states).
- Removed the introductory paragraph above the environment list.
- Renamed `Instance default environment` to `Default` and removed its
helper sentence.
- Removed the duplicate helper text below the default environment
select.
- Changed `Local (built-in default)` to `Local`.
- Removed the redundant `Saved environments` section heading.
- Removed the empty-state line `No saved environments yet. Local remains
the default until you add another target.`
- Removed the previously added UI test, which only asserted the
now-final copy and added no lasting value.
- Removed the now-unused `Settings` import and
`instanceDefaultEnvironment` variable.
## Verification
- Ran the existing `CompanyEnvironments` vitest suite locally: 4 passed.
- This is a presentation-only copy change with no API, state, or
behavior changes.
- Reviewer can open Company Settings -> Environments and confirm the
screen matches the requested wording cleanup with no behavior change.
## Risks
- Low. Copy-only UI change with no API or state-management changes.
- The only real risk is removing more context than intended from the top
of the screen; review should focus on clarity.
> 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
- Anthropic Claude Opus 4.8 via the Claude local adapter with tool use
in a Paperclip-managed workspace.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] 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] This is a `style:` copy-only change; no new test is warranted
(test-coverage gate skips `style:`)
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open-source app that manages AI agents and their
work coordination
> - The server subsystem exposes a control-plane HTTP API that agents
read and write task state against at runtime
> - On local development machines, Paperclip binds to loopback
(`127.0.0.1:3100`) but also advertises LAN hostnames via
`allowedHostnames` for multi-device setups
> - Agents inherit their API URL from `PAPERCLIP_API_URL` /
`PAPERCLIP_RUNTIME_API_URL` env vars exported during server startup
> - `choosePrimaryRuntimeApiUrl` was selecting the first entry in
`allowedHostnames` (the LAN IP) before the loopback bind host, so agents
on the same machine tried to connect to an unreachable LAN address
(NEE-327)
> - This PR fixes the chooser to return the normalized loopback bind
host first, before considering LAN `allowedHostnames`
> - The benefit is that local agents reliably reach the control plane
regardless of `allowedHostnames` configuration
## What's going on
Local Paperclip agents were sometimes inheriting
`PAPERCLIP_API_URL=http://192.168.1.50:3100` even when the server was
bound to loopback, which made the control plane unreachable from this
workspace. This keeps the runtime API on the loopback bind host for
local startup while still preserving LAN candidates for other callers.
## Problem
`choosePrimaryRuntimeApiUrl` preferred the first allowed hostname over
the actual loopback bind host. In the failing setup from NEE-327, that
exported `http://192.168.1.50:3100` into agent env even though
`http://127.0.0.1:3100` was the reachable control-plane URL.
## Solution
The primary runtime URL chooser now returns the normalized loopback bind
host before considering `allowedHostnames`. I added a focused unit test
for the chooser and a startup regression that verifies
`PAPERCLIP_RUNTIME_API_URL` / `PAPERCLIP_API_URL` stay on `127.0.0.1`
while the candidate list still includes the LAN hostname.
## Testing
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/runtime-api.test.ts
src/__tests__/server-startup-feedback-export.test.ts`
- Result: `2` files passed, `13` tests passed
- Runtime check: `curl -sS http://127.0.0.1:3100/api/health` returned
`{"status":"ok",...}` during this heartbeat
## Notes
Already-running local Paperclip servers need a restart to export the
corrected `PAPERCLIP_API_URL` into new agent runs. Rollback is revert
`df8d7fcf`.
## Model Used
Claude Sonnet 4.6 (`claude-sonnet-4-6`), 1M context window, via the
Paperclip Founding Engineer agent harness. Capabilities used: extended
tool use, code editing, test execution, shell commands.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Neeraj Kumar Singh <b.nirajkumarsingh@hotmail.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The claude-local adapter launches the Claude CLI inside execution
environments (local, SSH, and ephemeral sandboxes such as Daytona)
> - Newer adapter code passes `--effort` to the CLI, but the Claude
binary baked into some sandbox images is older and rejects it with
`error: unknown option '--effort'`, so every run in those environments
fails
> - This needs addressing because the failure is environment-dependent
and silent from the operator's perspective — the run just dies with a
CLI usage error
> - This pull request probes the in-sandbox CLI for `--effort` support
once, caches the result per environment, and strips the flag (with a
warning) when the CLI does not support it
> - The benefit is that sandboxes with older Claude CLIs keep working
instead of failing, with negligible probe overhead because the
capability check is cached and reused across ephemeral leases
## Linked Issues or Issue Description
No public GitHub issue exists. Describing the bug inline following the
bug report template:
### What happened?
Runs using the `claude-local` adapter inside certain sandbox/execution
environments fail with `error: unknown option '--effort'`. The adapter
unconditionally appends `--effort` to the Claude CLI invocation, but the
Claude CLI version present in some sandbox base images predates that
flag, so the process exits with a usage error and the run dies.
### Expected behavior
The adapter should detect that the target environment's Claude CLI does
not support `--effort` and degrade gracefully — drop the flag and emit a
warning — rather than failing the run.
### Steps to reproduce
1. Configure an execution environment (e.g. a sandbox image) whose
bundled Claude CLI is old enough to predate the `--effort` option.
2. Run any claude-local task that resolves to an effort level (so
`--effort` is appended).
3. Observe the run fail immediately with `error: unknown option
'--effort'`.
### Paperclip version or commit
`master` at the time of this PR (branch forked from current `master`).
### Deployment mode
Self-hosted / local instance using execution environments (reproducible
with ephemeral sandbox providers such as Daytona where `reuseLease:
false`).
### Agent adapter(s) involved
Claude Code (`claude-local`).
## What Changed
- Add a CLI capability probe (`cli-capabilities.ts`) that runs the
target Claude binary's `--help` inside the execution environment to
detect `--effort` support.
- Strip `--effort` from the CLI args (emitting a warning) when the probe
reports the flag is unsupported; keep it otherwise.
- Cache probe results keyed by
`sandbox:providerKey:environmentId:command` (no lease id) so the probe
is reused across ephemeral leases — important for `reuseLease: false`
sandbox configs like Daytona, which would otherwise re-probe on every
run.
- Conservative fallback: if the probe itself can't run/parse, assume the
flag is supported (preserves prior behavior).
## Verification
- `node_modules/.bin/vitest run
src/__tests__/claude-local-execute.test.ts
src/__tests__/claude-local-adapter-environment.test.ts` → **2 files, 30
tests passed**.
- Regression test issues two `execute()` calls with distinct lease ids
and asserts the in-sandbox `--help` probe runs exactly once (cache reuse
across leases).
- Added tests covering: flag stripped when unsupported, flag retained
when supported, warning emitted, and conservative fallback when the
probe fails.
## Risks
Low risk. The change is additive and gated behind a probe with a
conservative default (assume supported on probe failure), so existing
environments that support `--effort` are unaffected. Worst case for an
environment where the probe is unreliable is the prior behavior (flag
passed through).
## Model Used
Claude Opus 4.8 (claude-opus-4-8), extended thinking, with tool use /
code execution via Claude Code.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots (N/A — no UI change)
- [ ] I have updated relevant documentation to reflect my changes (N/A —
no doc-facing behavior change)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending CI)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the control plane for running AI-agent companies, so
long-running remote work needs to stay observable to human operators.
> - Cloud / sandbox agents are an active roadmap area, and their
workspace sync path is part of the runtime substrate every remote coding
run depends on.
> - In the sandbox and SSH execution-target flows, Paperclip logged that
sync had started, then often went silent for the full transfer window.
> - That made large remote syncs feel stalled and also hid a real
performance problem in the command-managed sandbox upload path.
> - The first part of this pull request threads a throttled
progress-reporting surface through the adapter execution-target stack so
sync and restore work can emit meaningful updates.
> - The second part fixes the command-managed sandbox transport itself:
it removes the old serial 32KB append bottleneck, but also falls back
away from the single-stream path when a provider-backed sandbox runner
cannot surface mid-flight stdin progress.
> - The result is that sandbox and SSH transfers are both faster and
more observable, including the live Daytona-style sandbox case that
previously only emitted `0%` and `100%`.
## Linked Issues or Issue Description
No public GitHub issue exists for this bug, so it is described inline
below following the bug report template.
### What happened
- Remote sandbox and SSH workspace syncs could spend a long time
transferring data while only logging a start line (`Syncing workspace
and runtime assets to sandbox environment`) and, at best, a terminal
line.
- In the command-managed sandbox path, the original upload
implementation also paid a large performance cost by appending base64
data in many small sequential remote writes (thousands of serial 32KB
round-trips on a large workspace).
- After the initial transport rewrite, live provider-backed sandbox runs
still only emitted `0%` and `100%` because the single-stream stdin RPC
buffered progress until completion.
### Expected behavior
- Long-running sandbox and SSH syncs should periodically report how much
of the transfer is complete (a percentage and/or MB transferred) so an
operator can tell the run is healthy and making progress rather than
stuck.
- The main sandbox upload path should not be artificially slow.
- A transfer that fails partway should leave an explicit failure marker
in the log rather than a dangling intermediate percentage.
### Steps to reproduce
1. Run an agent against a sandbox (command-managed) or SSH
(remote-managed) execution target with a non-trivial workspace.
2. Watch the run log during the workspace/runtime asset sync phase.
3. Observe that the log shows the sync start line and then stays silent
for the full transfer (live provider-backed sandbox runs only show `0%`
then `100%`).
### Paperclip version or commit
- Branch `PAPA-825-provide-status-updates-when-syncing-sandboxes` off
`master`.
### Deployment mode
- Self-hosted / local instance using sandbox (command-managed) and SSH
(remote-managed) execution targets, including provider-backed sandbox
runners.
## What Changed
- Added shared throttled runtime progress reporting and threaded
`onProgress` through the adapter execution-target surface and adapter
`execute.ts` entrypoints.
- Added sync and restore progress reporting for the command-managed
sandbox path and the SSH/remote-managed path, including git
import/export progress where totals are known.
- Reworked command-managed sandbox transfer behavior so uploads use the
faster single-stream path when appropriate, but fall back to chunked
progress-emitting writes when the runner cannot expose mid-stream stdin
progress.
- Marked provider-backed environment sandbox runners as not supporting
single-stream stdin progress so live sandbox runs emit meaningful
intermediate updates instead of only `0%` and `100%`.
- Emit an explicit terminal failure marker (`failed at NN% (x/y MB)`)
when an SSH/tar transfer rejects, so a failed sync no longer leaves a
dangling intermediate percentage in the log.
- Run the SSH sync/restore size estimate (local directory walk / remote
`du` probe) concurrently with the transfer instead of awaiting it before
opening the pipe, so progress instrumentation no longer adds startup
latency proportional to workspace file count.
- Added and extended focused regression coverage for runtime progress
throttling and the new failure marker, command-managed sandbox
transfers, sandbox orchestration, SSH transfer progress, and environment
execution-target wiring.
## Verification
- `pnpm exec vitest run
packages/adapter-utils/src/runtime-progress.test.ts
packages/adapter-utils/src/ssh-fixture.test.ts
packages/adapter-utils/src/command-managed-runtime.test.ts
packages/adapter-utils/src/sandbox-managed-runtime.test.ts`
- `pnpm exec vitest run
packages/adapter-utils/src/command-managed-runtime.test.ts
server/src/__tests__/environment-execution-target.test.ts`
- `npx tsc --noEmit` for `packages/adapter-utils`
## Risks
- The provider-backed sandbox fallback now prefers chunked
command-managed writes when progress hooks are active, so
small-to-medium uploads may trade some raw throughput for observable
intermediate progress on runtimes that cannot surface true mid-stream
stdin progress.
- Progress percentages on tar-based transfers still depend on estimates
in some cases, so operators may briefly see MB-only lines before the
estimate resolves, then near-final clamping before the terminal `100%`
line.
- This PR changes shared execution-target behavior used by multiple
adapters, so regressions would most likely appear in remote runtime
setup/teardown flows rather than in a single adapter.
## Model Used
- Initial implementation: OpenAI GPT-5.4 via Codex local agent
(`codex_local`), high reasoning mode.
- Observability follow-ups (failure marker, concurrent size estimate,
added tests): Claude Opus 4.8 via Claude Code (`claude_local`).
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run in execution environments, and the Company Environments
settings page lets operators configure them
> - That page rendered an "environment support by adapter" matrix table
plus two descriptive text boxes explaining the adapter/driver/sandbox
support model
> - That matrix duplicated information already surfaced where operators
actually pick a driver/provider, and added a wide, dense table that
provided no actionable value on this screen
> - This pull request removes the support-matrix table and the two
explanatory text boxes above it, along with the now-orphaned helper
component, derived constant, and unused imports
> - The benefit is a simpler, less cluttered environments settings page
that only shows the controls an operator acts on
## Linked Issues or Issue Description
No public GitHub issue exists for this. Describing the change following
the feature-request template:
### Problem or motivation
The Company Environments settings page showed an adapter support matrix
table (Adapter × Local/SSH/Sandbox) and two paragraphs of descriptive
text above it. The table restated the static adapter/driver support
model and the sandbox-provider plugin caveat, neither of which is
actionable on this page — operators don't change adapter capabilities
here, they create and edit environments. The table was wide enough to
require horizontal scroll and added visual noise without helping the
operator complete any task.
### Proposed solution
Remove the support-matrix table and the two descriptive text boxes above
it from `CompanyEnvironments.tsx`, along with the now-unused helper
component, derived constant, locals, and imports. Leave all environment
create/edit controls, provider selection, and sandbox-provider logic
untouched.
### Alternatives considered
Collapsing the table behind a disclosure/"Learn more" toggle instead of
removing it — rejected because the information is static, non-actionable
on this screen, and already available where operators pick a
driver/provider; hiding it would keep the maintenance cost without
adding value.
### Roadmap alignment
Not a roadmap feature — this is a small, focused UI cleanup that removes
non-actionable content from an existing settings page.
Related (already merged) work that last touched this copy:
paperclipai/paperclip#4902 (clarified sandbox-provider messaging in
company environments). No open duplicate PR was found.
## What Changed
- Removed the adapter support matrix `<table>` from
`ui/src/pages/CompanyEnvironments.tsx`
- Removed the two descriptive text boxes rendered above that table
(adapter support model + installed sandbox providers blurbs)
- Deleted the now-unused `SupportMark` helper component and the
`ENVIRONMENT_SUPPORT_ROWS` derived constant
- Removed the now-unused `sandboxSupportVisible` local and four
now-unused imports (`AGENT_ADAPTER_TYPES`,
`getAdapterEnvironmentSupport`, `Check`, `adapterLabels`)
- Updated `ui/src/pages/CompanySettings.test.tsx` to drop the two
assertions tied to the removed copy
## Verification
- `vitest run src/pages/CompanySettings.test.tsx` → 4/4 pass
- `tsc --noEmit` on the UI project → no errors in the changed files
- The remaining environment create/edit controls, provider selection,
and sandbox-provider logic are untouched (the `environmentCapabilities`,
`discoveredPluginSandboxProviders`, and `sandboxCreationEnabled` values
are still used by the form)
## Risks
Low risk — pure presentational removal of a read-only informational
table and static copy. No API, data model, or behavioral changes; no
state that other components depend on was removed.
## Model Used
Claude (Anthropic), model id `claude-opus-4-8` (Claude Opus 4.x family),
with extended thinking and tool use, run via Claude Code.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage AI
agents and the work they do.
> - This change lives in the board UI, specifically the instance
environments settings page where operators define reusable execution
targets.
> - That page currently rendered the create/edit environment form inline
near the bottom of the screen.
> - Because the form appeared away from the action that triggered it, it
was easy to miss and felt inconsistent with the rest of the UI.
> - This pull request moves environment creation and editing into a
proper centered dialog with the existing form behavior preserved.
> - The benefit is a more obvious, consistent, and easier-to-complete
settings flow for environment management.
## Linked Issues or Issue Description
No public GitHub issue matched this exact UI problem when I searched
related issues and PRs.
### Pre-submission checklist
- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I am on the latest released version of Paperclip (or can reproduce
on `master`).
- [x] I have confirmed the issue originates in Paperclip itself, not
adapter or local configuration.
### What happened?
On `Settings -> Instance settings -> Environments`, clicking create or
edit revealed the environment form inline near the bottom of the page.
Because the form appeared away from the action that triggered it, it was
easy to miss.
### Expected behavior
Create and edit should open a centered modal dialog with a dimmed
backdrop and the same form controls.
### Steps to reproduce
1. Open `Settings -> Instance settings -> Environments`.
2. Click the create or edit action for an environment.
3. Observe that the form expands inline on the page instead of opening
in a modal.
### Paperclip version or commit
Observed on current `master` before this PR, for example
[`e93d78b46`](https://github.com/paperclipai/paperclip/commit/e93d78b46).
### Deployment mode
Local dev (`pnpm dev`).
### Installation method
Built from source (`pnpm dev` / `pnpm build`).
### Agent adapter(s) involved
- [x] Not adapter-specific (core bug)
### Database mode
Embedded PGlite (default, `DATABASE_URL` unset).
### Access context
Board (human operator).
### Node.js version
Node.js 25.6.1 in the local contributor environment.
### Operating system
macOS local development environment.
### Relevant logs or output
None. This was a visible UI behavior issue rather than a logged
server/runtime error.
### Relevant config (if applicable)
Not config-related.
### Additional context
Related PR/search context checked before opening this fix. UI preview
screenshots are attached below.
### Privacy checklist
- [x] I have reviewed all pasted output for PII and redacted where
necessary.
## What Changed
- Moved the environment create/edit form in
`ui/src/pages/CompanyEnvironments.tsx` into a shared dialog.
- Added an explicit `Add environment` action near the saved environments
list and wired row edit actions to open the same dialog in prefilled
mode.
- Preserved existing save/test behavior while resetting dialog-local
mutation state when the modal opens or closes.
- Extended `ui/src/pages/CompanyEnvironments.test.tsx` to cover
add-open/cancel and edit-open/save dialog flows.
- Updated `ui/src/pages/CompanySettings.test.tsx` so the existing
environments coverage opens and inspects the dialog through the Radix
portal.
## Verification
- `node node_modules/vitest/vitest.mjs run
ui/src/pages/CompanyEnvironments.test.tsx`
- `node node_modules/vitest/vitest.mjs run
ui/src/pages/CompanySettings.test.tsx`
- Manual review:
- Open `Settings -> Instance settings -> Environments`
- Click `Add environment` and confirm the form opens in a centered
dialog
- Click `Edit` on an existing environment and confirm the dialog opens
with existing values
- Confirm `Cancel`, `Test`, and save actions still behave as expected
- Before/after screenshots:
- [Cancel environment
creation/edit](https://artifacts.cutter.sh/8391/run-1175b5c-2026-06-20T18-47-11/preview/clip-01.mp4)
- [Add a new
environment](https://artifacts.cutter.sh/8391/run-1175b5c-2026-06-20T18-47-11/preview/change-02.png)
- [Save an
environment](https://artifacts.cutter.sh/8391/run-1175b5c-2026-06-20T18-47-11/preview/change-03.png)
## Risks
- Low risk overall because the change is contained to the environments
page and keeps the existing form fields and mutations.
- The main behavior change is modal layout, so the biggest risk is
regressions in tall-form scrolling or smaller-screen dialog ergonomics.
> I checked [`ROADMAP.md`](ROADMAP.md). This is a tightly scoped UI
polish fix, not duplicate roadmap-level core feature work.
## Model Used
- OpenAI GPT-5.4 via the `codex_local` Paperclip adapter
- High reasoning mode with tool use, shell execution, git operations,
and targeted test execution
- Model-assisted authoring and verification of the UI and test changes
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The server exposes live event updates to board and agent clients
over WebSocket upgrade requests
> - WebSocket upgrade authorization can be asynchronous, leaving the raw
HTTP upgrade socket in server-owned code before `ws` takes over
> - If the client disconnects during that authorization window, the
server can still try to reject or upgrade a closed socket
> - A raw socket write after peer disconnect can emit `EPIPE` /
`ECONNRESET`, and without a listener that can become process-fatal
> - This pull request hardens the pre-`ws` upgrade socket path and adds
regression coverage for disconnect/error races
> - The benefit is that live event reconnect churn degrades gracefully
instead of risking a server crash
## Linked Issues or Issue Description
External public context: Refs
https://github.com/aronprins/paperclip-desktop/issues/14
No matching open upstream issue or PR was found when searching
`paperclipai/paperclip` for `EPIPE`, `rejectUpgrade`, `live-events-ws`,
`websocket upgrade`, and related socket-write terms.
### Bug report
**What happened?**
The live events WebSocket upgrade handler could write a rejection
response to the raw upgrade socket after the peer had already
disconnected during async authorization. A transport-level `EPIPE` or
similar socket error on that raw socket can terminate the server process
if it is emitted without an error listener.
**Expected behavior**
The server should not write to destroyed/non-writable upgrade sockets,
should tolerate raw socket errors while authorization is pending, and
should not call `handleUpgrade()` after the socket is no longer
writable.
**Steps to reproduce**
1. Start a Paperclip server with live events enabled.
2. Open a raw WebSocket upgrade request to
`/api/companies/:companyId/events/ws`.
3. Disconnect the client before async authorization resolves.
4. Let the server take the reject or upgrade path.
5. Observe that the pre-fix path can still write to or upgrade a closed
socket.
**Paperclip version or commit**
Reproduced by static inspection on `master` before this PR at
`950484d20`.
**Deployment mode**
Any mode using live event WebSocket upgrades. The report was originally
observed from local Desktop embedding, but the vulnerable code is in the
server package.
## What Changed
- Added a writable-state guard for raw upgrade sockets before rejecting
or completing an upgrade.
- Changed rejection responses from raw `write()` + `destroy()` to
guarded `end()` with warning logging if rejection fails synchronously.
- Attached a temporary raw socket error listener during the async
upgrade authorization window and cleaned it up on socket close or
successful `ws.handleUpgrade()`.
- Added regression tests for rejecting after an early socket close and
for handling raw socket `error` events while authorization is pending.
## Verification
- `pnpm exec vitest run server/src/__tests__/live-events-ws.test.ts`
- `pnpm --filter @paperclipai/server typecheck`
- `git diff --check`
## Risks
- Low risk: the change is scoped to the live-events WebSocket upgrade
path before `ws` takes ownership of the socket.
- Rejected upgrade responses now use graceful `socket.end(...)`; clients
should still receive the same HTTP status text when the socket is
writable.
- The temporary error listener is removed before successful
`handleUpgrade()` so normal WebSocket client error handling remains
owned by the existing `connection` path.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent with repository-aware tool use, shell
command execution, GitHub connector access, and local code editing.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the control plane operators use to manage AI-agent
companies.
> - Board operators rely on the settings UI and CLI docs to understand
which product surfaces are stable to depend on.
> - Experimental features already existed in the product, but the
operator-facing contract around them was too soft and too fragmented.
> - That created a risk that users would enable experiments without
being told clearly that they can break, change, or disappear.
> - The docs and the in-product settings page both needed the same
explicit warning language so the contract is visible at the moment of
decision.
> - This pull request adds that warning to the board-operator guide, CLI
references, and the experimental settings page.
> - The benefit is clearer operator expectations without changing the
underlying feature flags or rollout behavior.
## Linked Issues or Issue Description
No public GitHub issue exists for this docs/polish gap.
Problem description:
- Board operators could enable experimental features without a clear
operator-facing statement that those features are opt-in and come
without compatibility guarantees.
- The docs site, repo CLI reference, and in-product experimental
settings page did not present one consistent warning contract.
- This PR closes that gap by documenting the risk explicitly where
operators discover and enable those settings.
Related public search:
- Searched public issues/PRs for related work with `gh search issues
--repo paperclipai/paperclip 'experimental features warning'` and `gh
search prs --repo paperclipai/paperclip 'experimental features
warning'`.
- Reviewed open PR #6165 during that search and found it unrelated; it
changes experimental auth/routing flags rather than documenting
experimental-feature risk.
## What Changed
- Added a new board-operator guide at
`docs/guides/board-operator/experimental-features.md` that defines the
Paperclip contract for experimental features.
- Registered that guide in `docs/docs.json` so it appears in the public
docs navigation.
- Added matching caveat language next to `instance
settings:experimental` in `docs/cli/control-plane-commands.md`.
- Added the same caveat to `doc/CLI.md` so the repo CLI reference does
not drift from the published docs.
- Added a single page-level warning banner to
`ui/src/pages/InstanceExperimentalSettings.tsx` stating that
experimental features are opt-in, carry no compatibility guarantees, and
may change, break, or be removed.
- Added a targeted UI test in
`ui/src/pages/InstanceExperimentalSettings.test.tsx` that asserts
exactly one page-level warning renders with the new risk language.
## Verification
- `jq empty docs/docs.json`
- `git diff --check`
- `cd ui && pnpm vitest run
src/pages/InstanceExperimentalSettings.test.tsx`
- Manual review of the warning contract across:
- `docs/guides/board-operator/experimental-features.md`
- `docs/cli/control-plane-commands.md`
- `doc/CLI.md`
- `ui/src/pages/InstanceExperimentalSettings.tsx`
UI note:
- This is a copy-level warning addition rather than a layout rework. I
did not attach before/after screenshots in this PR body.
## Risks
- Low risk: this changes operator-facing documentation and warning copy,
not feature-flag behavior.
- The main failure mode is wording drift across docs and UI in future
edits, which is why this PR adds the same contract to all relevant
operator-facing surfaces.
> I checked `ROADMAP.md` before opening this PR. This is docs/UI polish
around an existing experimental surface, not overlapping roadmap-level
core feature work.
## Model Used
- OpenAI Codex Local using `gpt-5.4` with high reasoning and tool use
for coordination, review, docs changes, and PR preparation.
- Anthropic Claude Local using `claude-opus-4-8` with high reasoning and
tool use for the in-product warning and targeted UI test.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the control plane for AI-agent companies, so execution
environment selection has to stay inspectable and predictable across
companies, agents, and runs.
> - The environment subsystem decides where an agent heartbeat actually
runs and how remote sandbox state is realized and restored.
> - That subsystem previously mixed company-scoped environment catalogs
with issue-level environment stamping, so a reassigned issue could keep
executing in the previous assignee's sandbox.
> - That behavior breaks the control-plane contract: changing the
assignee should change the executing agent/environment path unless there
is an explicit current override.
> - Fixing it cleanly required more than a narrow patch; the environment
model had to move to instance scope with a single inherited default and
per-agent override semantics.
> - This pull request rewires the schema, server/API surface, runtime
resolution, and UI around that model, then adds regression coverage for
cross-company inheritance and per-agent isolation.
> - The benefit is that environment choice now follows the approved
instance/agent configuration path instead of stale issue state, while
shared environments only need to be configured once per instance.
## Linked Issues or Issue Description
- No directly matching public GitHub issue or PR was found while
searching for this refactor.
### What happened?
Reassigning work between agents with different execution environments
could keep running in the previous sandbox because environment choice
was stamped onto the issue and outranked the current assignee. The same
subsystem also forced environment catalogs to be duplicated per company
even though the underlying execution environments were instance-wide
resources.
### Expected behavior
Execution should resolve through the current instance and agent
configuration path, with one instance-scoped environment catalog, one
instance default, optional per-agent override, and no stale issue-level
environment authority surviving reassignment.
### Steps to reproduce
1. Configure two agents to use different execution environments.
2. Assign an issue to the first agent so the issue records execution
state in that environment.
3. Reassign the same issue to the second agent and run another
heartbeat.
4. Observe that the pre-fix runtime can still sync or execute in the
original sandbox instead of the second agent's environment.
### Paperclip version or commit
Current `master` before this PR.
### Deployment mode
Self-hosted server.
### Installation method
Built from source (`pnpm dev` / `pnpm build`).
### Agent adapter(s) involved
- Claude Code
- Not adapter-specific (core bug in environment authority / resolution)
### Database mode
External Postgres.
### Access context
Both board reassignment and agent heartbeats were involved.
## What Changed
- Moved environments and their default selection contract to instance
scope in DB/shared types, including the migration that dedupes legacy
per-company environments and seeds the instance local default.
- Reworked environment CRUD/auth flows to use instance-scoped APIs and
added route/service coverage for instance-level environment management.
- Changed runtime resolution to prefer `agent default -> instance
default -> built-in local`, removed issue-level environment stamping
from the active execution path, and isolated sandbox/plugin leases by
`(executionWorkspaceId, agentId)`.
- Added environment env-var runtime precedence so environment-provided
values act as the baseline for agent execution.
- Moved the environment UI into instance settings and updated agent
configuration surfaces to reflect inherit/override behavior.
- Added regression coverage for instance-default inheritance across
companies and for the new runtime resolution behavior.
- Fixed a rebase-only duplicate `enableTaskWatchdogs` flag regression in
instance settings types/validators/services so the branch typechecks
cleanly on current `master`.
- Updated stale server tests so CI matches the shipped instance-scoped
environment contract.
## Verification
- `git diff --check`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run
server/src/__tests__/environment-runtime-driver-contract.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/environment-routes.test.ts
server/src/__tests__/environment-instance-routes.test.ts
server/src/__tests__/execution-workspace-policy.test.ts
server/src/__tests__/heartbeat-plugin-environment.test.ts
server/src/__tests__/instance-settings-routes.test.ts`
## Risks
- The migration changes environment scope and dedupes existing rows, so
installs with unusual legacy environment combinations should be reviewed
carefully during upgrade.
- Remote execution behavior now depends on instance-default inheritance
semantics instead of issue-level stamping, so any remaining code paths
that still assume issue-scoped environment authority would surface as
follow-up bugs.
- This PR includes both server/runtime behavior and UI relocation, so
reviewers should watch for authorization edge cases around instance
settings and environment management.
> I checked [`ROADMAP.md`](ROADMAP.md). This work fits the existing
Cloud / Sandbox agents direction as a bug-fix/refactor to current
behavior, not a new parallel product surface.
## Model Used
- OpenAI Codex coding agent in this Paperclip/Codex session; GPT-5-class
tool-using model with code execution and shell access. The exact backend
model ID is not exposed to the session runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run inside environments, and the Environments settings page
lets users configure each environment and verify it with a "Test
provider" / "Test connection" button per row
> - When a user clicked one environment's test button, every environment
row's button switched to the disabled "Testing..." state at the same
time, then all flipped back together
> - That happened because all rows read the same shared
`environmentProbeMutation.isPending` flag, so a single in-flight probe
disabled and relabeled every button
> - This is confusing: it looks like every environment is being tested,
and it blocks interacting with other rows while one probe runs
> - This pull request tracks the specific environment id being probed in
dedicated state and scopes the disabled/label logic to that id
> - The benefit is that only the button the user actually clicked shows
"Testing..." and is disabled, while the other rows stay interactive
## Linked Issues or Issue Description
No public GitHub issue exists, so the bug is described inline below
following the bug report template.
### What happened?
On the Environments settings page, clicking "Test provider" on one
environment caused the test button on *every* environment row to change
to "Testing..." and become disabled at the same time, then all reverted
together when the probe finished.
### Expected behavior
Only the button for the environment the user clicked should show
"Testing..." and be disabled while its probe runs. Every other row's
button should stay enabled and unchanged.
### Steps to reproduce
1. Open instance settings → Environments with two or more configured
environments.
2. Click "Test provider" / "Test connection" on a single row.
3. Observe that all rows' buttons enter the "Testing..." disabled state
simultaneously instead of just the clicked one.
### Paperclip version or commit
`master` at commit a10f17800 (branch
`fix/environments-test-provider-button-scope`).
### Deployment mode
Local dev (`pnpm dev`). UI-only; reproduces independent of backend.
## What Changed
- Added a dedicated `testingEnvironmentId` state in
`ui/src/pages/CompanyEnvironments.tsx` to track which environment is
currently being probed.
- Set it in the probe mutation's `onMutate` and clear it in `onSettled`,
and reset it when the selected company changes.
- Scoped the test button's `disabled` state and `"Testing..."` label to
`testingEnvironmentId === environment.id` instead of the shared
`environmentProbeMutation.isPending` flag.
- Added `ui/src/pages/CompanyEnvironments.test.tsx` verifying that
clicking one environment's test button puts only that row into the
"Testing..." disabled state while other rows stay enabled.
## Verification
- `pnpm typecheck` (UI) passes clean.
- `vitest run src/pages/CompanyEnvironments.test.tsx` passes (new test
fails against the old shared-`isPending` behavior, confirming it guards
the fix).
- Manual: on the Environments page with multiple environments, click one
row's test button and confirm only that button shows "Testing..." / is
disabled while the others remain enabled, then it reverts on completion.
## Risks
- Low risk. Single-file UI change scoped to per-row button state; no
API, schema, or behavior changes to the probe itself. The probe mutation
still runs identically — only which buttons reflect the pending state
changed.
## Model Used
- Claude Opus 4.8 (Anthropic), `claude-opus-4-8`, with extended
reasoning and tool use, via Claude Code.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The web UI renders work items in a task/issue list and board
columns, each row leading with a status circle next to the title text
> - The status-circle wrapper `<span>` was not enforcing cross-axis
centering, so the circle aligned to the top of taller rows instead of
the title
> - This looked broken: the circle floated above the text rather than
sitting centered against it, across statuses
> - This pull request adds flex centering (`inline-flex` +
`items-center`) to the status-icon wrapper spans so the circle is
vertically centered with the title in every row
> - The benefit is a consistent, correctly aligned task list regardless
of task status or row height
## Linked Issues or Issue Description
No public GitHub issue exists; describing inline following the bug
report template.
### What happened?
In the task/issue list and board, the leading status circle was not
vertically centered with the task title text. On taller rows the circle
sat near the top instead of aligned with the title, so the list looked
misaligned across all statuses.
### Expected behavior
The status circle should be vertically centered with the title string in
every task row, regardless of status.
### Steps to reproduce
1. Open the task list (or board) view in the web UI.
2. Observe rows in different statuses, especially taller rows.
3. Notice the status circle is top-aligned rather than vertically
centered with the title text.
### Paperclip version or commit
`master` @ 411a9a51 (branch base for this PR).
### Deployment mode
Local dev (pnpm dev).
## What Changed
- `ui/src/components/IssueColumns.tsx`: added `items-center` to the
leading status-icon wrapper span (`inline-flex` container) in
`InboxIssueMetaLeading`.
- `ui/src/components/IssuesList.tsx`: added `inline-flex items-center`
to the two status-icon wrapper spans so the circle centers with the row
text (matching the `IssueColumns.tsx` change).
## Verification
- Manual: open the task list and board views; confirm the status circle
is vertically centered with the title across statuses and on taller
rows.
- Before/after matches the screenshots from the originating request
(broken: circle top-aligned; fixed: circle centered).
- `cd ui && npx tsc --noEmit` — type check passes for the touched
components.
## Risks
- Low risk. CSS-only change to wrapper spans; no logic, data, or API
changes. Limited to status-icon alignment in the task list/board rows.
## Model Used
- Claude Opus 4.6 (`claude-opus-4-6`), extended thinking + tool use, via
Claude Code.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [ ] I have added or updated tests where applicable (CSS-only alignment
change; no meaningful unit test — retitled `refactor:` per contribution
gate)
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes (none
required)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run through adapters; `@paperclipai/adapter-claude-local`
shells out to the Claude CLI and maps its result JSON into Paperclip's
run outcome (`errorCode` / `errorFamily`)
> - A Fable 5 policy refusal exits the CLI cleanly (`exitCode=0`,
`is_error=false`) with `stop_reason: "refusal"`, which the adapter
mapped to `errorCode: null`
> - Paperclip therefore recorded the run as a silent success, so the
agent's heartbeat stalled with no signal for operators and no hook to
retry or alert
> - This pull request adds a refusal detector to the adapter so a
refusal resolves to a distinct `errorCode: "claude_refusal"`
(`errorFamily: "model_refusal"`)
> - The benefit is that policy refusals become observable: the server
persists the code, operators can see it on the run, and downstream
retry/alert policy can key off it
## Linked Issues or Issue Description
No public GitHub issue exists. Describing the underlying bug in-PR,
following `.github/ISSUE_TEMPLATE/bug_report.yml`:
**What happened?**
When the Claude CLI returns `stop_reason: "refusal"` (a model policy
refusal), it still exits cleanly (`exitCode=0`, `is_error=false`).
`@paperclipai/adapter-claude-local` keyed refusal detection off the
failure flag during error-code resolution, so it returned `errorCode:
null` — a refused run was indistinguishable from a successful one,
recorded by Paperclip as a silent success with no signal to retry or
alert.
**Expected behavior**
A refusal should resolve to a distinct, non-null error code (`errorCode:
"claude_refusal"`, `errorFamily: "model_refusal"`) so the server can
surface it to operators and downstream policy can react.
**Steps to reproduce**
1. Run any agent on the `claude-local` adapter with a prompt the model
refuses on policy grounds.
2. The Claude CLI exits `0` with `is_error=false` and `stop_reason:
"refusal"`.
3. Observe the run resolves `errorCode: null` (pre-fix) instead of a
refusal-specific code.
**Paperclip version or commit**
`adapter-claude-local` v2026.609.0 (`dist/server/execute.js` error-code
resolution block); reproduced on `master`.
**Deployment mode**
Local / self-hosted.
**Agent adapter(s) involved**
`@paperclipai/adapter-claude-local` (Claude Code, local).
## What Changed
- **`packages/adapters/claude-local/src/server/parse.ts`** — new
`isClaudeRefusalResult()` helper, parallel to
`isClaudeMaxTurnsResult()`. Detects `stop_reason` / `stopReason` /
`error_code` / `errorCode` == `refusal` and `subtype` ==
`model_refusal`, case/whitespace tolerant.
- **`packages/adapters/claude-local/src/server/execute.ts`** — compute
the refusal flag independent of the `failed` flag (a refusal exits
cleanly, so keying off `failed` would miss it); resolve `errorCode:
"claude_refusal"` and `errorFamily: "model_refusal"`; surface
`stopReason: "refusal"` in `resultJson`.
- **`packages/adapter-utils/src/types.ts`** — widen
`AdapterExecutionErrorFamily` with `"model_refusal"`.
- **`packages/adapters/claude-local/src/server/index.ts`** — export the
new helper.
- **`packages/adapters/claude-local/src/server/parse.test.ts`** — 7 new
unit tests.
## Verification
```
npx vitest run packages/adapters/claude-local # 35 passed (7 new)
npx tsc --noEmit -p packages/adapter-utils # clean
npx tsc --noEmit -p packages/adapters/claude-local # clean
```
Manual: a result JSON with `stop_reason: "refusal"` on a clean exit now
resolves `errorCode: "claude_refusal"` and `errorFamily:
"model_refusal"`; non-refusal results are unaffected.
## Risks
Low risk. Additive only — it introduces a new error code on a path that
previously returned `null`; no existing error code or success path
changes. `claude_refusal` is deliberately **not** added to the
transient-upstream retry set: a refusal is deterministic, so retrying
the same prompt yields the same refusal. It surfaces as a distinct
non-transient code for operators; downstream retry/alert policy can key
off `claude_refusal` / `errorFamily: model_refusal` later.
## Model Used
Claude Opus 4.8 (`claude-opus-4-8`), extended-thinking mode, run via
Claude Code with tool use.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (no related PRs found)
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots (N/A — no UI changes)
- [x] I have updated relevant documentation to reflect my changes (N/A —
internal adapter error-code addition; no user-facing docs)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (this push re-runs the gates;
will confirm once green)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(prior run was 4/5 on a PR-description note now addressed; will confirm
on re-run)
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Full-Stack Engineer <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Heartbeats are the control-plane path that turns scheduled,
comment-driven, or on-demand wakeups into adapter executions.
> - Budgeting and recurring work need enforcement before an adapter
starts, not only after model usage is recorded.
> - Empty timer wakes also need an opt-in fast-exit path so operators
can keep routine schedules without paying for no-op model turns.
> - This pull request adds heartbeat preflight gates for daily run and
daily cost caps, plus an explicit timer no-work skip policy.
> - The benefit is safer autonomous operation: capped agents stop before
new execution, queued work is cancelled cleanly at claim time, and
proactive agents still run by default unless the operator opts into
no-work skipping.
## Linked Issues or Issue Description
No public issue exists for this change. Inline bug report:
### What happened?
Heartbeat execution can start without enforcing per-agent daily
invocation and spend limits at the heartbeat boundary. A run that was
queued before a cap was reached can also be claimed later and invoke the
adapter unless the cap is checked again immediately before execution.
Operators also do not have an explicit opt-in fast-exit policy for
generic timer wakes with no actionable assigned work.
### Expected behavior
Configured daily run and daily cost caps should stop new heartbeat runs
before adapter execution. Already queued runs should be rechecked at
claim time and cancelled cleanly when a cap is now reached. Queued issue
runs cancelled by daily caps should release their issue execution locks
and promote deferred wakeups without entering immediate recovery loops.
Generic timer no-work skipping should be opt-in so proactive agents
continue to run by default.
### Steps to reproduce
1. Configure an agent heartbeat policy with a one-run daily cap or a
daily cost cap.
2. Create or queue heartbeat wakeups for that agent after the cap has
already been consumed.
3. Observe that without preflight and claim-time checks, the heartbeat
path can still enqueue or claim work that should be blocked before
adapter execution.
### Paperclip version or commit
Reproduced against `master` before this branch.
### Deployment mode
Local dev (`pnpm dev`) / built from source.
### Installation method
Built from source (`pnpm dev` / `pnpm build`).
### Agent adapter(s) involved
Not adapter-specific (core heartbeat scheduling and claim logic).
### Database mode
External Postgres in tests via embedded test harness.
### Access context
Not applicable.
### Node.js version
Node 20 in CI-compatible local development.
### Operating system
macOS local development, Linux CI-compatible tests.
### Relevant logs or output
The regression suite added in this PR covers the failing paths:
```shell
pnpm exec vitest run server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts
```
### Relevant config (if applicable)
```json
{
"heartbeat": {
"maxDailyRuns": 1,
"maxDailyCostCents": 1,
"skipTimerWhenNoActionableWork": true
}
}
```
### Additional context
This affects recurring/autonomous operation because the safest place to
stop excess work is before adapter execution starts.
### Privacy checklist
Reviewed for sensitive data; no private logs, credentials, or local
instance URLs are included.
## What Changed
- Added heartbeat policy parsing for per-agent daily run caps, daily
cost caps, and opt-in no-actionable-work timer skipping.
- Added pre-queue daily cap checks while preserving same-issue wake
coalescing.
- Added claim-time cap checks so already queued runs are cancelled
before adapter execution when a cap is reached.
- Added skipped wakeup metadata for cap and timer fast-exit decisions.
- Released issue execution locks for queued issue runs cancelled by
daily caps, with deferred wake promotion and without immediate recovery
loops while caps are active.
- Added regression coverage for timer skipping, proactive default
behavior, run caps, cost caps, queued-run cancellation, started
cancelled runs, and deferred issue wake promotion.
## Verification
- `git diff --check`
- `node -c server/src/services/heartbeat.ts`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts`
- `pnpm --filter @paperclipai/server typecheck`
- Local autoreview: `skills/autoreview/scripts/autoreview --mode branch
--base origin/master --engine codex --model gpt-5.5 --thinking high`
- Result: clean, no accepted/actionable findings
## Risks
- Medium operational risk because this changes heartbeat scheduling and
claim-time behavior.
- The no-actionable-work timer fast-exit is explicitly opt-in to avoid
suppressing proactive agents unexpectedly.
- Daily run caps count runs by `startedAt` so old queued rows do not
consume today’s cap, while started runs still count even if they later
end as cancelled.
- Queued issue-run cap cancellation uses the existing release/promotion
path with immediate recovery suppressed to avoid retry loops while caps
are active.
## Model Used
Codex with GPT-5.5 high reasoning assisted with implementation, local
testing, and autoreview. The final review gate used local autoreview
with `gpt-5.5` high 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)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Local adapters turn Paperclip runs into unattended CLI invocations.
> - The `gemini_local` adapter depends on Gemini CLI behaving
non-interactively in headless worker sessions.
> - When Gemini CLI falls back to browser-based auth, the process can
stall at startup instead of producing stream-json output.
> - The adapter should make headless intent explicit and turn missing
auth into a classified, fast failure.
> - This pull request hardens the Gemini child-process environment and
parser classification around that failure mode.
> - The benefit is that operators get actionable `gemini_auth_required`
failures instead of silent hung runs.
## Linked Issues or Issue Description
No exact public issue exists for this runtime stall.
Related: #2344 covers a Gemini CLI adapter environment/auth probe
failure. This PR addresses a different runtime path: unattended
`gemini_local` executions that can stall when Gemini CLI attempts
interactive browser auth in a headless session.
Bug summary:
- Adapter: `gemini_local`
- Symptom: child Gemini CLI process starts but produces no stream-json
output when auth requires interactive browser flow
- Expected: unattended runs either produce stream-json output or fail
quickly with a classified auth error
- Actual: the run can hang at invocation until an external watchdog
kills it
- Scope: Gemini adapter process env, auth-required parsing, and adapter
docs/tests
Duplicate search performed:
- `gh pr list --state all --search "gemini headless invocation stall"`
found only this PR.
- `gh pr list --state all --search "gemini NO_BROWSER NO_COLOR"` found
only this PR.
- `gh issue list --state all --search "gemini headless authentication"`
found related issue #2344 but no exact duplicate.
## What Changed
- Set a headless-safe Gemini invocation env at the final child-process
boundary: `TERM=xterm-256color`, `COLORTERM=truecolor`, and
`NO_BROWSER=1`.
- Delete inherited `NO_COLOR` from the child env so Gemini CLI keeps
color-capable terminal behavior when deciding whether it can run
non-interactively.
- Classify Gemini `FatalAuthenticationError: Manual authorization is
required...` failures as `gemini_auth_required`.
- Update Gemini adapter docs to describe the non-interactive `--prompt`
path and headless env behavior.
- Add/extend focused parser and remote execution tests for the
auth-required and env invariants.
## Verification
- `pnpm exec vitest run
packages/adapters/gemini-local/src/server/parse.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts` — 23
tests passed.
- `pnpm --filter @paperclipai/adapter-gemini-local typecheck` — passed.
- Local Gemini CLI probe confirmed `NO_BROWSER=1` turns the browser-auth
stall into a fast auth error instead of an interactive wait.
## Risks
Low risk. The change is limited to `gemini_local` invocation env,
parsing, docs, and tests. It does not touch schemas, API routes,
persisted data, or other adapters.
Operational risk: environments that intentionally rely on `NO_COLOR`
being inherited by Gemini child processes will no longer pass that
variable through. That is intentional here because Gemini CLI
auth/headless behavior should not depend on inherited color suppression.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex via Codex CLI, with shell/file-editing tool use for
repository inspection, code edits, tests, and GitHub PR maintenance.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
/cc @codex — please review.
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The productivity-recovery subsystem watches for "stranded" assigned
issues — claims whose live run disappeared — and repairs, resumes, or
visibly blocks them
> - When an executor decomposes an umbrella issue into sub-tasks, it
parks its own continuation as "waiting on review/approval" (error code
`issue_continuation_waiting_on_review`) — a deliberate pause, not a lost
run
> - Recovery's staleness gate mistook that deliberate park for a
disappeared run: it retried once, then escalated the issue to `blocked`
with a recovery action and an operator-facing failure notice — even
though nothing had failed and there was nothing for a human to do
> - The user is left staring at an inscrutable, over-technical
"stranded" error on a task they did nothing wrong with, with no idea
what action to take
> - This pull request teaches recovery to recognize a review-parked
continuation and, when the issue has a real waiting target (open
sub-tasks or unresolved blockers), convert it into a first-class
dependency wait: `blocked`-by-children, original assignee kept, plus a
plain-language comment saying it will resume automatically
> - The benefit is that post-decomposition umbrellas sit on a real
waiting path and self-resume through the normal blockers-resolved flow,
while genuine strands (no waiting target) still escalate exactly as
before
## Linked Issues or Issue Description
Refs #6503
## What Changed
- `server/src/services/recovery/service.ts`: add
`resolveContinuationWaitingOnReview`. When a continuation was cancelled
with `issue_continuation_waiting_on_review` and the issue has a real
waiting target — open (non-terminal) sub-tasks or existing unresolved
blockers — recovery sets the issue `blocked` by those issues, keeps the
original assignee, posts a plain-language `system` comment, and logs the
activity. Wired into `reconcileStrandedAssignedIssues` ahead of the
escalation path, with a new `waitingOnReviewResolved` counter on the
result.
- With no waiting target, the code falls through to the existing
escalation, preserving genuine stranded-run detection.
- `server/src/__tests__/heartbeat-process-recovery.test.ts`: two new
tests — (1) a review-parked continuation converts into a dependency wait
on its open sub-tasks (done children excluded, no recovery issue opened,
plain-language comment, raw error code never leaks), and (2) it still
escalates when no open dependency remains.
- `doc/execution-semantics.md`: document the "Deliberate wait is not a
lost run" recovery rule and the requirement that a post-decomposition
umbrella hold a first-class waiting path rather than relying on
`parentId` rollup.
## Verification
- `cd server && npx tsc --noEmit` — passes against current `master`.
- New tests in `server/src/__tests__/heartbeat-process-recovery.test.ts`
(describe: "heartbeat orphaned process recovery"):
- "converts a continuation parked for review into a dependency wait on
its open sub-tasks"
- "still escalates a continuation parked for review when no open
dependency remains"
- Run with the repo's vitest setup, e.g. `pnpm vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts` (requires the
embedded-postgres test harness).
## Risks
Low. The change adds a single guarded pre-check ahead of the existing
escalation path; behavior is unchanged when the cancellation error code
is not `issue_continuation_waiting_on_review` or when the issue has no
open sub-task / unresolved blocker to wait on. No schema or migration
changes.
## Model Used
Claude (Anthropic), Opus-class model, via the Claude Code agent harness
— extended thinking 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 and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots (N/A — server-only)
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending CI)
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work, with a web UI that must work on mobile as well as desktop.
> - The UI layout root in `ui/src/components/Layout.tsx` branches on
`isMobile` to choose between a desktop flex column that clips overflow
and a mobile container.
> - The mobile branch was only `min-h-dvh` — it had no
horizontal-overflow guard, while the desktop branch already used
`overflow-clip`.
> - As a result, any descendant wider than the screen (a long
unbreakable token, an over-wide element) pushed the entire viewport
sideways, producing horizontal scroll on the whole page.
> - This pull request adds `overflow-x-clip` to the mobile root
container so stray wide descendants are clipped to the viewport width.
> - The benefit is a durable, page-agnostic guard: it prevents this
class of bug on every mobile page regardless of which element overflows,
without breaking vertical body scroll or the sticky breadcrumb.
## Linked Issues or Issue Description
Fixes: #8369
Related (different scope — these fix horizontal scroll *inside* a
specific component's own container, not the viewport-level layout
guard): Refs #2128.
## What Changed
- `ui/src/components/Layout.tsx`: added `overflow-x-clip` to the mobile
(`isMobile`) layout root container.
- Used `clip` rather than `hidden` deliberately: `clip` leaves
`overflow-y` computed as `visible`, so native body scrolling and the
sticky breadcrumb keep working; `hidden` would have forced a scroll
container and broken them.
- `ui/src/components/Layout.test.tsx`: added a regression test asserting
the mobile root carries `overflow-x-clip` (and not `overflow-hidden`)
and the desktop root carries `overflow-clip`.
## Verification
- `pnpm --filter @paperclip/ui test src/components/Layout.test.tsx` → 12
passed (10 existing + 2 new).
- Manual: open the web UI at a mobile-width viewport (≤ 768px) on a page
with a wide/unbreakable descendant (e.g. a task whose body contains a
long unbreakable token).
- Before: the entire page scrolls horizontally.
- After: the overflow is clipped to the viewport width; the page no
longer scrolls sideways, and vertical body scroll plus the sticky
breadcrumb continue to work.
- The change is a single Tailwind utility on the mobile branch only;
desktop layout is unchanged (it already used `overflow-clip`).
## Risks
Low risk. The change only adds horizontal-overflow clipping to the
mobile layout root. It does not affect the desktop branch, vertical
scrolling, or any component internals. Components that need to scroll
horizontally manage their own internal overflow and are unaffected.
## Model Used
Claude Opus 4 (claude-opus-4) via the Claude Code agent harness, with
extended thinking and tool use.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Every PR runs the `PR` GitHub Actions workflow, whose `verify` gate
fans out into parallel test lanes (general tests, serialized server
route suites, build, typecheck)
> - The `General tests (server)` lane had grown into the run's critical
path: it executed all ~213 non-route server suites serially in a single
job (~7.2m of test time), more than 2x any other job
> - It runs serially because `server/vitest.config.ts` pins `maxWorkers:
1`, so server suites cannot parallelize within a single runner — the
only lever is spreading them across runners
> - This pull request shards that lane into 3 even partitions that run
on separate runners, mirroring the 4-way sharding already used for the
serialized route suites
> - The benefit is the lane drops from ~7.7m to ~2.4m/shard, cutting
overall PR wall time roughly in half (~8.5m → ~4.2m)
## Linked Issues or Issue Description
No public GitHub issue exists for this work, so the underlying issue is
described inline following the feature-request template.
### Problem or motivation
PR CI wall time had crept back up to ~8.5m. On a recent fully-green run,
the `General tests (server)` job took 7.72m — more than double any other
job and the clear critical path. Of that, 7.23m was pure test execution
(dependency install was a cached 0.27m). The job ran all server suites
that are not route/authz tests (213 files) one after another, because
the server vitest project pins `maxWorkers: 1`, making these suites
inherently serial within a single runner.
### Proposed solution
Shard the general-server lane across 3 parallel runners — the same
technique the route/authz suites already use — so the suite set is split
into even, deterministic partitions that run concurrently. Add a
regression test that proves the shards always cover the full suite set
with no gaps or overlap.
### Alternatives considered
- **Raise `maxWorkers` for the server project** to parallelize within
one runner — rejected: the server suites share process-level state
(DB/port), which is exactly why `maxWorkers: 1` is pinned.
- **Two shards instead of three** — would leave the lane at ~3.6m, still
above the next bottleneck (Canary Dry Run, ~4.1m wouldn't be the gate).
Three lands the lane comfortably below it.
- **Do nothing / accept the slow lane** — rejected: it gates every PR.
### Roadmap alignment
Developer-experience / CI tooling. Not core product roadmap work; does
not overlap with planned features in `ROADMAP.md`.
## What Changed
- `scripts/run-vitest-stable.mjs`: the `general-server` general-test
group now accepts `--shard-index` / `--shard-count`. It enumerates the
full server test set (the whole `server/src` tree, minus the route/authz
suites that already run in their own serialized shards) and splits it
deterministically by modulo. The non-sharded local invocation (`pnpm
test:run:general --group general-server`) is unchanged.
- `.github/workflows/pr.yml`: the `general_tests` matrix runs
`general-server` as 3 parallel shards (1/3, 2/3, 3/3). Workspace groups
are unchanged. The `verify` gate already aggregates the whole matrix
result, so the required check name is unaffected.
- `scripts/__tests__/run-vitest-stable-shard.test.mjs`: a `node:test`
suite asserting the 3 shards form a complete, non-overlapping partition
of the general-server set, that no route/authz suite leaks into it, and
that shard flags are rejected for the parallel workspace groups. Wired
into the `policy` job.
## Verification
- New partition test passes locally: `node --test
./scripts/__tests__/run-vitest-stable-shard.test.mjs` (3/3).
- Confirmed the 3 shards form a complete, non-overlapping partition of
all 213 files (71/71/71).
- Ran a live thin shard (3 real server suites, including one outside
`__tests__`) — 23 tests passed, confirming positional-include execution
works end to end.
- This PR's own CI is the authoritative check: all three `General tests
(server (n/3))` jobs went green on the prior run, collectively covering
every suite the old single job ran.
## Risks
- Low risk. No product code changes — only test orchestration and CI
matrix. Shard partitioning is deterministic and is now covered by an
automated test that fails if the partition ever develops a gap or
overlap. Modulo-on-sorted-filenames balances duration reasonably,
matching the approach already proven by the serialized route shards.
## Model Used
- Claude (Anthropic), `claude-opus-4-8`, extended thinking + tool use
(agentic coding via Paperclip).
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots (N/A — no UI change)
- [x] I have updated relevant documentation to reflect my changes
(inline comments explain the sharding rationale)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending this PR's run)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>