fix: [CL-SYNC] merge upstream paperclip v2026.626.0 + renumber forked migrations #123
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "chore/claude-upstream-sync-626"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Thinking Path
Linked Issues or Issue Description
Внутренняя задача синхронизации форка (без публичного GitHub-issue) — описание по feature-шаблону:
Problem
Форк отставал на релиз:
v2026.618.0против upstreamv2026.626.0. Прошлая попытка merge осталась незакоммиченной, фактического апдейта не произошло. Параллельная разработка форка и upstream дала дубли номеров drizzle-миграций.Proposed solution
Влить upstream-тег
v2026.626.0, сохранив наши кастомизации (heartbeat self-learning, codex-local MCP-merge #122, autonomy claude-quota holds), и перенумеровать наши миграции0103-0115 → 0125-0137под уже обновлённый drizzle journal.Alternatives
Полный rebase форка на upstream рассмотрен и отклонён: 759 конфликтов уже разрешены в merge-подходе, rebase переиграл бы их на каждый коммит и потерял бы контекст наших кастомизаций.
What Changed
v2026.626.0(759 файлов конфликтов): GitHub Actions workflows оставлены удалёнными (Forgejo-only), upstream-перезаписи приняты, наши кастомизации сохранены.origin/master— вернул 10 наших свежих фиксов поверх upstream-базы.codex-home.ts: восстановлен наш managed-MCP блок (mergeManagedMcpConfig,ensureManagedMcpConfig) и его call-site вprepareManagedCodexHome(upstream-rewrite снёс вызов); объединены оба const.codex-home.test.ts: объединены наборы тестов обеих сторон.fix(db): переименованы 13 миграций0103-0115 → 0125-0137под уже перенумерованный drizzle journal (устранены дубли номеров).skills-catalogманифест.Verification
pnpm --filter @paperclipai/adapter-codex-local typecheck→ clean.vitest run codex-home.test.ts→ 24/24 passed (включаяmergeManagedMcpConfig).pnpm --filter @paperclipai/server typecheck→ EXIT 0.pnpm --filter @paperclipai/db check:migrations→ EXIT 0 (journal ↔ files 1:1).pnpm --filter @paperclipai/server... build→tscпроходит; локально падает только Windows-only asset-copy шаг (mkdir -p/cp -R), на Linux-CI отрабатывает.Risks
0103-0124— проверяется черезcheck:migrationsиdb:migrateс rollback на деплое.0125-0137не обновлены: runtimemigrateиcheck:migrationsих не читают, влияет лишь на будущийdrizzle-kit generate(отдельный follow-up).@paperclipai/hermes-paperclip-adapterпришла из upstream-merge.Model Used
None — human-authored. Автор изменений — Andrei (EuropaTech), единственный автор кода.
Checklist
**Issue (described inline; no existing tracking issue):** Pausing an agent is not durable. Pausing cancels the in-flight run, but a queued or recovery-dispatched run can clobber the agent back to `running` because the execution-start status update is unconditional — so a "paused" agent silently resumes work while `paused_at` is still set. ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies; agent work executes as "runs" tracked in `heartbeat_runs`, with a recovery/automation layer that re-dispatches work when a run disappears. > - The agent lifecycle has a pause control (status `paused`, `paused_at` set) meant to stop an agent from taking or continuing work. > - The problem: pause is not durable. Pausing cancels the in-flight run, but the execution-start path then sets `agents.status = 'running'` with an unconditional `UPDATE ... WHERE id = ?`, so any queued or recovery-dispatched run can clobber the paused agent back to `running` and execute. > - Why it matters: a "paused" agent silently resuming undermines the core operational control operators rely on to halt runaway, cost-sensitive, or unsafe work. > - This pull request guards the execution-start status flip with an atomic conditional UPDATE, and tags pause-cancellations for observability without changing resume behaviour. > - The benefit is that a paused agent can no longer transition back to `running`; queued/recovery-dispatched runs are cancelled cleanly instead of clobbering status, while un-pausing still resumes in-flight work. ## What Changed - Execution-start guard: replaced the unconditional `UPDATE agents SET status='running' WHERE id = ?` with an atomic conditional `UPDATE ... WHERE id = ? AND status NOT IN ('paused','terminated','pending_approval')`. On a zero-row match the run is cancelled (`errorCode: "agent_not_invokable"`), the issue execution lock is released, and the path returns — instead of clobbering status. - Exported `DIRECT_NON_INVOKABLE_STATUSES` from `agent-invokability.ts` and reused it in `heartbeat.ts` as the single source of truth for the guard. - Pause observability: `cancelActiveForAgentInternal` now accepts an `errorCode` (default `"cancelled"`); the pause-route wrapper `cancelActiveForAgent` passes `"agent_paused"`. This is classification-neutral — `agent_paused` is NOT added to `NON_RETRYABLE_CONTINUATION_ERROR_CODES`, so on un-pause the issue's continuation re-enqueues and work resumes. - Exported `classifyContinuationFailure` from `recovery/service.ts` for unit testing (no logic change). - Added `server/src/services/recovery/service.pause-durability.test.ts` covering continuation classification. ## Verification - `pnpm --filter @paperclipai/server typecheck` — clean. - `pnpm exec vitest run server/src/services/recovery/service.pause-durability.test.ts` — 5 passed. - `pnpm exec vitest run server` — full server suite passes locally. The only failures are pre-existing and environment-specific, unrelated to this change (a git default-branch test fixture, and a known checkout-lock race) — both reproduce identically on clean `master` with this change stashed out. - Behavioural: a paused agent's execution-start now aborts cleanly with no status clobber; non-pause cancellations keep `errorCode "cancelled"` and existing behaviour; un-pausing resumes the in-flight issue. ## Risks - Low risk. No schema change, no migration, no new dependency; four files. The change narrows a single UPDATE to be conditional and adds a rarely-taken abort branch on the run-start path; behaviour for invokable agents is unchanged. The abort's `agent_not_invokable` code is already in `NON_RETRYABLE_CONTINUATION_ERROR_CODES`. The only caller of `cancelActiveForAgent` is the pause route. ## Related upstream work — not duplicates This area has prior and in-flight PRs; #8317 was checked against them and is intentionally distinct: - **#4503** (`fix(heartbeat): make agent pause status guard atomic with status update`) targets a different TOCTOU race on the **post-run / finalize** path (`finalizeAgentStatus`). #8317 targets the **execution-start** race, where a recovery-dispatched run flips a paused agent back to `running` *before the run begins*. #4503 does not cover the proven failure path here: `pause → active run cancelled → recovery dispatches a new run → execution-start overwrites the paused state`. #4503 also does not add the resume semantics below. - **#4356** (`honor system/manual/auto pause at all heartbeat-run enqueue sites`) and **#1067** (`pause guard on queue drain`) protect the **enqueue / queue-drain** layer. They are complementary to — not substitutes for — the execution-start guard, which is the last gate before a run actually starts. - **#6944** (`guard executeRun against paused agent`), **#7140**, and **#7141** attempted similar execution-start ideas but were closed for implementation hygiene / build issues, not because the guard concept was wrong. #8317 implements that concept cleanly: a single atomic conditional UPDATE, a clean abort with `errorCode: "agent_not_invokable"`, a shared `DIRECT_NON_INVOKABLE_STATUSES` source of truth, and passing tests + typecheck. Intentional, minor difference (not a criticism of #4503): #8317's execution-start deny-list is `paused`, `terminated`, and `pending_approval` — the full non-invokable set for run-start invokability — whereas #4503 appears focused on `paused`/`terminated`. The broader set is deliberate for the execution-start guard. Resume semantics: #8317 keeps `agent_paused` as observability-only and classification-neutral (retryable), so a paused agent's in-flight work resumes on un-pause rather than escalating to blocked. ## Model Used - Provider: Anthropic. Model: Claude Opus 4 (`claude-opus-4-8`), via the Claude desktop "Cowork" agent. Mode: agentic/extended reasoning with tool use (shell, file editing, running `tsc`/`vitest`, git). Used to investigate the root cause in source, design the fix, implement it, and validate locally. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots (N/A — no UI change) - [ ] I have updated relevant documentation to reflect my changes (N/A — no doc-facing change) - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge - [x] I have searched the open and closed PR list for similar/duplicate PRs and found none## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Creating an agent starts at **New Agent → "manually" → pick an adapter**, which routes to `/{company}/agents/new?adapterType=claude_local` and renders the `NewAgent` page with the `AgentConfigForm` > - `AgentConfigForm` hands the parent a `triggerTestEnvironment` callback via an `onTestActionChange` effect so the page can wire up its "Test"/"Save + Test" button > - That trigger was rebuilt on every render: it depended on `runEnvironmentTest`, which is derived from a react-query `useMutation` result, and `useMutation` returns a **brand-new result object identity on every render** > - So the `onTestActionChange` effect re-fired every render and pushed a new function into the parent's state, producing an infinite `setState` loop ("Maximum update depth exceeded") that threw during render > - The app's custom router updates location **without remounting**, and there was no error boundary around the routed outlet, so the throw left a dead render tree — a fully **blank page** that stayed blank on back-navigation until a hard refresh > - This pull request stabilizes the trigger with a latest-ref pattern so the effect no longer re-fires, and adds a route-keyed error boundary so any future render throw degrades to a recoverable error card instead of a blank screen > - The benefit is that creating an agent works again, and render-time failures anywhere in the routed UI are contained and recoverable rather than silently blanking the app ## Linked Issues or Issue Description No public GitHub issue exists, so the underlying bug is described inline following the bug report template (`.github/ISSUE_TEMPLATE/bug_report.yml`): ### What happened? In the UI, choosing **New Agent → "manually" → (any adapter, e.g. Claude)** navigates to the agent-config page and renders a **completely blank page**. The browser console shows React's `Maximum update depth exceeded`. Using the back button changes the URL but the page stays blank until a full hard refresh. ### Expected behavior Selecting an adapter shows the agent configuration form so the agent can be created. ### Steps to reproduce 1. Open the app and click **New Agent**. 2. Choose **manually**. 3. Pick an adapter (e.g. Claude / `claude_local`). 4. Observe the blank page (URL becomes `/{company}/agents/new?adapterType=claude_local`). ### Paperclip version or commit Reproduces on `master` (base of this PR). ### Deployment mode Reproduces regardless of deployment mode — it is a client-side render loop. ### Root cause `useMutation` returns a new result object identity each render, so the `runEnvironmentTest`-derived `triggerTestEnvironment` callback was unstable, which made the `onTestActionChange` effect push a new function into parent state every render → infinite update loop → render throw → no boundary → blank tree. ## What Changed - **`ui/src/components/AgentConfigForm.tsx`** — Stabilize the environment-test trigger handed to the parent using a latest-ref pattern: the churny behavior (`runEnvironmentTest`, `testEnvironmentDisabled`) lives in a `useRef` updated by an effect, and the exposed `triggerTestEnvironment` is a `useCallback(() => triggerRef.current(), [])` with an empty dep array, so its identity is stable across renders and the `onTestActionChange` effect no longer re-fires every render. - **`ui/src/components/RouteErrorBoundary.tsx`** (new) — A route-keyed React error boundary that catches render throws and renders a recoverable error card (showing the error message, with "Go back" and "Reload page" actions). It resets automatically when the route (`pathname + search`) changes. - **`ui/src/components/Layout.tsx`** — Wrap the routed `<Outlet />` in `<RouteErrorBoundary>` so a render throw degrades to the error card instead of a blank page. - **`ui/src/components/RouteErrorBoundary.test.tsx`** (new) — Regression test: a throwing child is contained as a recoverable error card (showing the message), "Go back" calls `navigate(-1)`, and the boundary resets to render children again after the route changes. ## Verification - `npx vitest run ui/src/components/RouteErrorBoundary.test.tsx` → 3 passed (regression test for this fix). - `npx vitest run ui/src/components/AgentConfigForm.test.ts` → 9 passed. - `npx tsc -b` in `ui` → 0 errors. - Manual: ran the dev server, clicked **New Agent → manually → Claude**. - **Before:** blank page; console logs `Maximum update depth exceeded`; back button leaves the page blank until hard refresh. - **After:** the agent configuration form renders normally and the agent can be created; navigating away and back works without a hard refresh. - Boundary check: with the loop still in place (pre-fix), the new boundary catches the throw and shows a recoverable error card instead of a blank screen; "Go back" / route change resets it. _Screenshots: the before-state is the React `Maximum update depth exceeded` error and a blank `/agents/new` page; the after-state is the rendered agent-config form. Both were observed locally; rendered images can be attached on request._ ## Risks - **Low risk.** Changes are confined to three UI files with no API, schema, or behavioral change to agent creation beyond fixing the loop. - The latest-ref pattern preserves identical runtime behavior of the test trigger (same guard, same `runEnvironmentTest()` call) — it only stabilizes the callback identity. - The error boundary is additive; on the happy path it renders its children unchanged. Its only behavior is to catch render throws that previously blanked the app. ## Model Used Claude (Anthropic), model `claude-opus-4-8` — extended-thinking-capable, tool-use (file edit, shell, tests). Used to diagnose the infinite render loop, implement the latest-ref fix and route error boundary, and verify via local typecheck/tests. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] 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 (described textually in Verification — see note) - [ ] I have updated relevant documentation to reflect my changes (N/A — bug fix, no docs affected) - [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. > - 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 mergeplugin target(#8575) 1951c80237## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agent runs stream their transcripts through per-adapter stdout parsers into the chat/run transcript UI (`ui/src/adapters/transcript.ts`) > - The Cursor CLI (local) streams assistant text as many small `text` events (often a token or word each), and the parser emitted one assistant entry per event and trimmed each > - As a result the chat rendered one bubble per token ("every line a new token") and dropped inter-token whitespace, making Cursor runs hard to read > - The render layer already coalesces consecutive `delta` entries (`appendTranscriptEntry`), but the Cursor parser never tagged streamed text as a delta > - This pull request tags streamed `text` as a delta (without trimming) so the existing render-time coalescer merges them into one assistant block, while a `tool_call`/`tool_result` between deltas still breaks the run > - The benefit is readable Cursor transcripts with correct spacing and preserved tool boundaries, with no change to the canonical event stream (raw view unaffected) ## Linked Issues or Issue Description No existing public issue — describing the bug inline (per `.github/ISSUE_TEMPLATE/bug_report.yml`): **What happened** In the chat/run transcript, Cursor (local) assistant messages render as one bubble per token/word, and inter-token spaces are dropped, making the transcript unreadable. Root cause: `packages/adapters/cursor-local/src/ui/parse-stdout.ts` (`type: "text"` branch) emitted `{ kind: "assistant" }` per streamed `text` event without `delta: true` and trimmed each, so the render-time coalescer (`ui/src/adapters/transcript.ts`) never merged them and whitespace was lost. **Expected behavior** Streamed assistant text should render as a single contiguous prose block, with tool calls preserved as boundaries between blocks. **Steps to reproduce** 1. Run a Cursor (local) agent that streams a multi-word assistant message. 2. Open the run transcript in the chat UI. 3. Observe each streamed token/word rendered as its own bubble, with inter-token spaces missing. **Paperclip version** Reproduced on current `master` (cutover base `e68188c43`). **Deployment mode** Self-hosted, `cursor_local` adapter. ## What Changed - `packages/adapters/cursor-local/src/ui/parse-stdout.ts`: tag streamed `text` events as `{ kind: "assistant", delta: true }` and stop trimming, so the existing `appendTranscriptEntry` coalescer merges consecutive deltas into one block. - `ui/src/adapters/cursor-coalescing.test.ts` (new): dual-shape golden fixtures (Cursor local + cloud) exercising the full render-time projection via `buildTranscript`. ## Verification - `pnpm --filter @paperclipai/ui exec vitest run src/adapters/cursor-coalescing.test.ts src/adapters/transcript.test.ts` → **10/10 pass**. - `pnpm --filter @paperclipai/ui --filter @paperclipai/adapter-cursor-local typecheck` → **green**. - The golden fixtures assert the run `text → tool_call → tool_result → text → consolidated final` renders as exactly **two prose blocks with the tool between them**, **no duplication** of the consolidated final, and **inter-token whitespace preserved** across coalesced deltas. ## Risks - **Low risk.** Pure classification at parse time; the canonical event stream and the raw view are unchanged — only the "nice" render-time projection changes. The coalescing logic (`appendTranscriptEntry`) is pre-existing and already covered by tests. No schema, migration, or behavioral change outside transcript rendering. ## Model Used - **Claude Opus 4.8** (Anthropic), extended/high reasoning mode, driven via the Cursor agent with tool use + code execution. Diagnosis and fixtures grounded in the repo's actual parser/render code. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above (none found) - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub references) - [x] My branch name describes the change (`fix/cursor-transcript-coalescing`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] I have updated relevant documentation to reflect my changes (N/A — no documented behavior changes) - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green (pending CI run) - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups (pending review) - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Sebastian Heyneman <sebastian@joinnova.com> Co-authored-by: Cursor <cursoragent@cursor.com>## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The dev/server UI exposes a `/api/health` endpoint and a lower-left account drawer, but nothing surfaces *which* build the running instance is on or when it last restarted > - When iterating on a local dev instance it is hard to tell whether the server you're looking at has actually restarted onto your latest commit, or how stale the running process is > - Developers need a lightweight, opt-in way to confirm the running instance's identity without digging through logs or shelling into the host > - This pull request adds an experimental "Server Info Debug View" setting that surfaces the running instance's last-restart time and current commit as read-only rows in the account drawer > - The benefit is a quick, in-UI sanity check of what the live server is actually running, behind an experimental flag so it ships zero cost to users who don't opt in ## Linked Issues or Issue Description No public GitHub issue exists. Describing the underlying request inline following the feature request template: **Problem or motivation:** When working against a local Paperclip dev instance there is no in-UI way to confirm what the running server is — its current commit or when it last restarted. You have to check logs or the host shell to know whether the process picked up your latest build. **Proposed solution:** An opt-in experimental setting ("Server Info Debug View") that, once enabled, renders a small read-only "Server" section at the bottom of the lower-left account drawer showing **Last restarted** (the server process start time) and **Running commit** (the current git HEAD short SHA + subject). **Alternatives considered:** A separate top-right pill/overlay (like the work-life-balance plugin). The account drawer was chosen to reuse existing menu-row styling and avoid adding new always-present chrome. **Roadmap alignment:** Small, self-contained developer-experience aid gated behind an experimental flag; does not overlap planned core roadmap work. ## What Changed - Added `server/src/server-info.ts`: captures a `serverInfo` snapshot once at boot — process start time and current git commit (SHA + subject). Git is read via `execFileSync` with SHA validation and a timeout. - `/api/health` exposes the `serverInfo` snapshot, but only on full-details health responses (board/agent in authenticated mode, or local-trusted dev). - Gated the UI surface behind a new `enableServerInfoDebugView` experimental setting, wired through the shared instance type, validator, settings normalizer, and OpenAPI schema. - UI: added `SidebarServerInfo` rendering the read-only rows in the account drawer (`BreadcrumbBar` / `SidebarAccountMenu`), plus the experimental settings toggle and a typed `health` API client. - Moved `ServerGitInfo` / `ServerInfoSnapshot` into `@paperclipai/shared` so the server and UI share one definition instead of duplicating it. - Added unit tests for the server-info snapshot, health route exposure, validator/normalizer, settings routes, the experimental settings page, and the sidebar component. ## Verification - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/health.test.ts src/__tests__/server-info.test.ts src/__tests__/instance-settings-service.test.ts src/__tests__/instance-settings-routes.test.ts` — 32 passed - `pnpm --filter @paperclipai/ui exec vitest run src/components/SidebarServerInfo.test.tsx src/pages/InstanceExperimentalSettings.test.tsx` — 9 passed - `tsc --noEmit` on both `@paperclipai/server` and `@paperclipai/ui` — clean - Manual: enable **Settings → Experimental → Server Info Debug View**, refresh the UI, open the lower-left account drawer — a "Server" section shows Last restarted and Running commit. ## Risks - Low risk. The UI surface is fully opt-in via an experimental flag and defaults off. - The `serverInfo` field on `/api/health` is access-controlled to full-details responses only (board/agent in authenticated mode, or local-trusted dev) — never anonymous authenticated callers — so the git SHA is not broadly exposed. - The only new server work is a one-time git read at boot, guarded with SHA validation and a timeout; failures degrade gracefully (the git block reports `available: false` rather than throwing). ## Model Used Claude — `claude-opus-4` (Anthropic), extended thinking with tool use, via Claude Code. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change and contains no instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge✅ All checks passing — ready for review and maintainer approval.
— paperclip-gates
[CL-SYNC] merge: upstream paperclip v2026.626.0 + renumber forked migrationsto chore: [CL-SYNC] merge upstream paperclip v2026.626.0 + renumber forked migrationschore: [CL-SYNC] merge upstream paperclip v2026.626.0 + renumber forked migrationsto fix: [CL-SYNC] merge upstream paperclip v2026.626.0 + renumber forked migrations