feat(merger-gates): implement Gate 1 (issue-level approval check)#4
Merged
Conversation
…i#7824) ## Thinking Path > - Paperclip is the open source control plane people use to manage AI agents, work, and company context. > - The board UI sidebar is the main way operators keep orientation across companies, projects, agents, issues, and settings. > - The existing fixed expanded sidebar competes with route-specific navigation, especially company settings and plugin routes that bring their own contextual sidebar. > - A collapsible primary rail preserves global navigation while giving contextual pages more horizontal room. > - This pull request adds a persisted collapsed rail, hover/focus peek, keyboard toggle, and a secondary sidebar takeover model for settings and plugin `routeSidebar` surfaces. > - The benefit is a denser board shell that keeps the app rail available without replacing it when a route needs its own navigation. ## Linked Issues or Issue Description Paperclip issue: PAP-10638 Create collapsible sidebar branch. Related GitHub PR found during duplicate search: paperclipai#3838 (`feat/collapsible-sidebar`) covers a similar sidebar area but is a different head branch and implementation. This PR intentionally packages the work from `PAP-10638-collapsable-sidebar` into one reviewable branch. Problem description: The board shell needs a first-class collapsed sidebar mode. Contextual surfaces such as company settings and plugin route sidebars should not replace the global app sidebar; they should collapse the app sidebar to a rail and render their contextual navigation beside it. ## What Changed - Added desktop collapsed/sidebar-peek state to `SidebarContext`, including persisted user pins, route collapse requests, and forced collapse for secondary-sidebar routes. - Replaced the old resizable sidebar pane with `SidebarShell`, which supports a fixed 64px rail, persisted expanded width, keyboard/pointer resizing, and hover/focus peek overlay behavior. - Updated `Sidebar`, sidebar nav items, project/agent sections, badges, and account/company menu presentation for expanded, collapsed, and peeking states. - Added `RequestCollapsedSidebar` and `SecondarySidebar` so routes and plugin `routeSidebar` slots can request contextual sidebar layouts without replacing the primary app sidebar. - Wired company settings and plugin route sidebars into the secondary-pane takeover model. - Added focused Vitest coverage for sidebar state precedence, shell sizing, nav item rail rendering, keyboard shortcuts, layout takeover behavior, and route collapse requests. - Updated plugin authoring docs/spec references for route sidebar behavior. ## Verification Targeted local verification passed: ```sh NODE_ENV=test pnpm run preflight:workspace-links && NODE_ENV=test pnpm exec vitest run ui/src/context/SidebarContext.test.tsx ui/src/components/SidebarShell.test.tsx ui/src/components/Sidebar.test.tsx ui/src/components/Layout.test.tsx ui/src/components/RequestCollapsedSidebar.test.tsx ui/src/components/SidebarNavItem.test.tsx ui/src/components/SidebarAgents.test.tsx ui/src/components/SidebarProjects.test.tsx ui/src/components/KeyboardShortcutsCheatsheet.test.tsx ui/src/hooks/useKeyboardShortcuts.test.tsx ``` Result: 10 test files passed, 88 tests passed. Additional follow-up verification passed after review fixes: ```sh NODE_ENV=test pnpm run preflight:workspace-links && NODE_ENV=test pnpm exec vitest run ui/src/components/Layout.test.tsx ui/src/context/SidebarContext.test.tsx && pnpm --filter /ui typecheck ``` Result: 2 test files passed, 28 tests passed, and UI typecheck passed. Latest PR-head remote checks: Paperclip PR workflow, Snyk, Socket, and Greptile are green; commitperclip `review` is cancelled in its security-gate step after filing a non-blocking neutral `security-review` check. Notes: - A direct run without `NODE_ENV=test` loads React's production build in this workspace, where `act` is unavailable; the command above matches the repo stable runner's test environment. - I did not run Playwright/browser e2e or full workspace build/typecheck in this PR-creation heartbeat. - QA screenshots are attached in paperclipai#7824 (comment) for expanded, collapsed rail, hover peek, and settings secondary-sidebar states. ## Risks - Medium UI layout risk: this changes the board shell and primary sidebar composition across many routes. - Local storage migration risk is low: new collapsed state uses a new key and existing width storage remains scoped to the sidebar width. - Plugin route risk: plugin `routeSidebar` slots now render as secondary panes on desktop, so plugin authors should confirm their route sidebar content fits a 240px contextual pane. - Mobile risk appears low because mobile keeps the drawer model and gates collapsed/peek behavior to desktop. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex coding agent based on GPT-5, with local shell/git/GitHub CLI tool use. Exact service-side model identifier and context window were not exposed in this runtime. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Paperclip <noreply@paperclip.ing>
## Summary Adds the newly released Claude models from the [models overview](https://platform.claude.com/docs/en/about-claude/models/overview) to the `claude_local` adapter's model selector: - **Claude Fable 5** (`claude-fable-5`) — generally available as of 2026-06-09, Anthropic's most capable widely-released model. - **Claude Mythos 5** (`claude-mythos-5`) — limited availability (Project Glasswing). **Opus 4.8 stays first in the list so it remains the default selection** — per the request, the new flagship models are *offered* but not defaulted (not Fable, not Mythos). ## Changes - `packages/adapters/claude-local/src/index.ts` — add `claude-fable-5` and `claude-mythos-5` to the adapter model list, right after `claude-opus-4-8`. - `packages/adapters/claude-local/src/server/models.ts` — add the Fable 5 Bedrock identifier (`us.anthropic.claude-fable-5-v1`) to the Bedrock fallback list. Mythos 5 is limited-availability on Bedrock, so it's intentionally left out of that fallback. - `server/src/__tests__/adapter-models.test.ts` — assert the new models are present and that `claude-opus-4-8` remains first (the default). These flow through the single `claudeModels` source, so they also appear in the ACPX combined list (`registry.ts` prefixes them with `Claude:`) and are recognized by the ACPX Claude model filter. The UI selector reads models dynamically from the adapter, so no UI changes are needed. ## Testing - `npx vitest run src/__tests__/adapter-models.test.ts` — 13 passed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Summary Adds the user-facing stable release changelog for **v2026.609.0** (released 2026-06-09), generated per `.agents/skills/release-changelog/SKILL.md` from the diff between `v2026.529.0` (last stable) and `origin/master` — 119 non-merge commits. ## Highlights covered - Company Artifacts (page, task-stack grouping, playback, video thumbnails) - Collapsible sidebar rail and takeover panes - Rich issue attachments with video - Checkbox confirmation interactions - Information Architecture refresh (experimental) + instance settings under company settings - Automated PR quality/security gates + low-trust review containment ## Notes - No breaking changes — all 5 new migrations (0094–0098) are additive (backfills, tombstones, annotation links, source-trust tagging, project icon). - Contributor list excludes founders and bots per skill rules. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…i#7839) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The release workflow publishes canary npm packages on every push to `master` > - The failing canary job built successfully and published several packages before npm failed on `@paperclipai/mcp-server` > - The concrete failure was npm trusted-publishing provenance returning `TLOG_CREATE_ENTRY_ERROR` because an equivalent Sigstore transparency-log entry already existed > - The package version was not visible on npm afterward, so the release script could not safely treat that error as success by itself > - This pull request adds a narrow recovery path for that npm provenance failure and keeps the existing registry verification as the final source of truth > - The benefit is that transient duplicate transparency-log failures do not break canary publication when a package can be republished without provenance or is already visible on npm ## Linked Issues or Issue Description Bug fix, no public GitHub issue found in duplicate search. - What happened: the Release workflow canary publish failed in `publish_canary` after npm returned `TLOG_CREATE_ENTRY_ERROR` while publishing `@paperclipai/mcp-server@2026.609.0-canary.2`. - Expected behavior: canary publishing should either recover from npm's duplicate transparency-log failure when the package can still be published, or fail later in registry verification if the package never appears. - Steps to reproduce: inspect https://github.com/paperclipai/paperclip/actions/runs/27230012891/job/80411422155 from push `05cb18cf28074a6d1074c7575c5a44133146e368`. - Deployment mode: GitHub Actions Release workflow, npm trusted publishing. - Duplicate search: no open PRs or issues found for `canary publish TLOG provenance release` or the failing run/job IDs. ## What Changed - Added `publish_package_to_npm` in `scripts/release-lib.sh` to wrap canary/stable package publishing. - Detects npm's duplicate Sigstore transparency-log error and checks whether the package version is already visible on npm. - Retries that exact package once with `--provenance=false` when npm hit the duplicate tlog error but the version is not visible yet. - Keeps unrelated publish failures as hard failures. - Added shell-helper tests with fake `pnpm` and `npm` commands, and included them in `pnpm test:release-registry`. ## Verification - `node --test scripts/release-lib.test.mjs` - `pnpm test:release-registry` - Confirmed `pnpm publish --dry-run --no-git-checks --tag canary --access public --provenance=false` is accepted by pnpm 9.15.4. ## Risks - Low risk: the recovery only triggers when npm output contains both `TLOG_CREATE_ENTRY_ERROR` and the duplicate transparency-log message. - Publishing without provenance is a fallback for canary continuity; if npm still does not expose the package, the existing registry verification step still fails the release. - The same helper is used by stable publishing too, but only for this exact npm provenance failure 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`. This is a release reliability bug fix. I checked `ROADMAP.md`; it does not duplicate planned core product work. ## Model Used OpenAI Codex coding agent, GPT-5-class model, tool-enabled local shell and GitHub CLI workflow, medium reasoning mode. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - Each agent is woken via the heartbeat scheduler — `heartbeat_timer` for periodic interval wakes, `issue_assigned` / `execution_*` / `issue_commented` for event-driven wakes > - The heartbeat reuses the prior task session by default; only specific wake reasons trigger a fresh session via `shouldResetTaskSessionForWake` (assignment, review, approval, changes-requested) or explicit `forceFreshSession` > - In CEO run `292a5fd1`, repeated context compaction warnings appeared near the 64k threshold for the long-lived manager session — symptomatic of repeated `heartbeat_timer` wakes accumulating low-value "checked, nothing new" inbox-scan traces inside one ever-growing session > - PF-4 in the 2026-04-16 hangeul-school operational issue set asks for a compaction-aware session freshness policy: "manager sessions can rotate before low-value compaction pressure accumulates" and "repeated timer wakes do not indefinitely bloat the same session" > - This pull request adds `wakeReason === "heartbeat_timer"` to both `shouldResetTaskSessionForWake` and `describeSessionResetReason`, so each interval wake starts fresh and the run log explicitly records why. Event-driven wakes (`issue_commented`, `transient_failure_retry`, etc.) keep their existing reuse behavior. > - The benefit is that timer wakes — which are exploratory and carry no continuation state — stop bloating long-lived manager sessions. Compaction pressure that previously accumulated across N timer wakes is now bounded to a single interval's worth of context. ## Linked Issues or Issue Description No external GitHub issue is linked. Describing the problem inline following the bug-report template: **What happened:** Long-lived manager/CEO agent sessions hit the 64k context-compaction threshold after many `heartbeat_timer` wakes accumulated low-value inbox-scan traces inside one ever-growing task session. Reproduced in CEO run `292a5fd1`. **Expected behavior:** Periodic timer wakes — which carry no continuation state — should not indefinitely bloat the same session. The heartbeat should rotate sessions on timer wakes the way it already does on assignment/review/approval/changes-requested wakes. **Actual behavior:** `shouldResetTaskSessionForWake` only reset on `issue_assigned`, `execution_review_requested`, `execution_approval_requested`, `execution_changes_requested`, or explicit `forceFreshSession`. `heartbeat_timer` reused the prior session indefinitely, causing compaction pressure. **Scope of fix:** Add `heartbeat_timer` to the reset list and to `describeSessionResetReason` so the run log records why. Event-driven wakes keep their existing reuse behavior. ## What Changed - `shouldResetTaskSessionForWake` (`server/src/services/heartbeat.ts`) now also returns `true` when `wakeReason === "heartbeat_timer"`. The existing reset reasons (`issue_assigned`, `execution_review_requested`, `execution_approval_requested`, `execution_changes_requested`, `forceFreshSession`) are unchanged. - `describeSessionResetReason` returns a paired explanation `"wake reason is heartbeat_timer (timer-driven wake starts fresh)"` so run logs make session reset behavior legible. - `describeSessionResetReason` was promoted from internal to `export` so the paired contract can be unit-tested directly alongside `shouldResetTaskSessionForWake`. This is the only API surface change in this PR. Wake reasons whose reuse behavior is intentionally **unchanged**: - `issue_commented` — the comment is the reason to engage; continuation context matters - `issue_comment_mentioned` — same rationale - `transient_failure_retry` — resuming a previously-failed run; want continuity - `process_lost_retry` — resuming after process loss; want continuity - `missing_issue_comment`, recovery reasons — out of scope; can be revisited as follow-ups if observed bloat shows up ## Verification ```bash cd server pnpm vitest run src/__tests__/heartbeat-timer-wake-session-reset-pf4.test.ts # 12/12 pass pnpm vitest run \ src/__tests__/heartbeat-stale-queue-invalidation.test.ts \ src/__tests__/heartbeat-process-recovery.test.ts \ src/__tests__/heartbeat-comment-wake-batching.test.ts # 48/48 adjacent heartbeat tests pass ``` The 12 new tests assert: 1. `shouldResetTaskSessionForWake` resets on `heartbeat_timer` 2. `shouldResetTaskSessionForWake` still resets on the four existing reasons 3. `forceFreshSession === true` still triggers reset 4. `issue_commented`, `transient_failure_retry`, unknown reasons, and null/undefined context do **not** trigger reset 5. `describeSessionResetReason` describes `heartbeat_timer` explicitly so logs are legible 6. `describeSessionResetReason` keeps the exact wording for the four existing reasons 7. `describeSessionResetReason` returns the `forceFreshSession` message 8. `describeSessionResetReason` returns `null` for non-resetting reasons 9. **Parity invariant**: the two functions agree on every input — `describeSessionResetReason(ctx)` is non-null iff `shouldResetTaskSessionForWake(ctx)` returns true. This locks the pair so future changes to one must update the other. ## Risks - **Low–medium.** This changes behavior for every `heartbeat_timer` wake on every agent: the prior task session is no longer reused. - For **manager / CEO agents** (the documented case): this is the intended improvement. Timer wakes carry no continuation state for these roles. - For **worker agents** that may have used timer wakes to resume in-flight work: any genuine continuation should already be triggered by issue/execution wake reasons (which still reuse) or by an active checkout being resumed via `process_lost_retry` / `transient_failure_retry`. Timer wakes themselves do not create checkouts. - If a deployment relied on timer wakes to preserve mid-task context — which is fragile by design — the right path is to switch to a non-timer wake reason or accept the reset. The PR doesn't add a new opt-out flag because the goal is to bound session size; introducing an opt-out would re-open the bloat path this PR is closing. - No schema or API surface change beyond exporting `describeSessionResetReason`. No migration. No client-visible API change. ## Model Used Claude Opus 4.7 (1M context), model ID `claude-opus-4-7[1m]`. Used in interactive Claude Code session with extended reasoning, tool use (Read/Edit/Write/Bash), and verification gates between exploration → fix → tests → push. ## 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 the open PR list for similar/duplicate work — distinct from paperclipai#4080 (force-fresh follow-up wake — codex/general) and paperclipai#4195 (codex session reset on model change); this PR specifically targets the `heartbeat_timer` reuse path - [x] I have run tests locally and they pass (12 new + 48 adjacent = 60 tests, no regressions) - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots — N/A, server-only change - [x] I have updated relevant documentation to reflect my changes — none needed; the new export carries clear semantics and the run log message is self-explanatory - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Irene <irene@users.noreply.github.com> Co-authored-by: Devin Foley <devin@devinfoley.com> Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Devin Foley <devin@paperclip.ing>
…0732) (paperclipai#7848) ## Summary Rebuilds the routine detail page as **variation C** — a sub-sidebar shell that splits the page into **ROUTINE** (Overview · Triggers · Variables · Secrets · Delivery) and **OPERATE** (Runs · Activity · History), per the engineering spec on PAP-10730. Replaces the previous 5-tab `?tab=…` layout in `ui/src/pages/RoutineDetail.tsx`. Implements PAP-10732. Design source of truth: PAP-10730 `spec` document; approved direction PAP-10709. ## What changed - **Routing** (`ui/src/App.tsx`): real sub-routes under `routines/:routineId/:section`. Bare `/routines/:id` redirects to the last-viewed section (`localStorage`) or `overview`; old `?tab=…` URLs redirect to the matching section for back-compat. Every section URL is bookmarkable. - **Shell** (`RoutineDetail.tsx`): slim 56px sticky header (title + managed-by-plugin chip + Run / Active toggle), page-local sub-sidebar, full-canvas section body, per-section sticky save bar. All routine state/mutations stay in the shell and flow to sections via a `RoutineDetailContext`. - **New components**: `RoutineSubSidebar` (+ mobile `<Select>` picker, roving keyboard nav), `RoutineSaveBar` (scoped dirty count, ⌘/Ctrl+S save, Esc-discard confirm, 409 conflict recovery with Reload / Overwrite), `RadioCard` primitive (Delivery), `RoutineTriggerCard` (extracted from the inline editor, with human-readable cron), `RoutineActivityRow` (expandable JSON), `lib/cron-readable`, and the per-section components. - **Reuse**: History mounts the existing `RoutineHistoryTab`; Variables mounts `RoutineVariablesEditor` with a provenance banner; Secrets reuses `EnvVarEditor` + the one-time reveal banner. No backend or schema changes. - **States**: per-section loading/empty/error/save-conflict and read-only strip scaffolding (§1.6). ## Testing - New unit tests: sub-sidebar navigation/active/dirty markers, save-bar dirty + ⌘S + conflict recovery, cron helper. - Existing routine tests still pass: `Routines.test.tsx`, `RoutineHistoryTab.test.tsx`, `RoutineRunVariablesDialog.test.tsx`. - `vitest run` (routine scope): **36 passed**. Production `vite build`: **green**. - Screenshots at 1440×900 + 390×844 attached to [PAP-10732](https://example.invalid) (rendered via a new Storybook story with fixture data). ## Out of scope (per spec) - `/routines` list-page redo (follow-up). - Non-owner secret-value visibility (Open Q6 — CEO escalation; built with the spec default). --------- 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 is issue-centered, and reviewers often need to inspect files, artifacts, and path references produced during that work. > - Before this branch, workspace-relative paths and artifact file references were not first-class inspectable objects in the board UI. > - Safe file viewing needs shared resource contracts, server-side workspace boundary checks, and UI that opens files without exposing arbitrary host paths. > - The workspace file viewer branch needed to stay as one active PR and be rebased onto current `paperclipai/paperclip:master` for review. > - This pull request adds the workspace file resource API, issue-page file viewer and browser, markdown file-reference links, and artifact file chips. > - The benefit is that board users can inspect relevant files from issue context while preserving workspace boundaries and auditability. ## Linked Issues or Issue Description No public GitHub issue exists for this branch. Internal Paperclip issues: `PAP-1953`, `PAP-10539`, `PAP-10733`. Problem / motivation: - Board users need to open workspace-relative files mentioned by agents or attached as work-product metadata without switching to a terminal. - The UI needs to support both direct file-path opening and workspace browsing/searching from an issue page. - The server must enforce company access, workspace boundaries, size limits, rate limits, and safe audit logging. Related PR: - Prior closed attempt: paperclipai#4442 - Single active PR for this branch: paperclipai#7681 ## What Changed - Added shared workspace file resource types, validators, and workspace-file `resourceRef` metadata validation for work products. - Added server routes/services for resolving, listing, and previewing workspace-relative files with access checks, scan caps, list-specific limits, and audit logging. - Added the issue file viewer provider, sheet, workspace browser, command-palette action, markdown workspace-file autolinks, and artifact file chips. - Updated issue workspace UI and stories/tests for file browsing and workspace file opening. - Rebased the branch onto current `paperclipai/paperclip:master` and updated the existing single PR branch. - Addressed current-head Greptile follow-ups by applying `offset` consistently across search/recent/changed file listings, restoring stopped-service port ownership checks before auto-port reuse, and stabilizing the workspace browser pagination test. ## Verification Current local verification after rebase to `public/master`: - `pnpm exec vitest run packages/shared/src/work-product.test.ts server/src/__tests__/file-resources.test.ts server/src/__tests__/instance-settings-routes.test.ts server/src/__tests__/instance-settings-service.test.ts server/src/__tests__/workspace-runtime.test.ts ui/src/components/FileViewerSheet.test.tsx ui/src/components/FileViewerSheet.copy.test.tsx ui/src/components/WorkspaceFileBrowser.test.tsx ui/src/components/WorkspaceFileMarkdownBody.test.tsx ui/src/context/FileViewerContext.test.ts ui/src/lib/remark-workspace-file-refs.test.ts ui/src/lib/workspace-file-parser.test.ts ui/src/components/IssueWorkspaceCard.test.tsx` - 13 files passed, 197 tests passed. - `pnpm -r --filter @paperclipai/shared --filter @paperclipai/server --filter @paperclipai/ui typecheck` - passed. - `pnpm exec vitest run ui/src/components/WorkspaceFileBrowser.test.tsx` - 1 file passed, 25 tests passed. - `pnpm exec vitest run server/src/__tests__/file-resources.test.ts server/src/__tests__/workspace-runtime.test.ts` - 2 files passed, 90 tests passed. - `pnpm -r --filter @paperclipai/server typecheck` - passed. - Confirmed branch is `0` behind and `46` ahead of current `public/master` after rebase and follow-up commits. - Confirmed the PR diff does not include `pnpm-lock.yaml`. - Confirmed the PR diff does not include `.github/workflows` changes. - Searched GitHub for duplicate or related workspace file viewer PRs/issues; paperclipai#4442 is the prior closed attempt and this PR is the single active PR for the branch. - No screenshots were committed; the task explicitly asked not to add design screenshots or images unless they were part of the work. Current remote verification on head `a698a7bc10137baf7d25bd5722e1d6e0343387c1`: - Greptile Review - success, 64 files reviewed, 0 comments added, no unresolved Greptile review threads. - PR workflow `verify` - success. - Typecheck + Release Registry, General tests, workspace test shards, serialized server suites, Build, Canary Dry Run, e2e, Socket, and Snyk - success. - `security-review` - neutral, with output saying a draft advisory was filed for maintainer review and is not a merge block. - `commitperclip PR Review / review` - cancelled after the security gate detected flags and timed out while creating/reviewing the advisory. I reran it once and it cancelled the same way; no actionable code/test failure was exposed in the job logs. ## Risks - This is a broad UI/server feature PR, so review needs to pay attention to route authorization, workspace boundary handling, and markdown autolink false positives. - Workspace browsing intentionally caps list results and scan depth; very large workspaces may require users to refine search terms. - Remote workspace preview remains unavailable until remote file-access support is implemented. - The neutral commitperclip security-review advisory needs maintainer review, but the check output says it is not a merge block. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected - check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5 coding agent in a Paperclip/Codex local tool-use environment, medium reasoning, with shell/GitHub CLI tool use for branch inspection, verification, rebase, PR update, Greptile review, and CI inspection. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…aperclipai#7847) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The commitperclip review workflow runs a security gate as part of CI on every PR > - The security script's header promises it always exits 0 and stays silent/informational, but PRs that triggered a flag were failing with a 5-minute timeout > - Two compounding bugs: `findExistingDraftAdvisory` paginated without an upper bound, and the workflow step did not have `continue-on-error: true`, so any hang inside the script turned into a hard `review` check failure that blocked merge > - This pull request caps the advisory pagination at 20 pages and adds `continue-on-error: true` to the workflow step, aligning runtime behavior with the script's documented "always exit 0" contract > - The benefit is that future PRs flagged by the security gate no longer block merge on a 5-minute timeout, and the gate stays silent/informational as intended ## Linked Issues or Issue Description Fixes: paperclipai#7849 ## What Changed - `.github/workflows/commitperclip-review.yml`: added `continue-on-error: true` to the `Run security gates` step so a hang or non-zero exit cannot fail the `review` check (matches the script's documented "always exit 0" contract). - `.github/scripts/check-pr-security.mjs`: capped `findExistingDraftAdvisory` pagination at 20 pages (= 2000 advisories) and short-circuited with a `console.warn` when the cap is hit; if no match is found within the cap, callers will simply create a new draft instead of hanging forever. - `.github/scripts/tests/check-pr-security.test.mjs`: added a test asserting the pagination cap is enforced. ## Verification - `node .github/scripts/tests/check-pr-security.test.mjs` — 31/31 pass, including the new cap test. - Step-level guarantee: `continue-on-error: true` makes the `Run security gates` step non-blocking for the job, so even an unexpected hang/timeout in this step can no longer fail the `review` check. ## Risks - Low risk. Pagination cap is a defensive bound; the worst case is a duplicate draft advisory (acceptable — the workflow continues). `continue-on-error: true` is exactly what the script header already promised; the workflow now matches its stated contract. ## Model Used - Claude (claude-opus-4-7), 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 - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [ ] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
…detect + clearSession) (paperclipai#5972) ## Thinking Path > - Paperclip's `claude_local` adapter persists Claude Code session jsonls under `~/.claude/projects/…/{sessionId}.jsonl` and resumes them on the next heartbeat > - When Claude Code injects `<synthetic>` placeholder assistant messages (after rate-limit, max-turn exhaustion, or transient-upstream failures) those placeholders get UUID-format `message.id`s rather than `msg_…`-format ids > - On the next `--resume`, Claude Code passes that UUID as `previous_message_id` and Anthropic's API rejects it with a 400: ``diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with `msg_`)`` > - The adapter had a session-rotation fallback only for "unknown session" errors, so the poisoned session was `--resume`-d indefinitely and the agent flipped between `idle` and `error` every heartbeat > - Even worse, the *result* event of the failing run still carried a `session_id`, and the adapter was persisting that id into the issue-scoped session store (`agentTaskSessions`). So even after we detected the 400, every subsequent continuation re-loaded the same poisoned id and hit the same 400 again — the issue was permanently stranded > - We observed this on multiple agents in our deployment; the only manual fix was to rename the `.jsonl`, which is not a viable long-term workaround > - This PR detects the 400, runs the same session-rotation fallback the unknown-session path uses **and** stops persisting the poisoned id, so the next attempt starts genuinely fresh ## Linked Issues or Issue Description No external GitHub issue is linked. Describing the problem inline following the bug-report template: **What happened:** `claude_local` agents flipped between `idle` and `error` on every heartbeat because the persisted session jsonl carried a synthetic UUID `previous_message_id` (from `<synthetic>` assistant placeholders injected after rate-limit/max-turn/upstream errors). Anthropic's API rejected every `--resume` with a 400: ``diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with `msg_`)``. **Expected behavior:** When the persisted session is poisoned and unrecoverable, the adapter should rotate to a fresh session — the same fallback path already used for unknown-session errors — and stop re-persisting the poisoned `session_id`. **Actual behavior:** The session-rotation fallback only matched the "unknown session" pattern, so the poisoned session was `--resume`-d forever. The result event of the failing run still carried `session_id`, which was being persisted into `agentTaskSessions`, so every subsequent continuation reloaded the same poisoned id and hit the same 400. **Reproduction:** Inject any flow that causes Claude Code to emit a `<synthetic>` placeholder (rate-limit, max-turn exhaustion, transient upstream failure). The next `--resume` will fail with the 400 and the agent will not self-recover. **Scope of fix:** Add a `previous_message_id` 400 detector; route it through the existing unknown-session fallback; drop the poisoned `sessionId` and emit `clearSession: true` so the heartbeat service wipes the persisted row; best-effort delete the local poisoned `.jsonl`. ## What Changed Two commits: 1. **`adapter-claude-local: auto-rotate session on previous_message_id 400 (synthetic-msg poisoning)`** — detector + execute-time rotation 2. **`adapter-claude-local: guard against persisting poisoned sessionId`** — validate-before-persist + `clearSession` Combined diff: - `parse.ts`: new `isClaudePoisonedPreviousMessageIdError(parsed)` matching ``/diagnostics\.previous_message_id.*starts with `msg_`/i`` against `parsed.result` and `extractClaudeErrorMessages(parsed)` - `parse.ts`: `isClaudeTransientUpstreamError()` excludes the new error from transient classification so it isn't masked as retryable upstream noise - `execute.ts`: expand the resume-fallback branch so it triggers on both `isClaudeUnknownSessionError` and the new `isClaudePoisonedPreviousMessageIdError`, with a distinct log line (`"returned a poisoned message-id"` vs `"is unavailable"`) - `execute.ts`: for local (non-remote) execution targets, best-effort delete the poisoned `~/.claude/projects/.../{sessionId}.jsonl` before retrying so the file can't be accidentally resumed by an out-of-band caller. The `fs.unlink` and follow-up log call are in separate try/catch blocks so a closed log stream cannot mask a successful unlink (and vice versa) - `execute.ts` / `toAdapterResult`: when a result carries the poisoned 400, **drop** `sessionId`/`sessionParams`/`sessionDisplayId` (return `null`) and emit `clearSession: true` so the heartbeat service's `resolveNextSessionState` wipes the persisted row. The result also surfaces `errorCode: "claude_poisoned_previous_message_id"` for observability - `docs/adapters/claude-local.md`: runbook entry — symptom, auto-recovery flow, on-call checklist - Tests: - 4 new `parse.test.ts` cases covering positive detection in `result` and `errors[]`, negative cases, and non-transient classification - 3 new `claude-local-execute.test.ts` cases: (a) fresh run reports the poisoned error → sessionId dropped + `clearSession: true`; (b) recovery retry also reports the poisoned error → same guards apply; (c) session-rotation success on retry ## Verification ```bash pnpm --filter @paperclipai/adapter-claude-local exec vitest run src/server/parse.test.ts pnpm --filter @paperclipai/server exec vitest run src/__tests__/claude-local-execute.test.ts ``` Both suites green locally. This patch is also currently running as a hot-patch over the published `2026.513.0` adapter on the reporting deployment — sessions that previously looped indefinitely now self-recover on the first heartbeat after the 400 surfaces. ## Risks - Low risk. The detector is conservative (regex over `result` + `errors[]` only) and the rotation reuses the existing unknown-session fallback path - The local-only `fs.unlink` of the poisoned `.jsonl` is wrapped in `try/catch` and ignored on failure — strictly an optimization; the server-side session clear is the authoritative reset - Remote execution targets (`executionTargetIsRemote`) skip the disk cleanup because the file lives on a remote host that we can't safely reach from the adapter - The `clearSession: true` + nulled session fields path is a no-op on healthy runs; it only fires when the new detector matches, so existing successful continuations are unaffected - No DB schema changes, no public API changes, no new dependencies ## Model Used - Provider: Anthropic Claude - Model: `claude-opus-4-7` (Opus 4.7) - Context window: 1M - Capabilities: extended reasoning, tool use, code execution - Role: implemented the detector, expanded the fallback branch, added the persist-guard + `clearSession`, wrote the unit + integration tests, validated locally, and applied the equivalent hot-patch to the deployed `2026.513.0` install while this PR is in review ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for similar or duplicate PRs and linked them — closed paperclipai#2295, paperclipai#2361, paperclipai#3572, paperclipai#5438 as duplicates of this canonical fix; complementary fixes paperclipai#4838 (heartbeat_timer reset) and paperclipai#4932 (gemini context-overflow rotation) target different code paths - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots — N/A, adapter-only change - [x] I have updated relevant documentation (`docs/adapters/claude-local.md` runbook entry) - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Danial Jawaid <danial.jawaid@gmail.com> Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Devin Foley <devin@paperclip.ing>
…erclipai#7854) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The web UI has a bottom-left account flyout menu where users reach profile, docs, and the light/dark toggle > - There was no in-product way for users to send feedback or report issues — they had to find an external channel > - We want a low-friction, always-visible entry point for feedback, and a clean URL we can re-point later without shipping app changes > - This pull request adds a **Feedback** item (Megaphone icon) to the account flyout, between Documentation and the theme toggle, that opens `https://paperclip.ing/feedback` in a new tab > - `paperclip.ing/feedback` is a stable indirection (added to the marketing site) that currently 302-redirects to a Google Form, so the destination can be swapped for a richer solution later with no app release > - The benefit is a one-click feedback path for users and a future-proof link the team controls ## Linked Issues or Issue Description No public GitHub issue exists (tracked internally as Paperclip PAP-107). Describing the underlying request inline as a feature, per CONTRIBUTING.md path (B): ### Problem or motivation Users have no in-app affordance to give feedback or report issues; that friction loses signal we'd otherwise act on. ### Proposed solution Add a Feedback item to the account flyout (Megaphone icon, between Documentation and the theme toggle) that opens a stable `paperclip.ing/feedback` URL in a new tab. That URL redirects to a Google Form for now, keeping the client decoupled from the destination. ### Alternatives considered Linking the Google Form directly from the app — rejected because it bakes a throwaway URL into the client; the `/feedback` indirection keeps the link clean and swappable. ### Roadmap alignment Small, self-contained UX addition; no overlap with planned core work (checked ROADMAP.md). The `/feedback` redirect lives in the separate `paperclip-website` repo (Astro site on Cloudflare Pages), commit `f65b566`. No duplicate/related PRs found in this repo (searched feedback/flyout/menu). ## What Changed - `ui/src/components/SidebarAccountMenu.tsx`: import `Megaphone` from `lucide-react`; add `FEEDBACK_URL = "https://paperclip.ing/feedback"` const next to `DOCS_URL`; insert a `Feedback` `MenuAction` between Documentation and the theme toggle using the `external` prop so it opens in a new tab (`target="_blank"`, `rel="noreferrer"`) and closes the popover on click. - `ui/src/components/SidebarAccountMenu.test.tsx`: assert the Feedback item renders with the correct `href`, opens in a new tab, and is ordered after Documentation and before the theme toggle. - (Separate repo, for context) `paperclip-website` `public/_redirects`: `/feedback` → 302 → the feedback Google Form. ## Verification - **Unit tests:** `SidebarAccountMenu` tests pass (item renders, correct `href`, `target="_blank"`, ordering). Run: `cd ui && npm test -- SidebarAccountMenu`. - **Manual / canary:** The board previewed the canary build of the menu item and accepted it. Clicking **Feedback** opens a new tab to `paperclip.ing/feedback`. - **Redirect:** After the Cloudflare Pages deploy propagates, `curl -sI https://paperclip.ing/feedback` returns the Google Form in the `Location` header. _Screenshots:_ UI change was validated via the accepted canary preview; the item reuses the existing `MenuAction` styling, so it visually matches the Documentation/theme rows. ## Risks - **Low risk.** Additive, self-contained UI change with no new state or API calls. The only external dependency is the `paperclip.ing/feedback` redirect (separate repo, already deployed); if it were missing the link would 404, but it is in place. No migrations, no breaking changes. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. ## Model Used - **Claude (Anthropic).** PR authoring/orchestration: **claude-opus-4-8** (extended thinking + tool use). The implementation commit `b454a12d` was produced with assistance from **claude-sonnet-4-6**. All changes reviewed before pushing. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Routines are the recurring-work surface that lets a company keep operating without a human manually kicking off every task > - The base routine detail Variation C shell already landed in paperclipai#7848, but the follow-up branch still had polish work for scheduling, section ergonomics, and the list layout > - Operators need routine edit screens to explain trigger behavior clearly, keep long detail pages usable on mobile/touch devices, and make grouped routine lists easier to scan > - This pull request rebases the remaining branch work onto current `master`, drops the duplicate commits already merged through paperclipai#7848, and keeps only the new routine UI follow-ups > - The benefit is a cleaner routines workflow without reopening the already-merged shell work or carrying unrelated lockfile, workflow, or screenshot changes ## Linked Issues or Issue Description Refs paperclipai#7848 Feature follow-up: polish the routines UI after the Variation C routine-detail shell landed. Problem / motivation: - Routine trigger configuration needs clearer previews for manual, schedule, API, and webhook execution modes. - Routine detail sections need better responsive spacing and touch ergonomics. - The routines list grouping should scan like grouped records instead of a table with heavy row dividers. - The routine tests need a React 19-compatible render helper so the focused routine suite can run in this workspace. Proposed solution: - Add cron-fire preview helpers and routine-run display helpers with focused tests. - Expand the routine editable and operate sections with richer trigger, variable, run, activity, and history presentation. - Adjust the routine detail shell and sub-sidebar spacing for mobile/touch layout. - Update grouped routine list presentation to use bordered group headers with borderless rows. - Switch affected routine tests to the repo's `flushSync` render-helper pattern. Alternatives considered: - Leaving the duplicate pre-paperclipai#7848 commits in the branch would recreate conflicts and make the PR review much larger than the remaining change. - Keeping grouped routine rows inside one bordered table was simpler, but made the grouping hierarchy less legible. Roadmap alignment: - ROADMAP.md lists Scheduled Routines as a core shipped capability and Output/Enforced Outcomes as ongoing priorities. This is polish on that existing routines capability, not a new roadmap-level feature. ## What Changed - Added routine scheduling preview helpers and tests for cron/manual/API/webhook fire-policy display. - Added routine run display helpers and tests for deduped trigger labels and run-row subtitles. - Polished routine detail sections, including trigger summaries, operate views, and env/variable editing ergonomics. - Adjusted routine detail page and sub-sidebar spacing so the title/header area is less pinned and touch layouts center better. - Reworked the routines list grouped layout so group headers are bordered cards and routine rows are borderless inside each group. - Added Storybook coverage for the routines list grouped layout and updated the existing routine detail story. - Repaired routine tests to use `flushSync` helpers compatible with the installed React 19 runtime. ## Verification - `pnpm exec vitest run ui/src/lib/cron-fires.test.ts ui/src/lib/routine-run-display.test.ts ui/src/pages/Routines.test.tsx ui/src/components/RoutineSubSidebar.test.tsx ui/src/components/RoutineSaveBar.test.tsx` - Result: 5 test files passed, 37 tests passed. - Confirmed the rebased PR diff does not include `pnpm-lock.yaml`, `.github/workflows/*`, or committed screenshots. - Confirmed `origin/master` is an ancestor of the pushed branch head after rebase. ## Risks - Medium UI risk: this touches the routine detail and routine list surfaces, so visual regressions are possible in edge cases not covered by the focused tests. - Low data risk: no schema, migration, server API, or lockfile changes are included. - Review note: the branch intentionally force-pushed after rebasing because the original first three commits were already merged through paperclipai#7848. > 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 runtime, with repository shell/tool access. Exact hosted runtime model identifier and context-window size were not exposed in the execution environment. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] 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: Claude Opus 4.8 <noreply@anthropic.com>
…ipai#4080) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The heartbeat service governs how agent wake events get queued, deferred, or folded into the currently-running adapter run > - `forceFreshSession: true` wakes on a same-agent/same-issue path get silently folded into the active run, so callers can never request a true cold-start follow-up > - This breaks phased workflows that need to drop a poisoned session and restart cleanly on the same issue without bouncing to another agent > - This PR extracts the existing same-issue follow-up decision into `shouldDeferFollowupWakeForSameIssue` and extends it to also defer `forceFreshSession: true` wakes into a follow-up run boundary > - The benefit is that `forceFreshSession` now behaves as documented: it actually starts a fresh session, even when the wake targets the same agent/issue/runtime that is currently executing ## Linked Issues or Issue Description **What happened?** A wake event posted with `forceFreshSession: true` against an issue whose current adapter run is still `running` on the same execution agent is silently coalesced into that in-flight run instead of starting a cold session. Callers that explicitly request a fresh-session reset see no behavior change until the run naturally completes. **Expected behavior** `forceFreshSession: true` should always force a fresh session start, even when the wake targets the same agent/issue that is currently executing. The wake should defer into a follow-up run boundary if the current run is still in-flight. **Steps to reproduce** 1. Start an adapter run for some issue. 2. While the run is still `running`, post a wake event for the same issue/agent with `forceFreshSession: true`. 3. Observe: the active run continues without resetting the session; the fresh-session signal is dropped. ## What Changed - Extracted same-issue follow-up decision into exported helper `shouldDeferFollowupWakeForSameIssue` in `server/src/services/heartbeat.ts` - Extended that helper so `forceFreshSession: true` (not only `wakeCommentId`) defers into a follow-up run when the current run is still `running` for the same execution agent - Added stickiness to `mergeCoalescedContextSnapshot`: if either side of a wake-merge has `forceFreshSession: true`, the merged snapshot keeps it set so it is not silently dropped while queued wakes coalesce - Added five unit tests in `heartbeat-workspace-session.test.ts` covering each decision branch of the helper ## Verification - `pnpm --filter @paperclipai/server test src/__tests__/heartbeat-workspace-session.test.ts` - `pnpm --filter @paperclipai/server typecheck` ## Risks Low. Behavior change only affects the narrow case where a same-agent/same-issue wake carries `forceFreshSession: true` while the active run is still `running`. Other wake paths (cross-agent, queued/failed runs) are untouched. The helper extraction is a pure refactor preserving the prior comment-wake deferral. ## Model Used Claude (Opus 4.7) — extended thinking enabled, used to extract the helper, extend the deferral condition to cover `forceFreshSession`, and write unit coverage. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots (N/A — no UI changes) - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green (in progress) - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Devin Foley <devin@paperclip.ing>
…ipai#7855) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The issue thread is the operator surface where comments, assignee changes, pauses, resumes, and wakeups turn human intent into agent execution. > - Interrupting a live run and handing work to another assignee needs clear semantics so the product does not accidentally keep work alive, wake the wrong participant, or hide why an agent stopped. > - Comment-driven wakes also need strict boundaries so closed, blocked, and dependency-driven work only resumes when there is real actionable input. > - This pull request codifies the interrupt handoff contract, implements backend scheduling behavior, and gives the UI clearer handoff/pause language. > - The benefit is a more inspectable and predictable task lifecycle for both operators and agents. ## Linked Issues or Issue Description Paperclip issue: `PAP-10664` / `PAP-10751`. Problem: interrupting or reassigning live agent work could be ambiguous in the UI and backend. Operators needed clearer feedback about whether a handoff wakes an agent, what pause/cancel affects, and when comments should revive execution. The backend also needed stronger tests around comment wake boundaries, retry supersession, and structured agent mention dispatch. Related GitHub PR search found broad workflow-adjacent PRs paperclipai#5082, paperclipai#6359, and paperclipai#4083, but no exact duplicate for this head branch or interrupt-handoff scope. ## What Changed - Added an interrupt handoff semantics document covering destination behavior, wake expectations, and live-run interruption states. - Implemented backend interrupt handoff behavior and comment wake/reopen handling in issue routes/services and heartbeat scheduling. - Hardened structured agent mention dispatch so mentions resolve through the intended dispatch path. - Added UI helpers and components for handoff chips, wake rows, interrupt banners, pause-affects summaries, and composer guidance. - Updated the issue properties assignee picker and issue chat/composer surfaces to make interrupt/reassign behavior clearer. - Added backend, UI utility, component, and Storybook coverage for the new behavior. - Stabilized the new UI component tests with a local `flushSync`-backed act helper matching existing repo practice in this dependency set. - Addressed Greptile feedback by threading historical run `errorCode` through issue-run data and operator-interrupted chat labels. - Addressed Greptile's cancel ordering concern by terminating/deleting in-memory heartbeat processes before cancellation status persistence, with regression coverage for DB update failure. ## Verification - `git diff --check $(git merge-base HEAD origin/master)..HEAD` - `pnpm --filter @paperclipai/ui exec vitest run src/lib/interrupt-handoff.test.ts src/lib/issue-chat-messages.test.ts src/components/IssueProperties.test.tsx src/components/interrupt-handoff/InterruptHandoffViews.test.tsx --no-file-parallelism --maxWorkers=1` — 4 files / 91 tests passed before the Greptile follow-ups. - `pnpm run preflight:workspace-links && pnpm exec vitest run server/src/__tests__/heartbeat-process-recovery.test.ts server/src/__tests__/heartbeat-retry-scheduling.test.ts server/src/__tests__/issue-comment-reopen-routes.test.ts server/src/__tests__/issue-tree-control-service.test.ts server/src/__tests__/issue-update-comment-wakeup-routes.test.ts server/src/__tests__/issues-service.test.ts --no-file-parallelism --maxWorkers=1` — 6 files / 191 tests passed before the Greptile follow-ups. - `pnpm --filter @paperclipai/ui exec vitest run src/lib/issue-chat-messages.test.ts --no-file-parallelism --maxWorkers=1` — 1 file / 24 tests passed after the historical `errorCode` follow-up. - `pnpm exec vitest run server/src/__tests__/activity-service.test.ts server/src/__tests__/activity-routes.test.ts --no-file-parallelism --maxWorkers=1` — 2 files / 11 tests passed after the historical `errorCode` follow-up. - `pnpm exec vitest run server/src/__tests__/heartbeat-process-recovery.test.ts --no-file-parallelism --maxWorkers=1` — 1 file / 52 tests passed after the cancel ordering follow-up. - Greptile is green for head `272647636287d034bab8d981eaf5305865aa0f96`; the old inline P2 is resolved/outdated. - GitHub Actions, Socket, security-review, and Greptile checks are green for head `272647636287d034bab8d981eaf5305865aa0f96`. The external `security/snyk (cryppadotta)` status was still pending at `https://app.snyk.io/org/cryppadotta/pr-checks/85b3e8f4-04e1-4f8e-9362-899c8148c23c` after a bounded wait. ## Risks - Medium: changes touch issue comments, wake scheduling, and live-run interruption semantics, so regressions could affect when agents resume or stay stopped. - Medium: UI copy and state grouping for assignee changes may need reviewer tuning after product review. - Low migration risk: no database schema migration is included. - The branch was created before the latest `origin/master` commits; reviewers should confirm CI merge-base behavior and resolve any merge conflicts if GitHub reports them. > 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, tool use and local command execution enabled. Exact hosted model build and context window were not exposed by the runtime. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Screenshot note: this PR includes Storybook coverage for the new interrupt handoff UI states rather than captured before/after browser screenshots in this PR-creation heartbeat. --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…(DLD-889) (paperclipai#1742) ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies. > - The Claude-local adapter uses `claude --resume <session-id>` to continue prior sessions; the `--resume` value MUST be a UUID per Claude's CLI contract. > - Paperclip internally uses session IDs prefixed with `ses_` (not UUIDs); these get passed straight through to `--resume` and crash the run. > - On top of the crash, when the underlying error path triggers a secret-decryption failure or heartbeat setup failure, the diagnostics are too thin to tell key-mismatch from other failures, and the heartbeat error code is mis-classified as `adapter_failed` instead of `setup_failed`. > - This PR validates `runtimeSessionId` against a UUID regex before letting `canResumeSession` become true, adds `not a valid UUID` to Claude's own retry-error regex, improves AES-256-GCM decryption diagnostics in the local encrypted provider, and re-classifies pre-adapter setup failures. > - The benefit is that Paperclip session IDs are detected and skipped gracefully (logged, no crash), legitimate Claude UUID-rejection errors are treated as retriable, and operators can diagnose decryption/setup failures from the run log. ## Linked Issues or Issue Description **What happened?** The `claude-local` adapter passes Paperclip's internal session identifiers (e.g. `ses_…`) straight to `claude --resume <session-id>`. Because Claude's CLI requires the `--resume` argument to be a UUID, the run crashes with a `not a valid UUID` error. When the surrounding code path also hits a secret-decryption failure, the heartbeat reports it as `adapter_failed`, hiding the real `setup_failed` cause and making diagnosis hard. **Expected behavior** Non-UUID session IDs should be detected before `--resume` is called, the run should fall back to a fresh session with a clear log line, and any decryption / setup failure should be reported with enough detail (and the correct error code) for an operator to tell what failed. **Steps to reproduce** 1. Have a persisted task session whose ID is not a UUID (Paperclip-issued `ses_…` form). 2. Trigger a heartbeat that resumes that session via the `claude-local` adapter. 3. Observe: the adapter crashes with a UUID-validation error; if the path also involves a decryption failure, the heartbeat surfaces `adapter_failed` instead of `setup_failed`. ## What Changed - `packages/adapters/claude-local/src/server/execute.ts`: Validates `runtimeSessionId` against a UUID regex before setting `canResumeSession`; non-UUID IDs are logged and skipped gracefully. Guards the cwd-mismatch log block on `isValidUuid` so it does not fire for non-UUID session IDs. - `packages/adapters/claude-local/src/server/parse.ts`: Adds `not a valid UUID` to the session-error retry regex so Claude's own UUID rejection is treated as a retriable error. - `server/src/services/secrets/local-encrypted-provider.ts`: Wraps AES-256-GCM decryption in try/catch and re-throws with a key fingerprint hint to aid key-mismatch diagnosis. - `server/src/services/heartbeat.ts`: Corrects the outer-catch `errorCode` from `adapter_failed` to `setup_failed` for pre-adapter setup failures. - `AGENTS.md`: Adds task/PR/CI governance sections (10–13) and expands the Definition of Done. ## Verification - `pnpm --filter @paperclipai/adapter-claude-local test` covers UUID validation and the parse retry regex. - `pnpm --filter @paperclipai/server test src/services/secrets` covers decryption diagnostics. - `pnpm --filter @paperclipai/server typecheck` ## Risks Low. UUID validation is strictly additive (non-UUIDs that previously crashed now log and skip). Decryption diagnostics only fire on failure paths. The `setup_failed` error code change is a clearer classification, not a behavior change. ## Model Used Claude (Opus 4.6) — used to identify the UUID-validation root cause, mirror existing parse patterns, and re-classify the heartbeat setup error code. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots (N/A — no UI changes) - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green (in progress) - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: CTO Agent <cto@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Devin Foley <devin@paperclip.ing>
…aperclipai#4109) ## Thinking Path > - Paperclip orchestrates AI agents on pluggable adapters (`claude_local`, `opencode_local`, `codex_local`, …); each adapter wraps an external CLI. > - The heartbeat service stores a session ID per agent and replays it back to the adapter via `--resume` so within-task continuity is preserved. > - Session IDs are adapter-specific in format: claude expects a UUID, opencode emits `ses_…`, etc. They cannot be cross-replayed. > - When the cross-adapter session ID does slip through (operator changes `adapterType`, edge cases in the resume path, foreign-format ID in stored task sessions), the claude CLI hard-fails with a validation error and every subsequent heartbeat loops on the same error until the stored ID is manually cleared. > - Master now ships a canonical-session-ID guard at `heartbeat.ts:8450` (via paperclipai#5972) that prevents most of this at the source, and `isClaudePoisonedPreviousMessageIdError` recovers from the 400-class API error. > - This PR adds defense-in-depth at the adapter layer: the `--resume requires a valid session ID … not a UUID …` validation error from the claude CLI is now classified as an unknown-session signal, so the existing fresh-session retry recovers instead of hard-failing. ## Linked Issues or Issue Description Refs paperclipai#5972 — sibling fix on the same cluster (recovers from poisoned `previous_message_id` 400). This PR complements it by handling the CLI-layer `--resume` validation error class. ## What Changed - `packages/adapters/claude-local/src/server/parse.ts` — broaden `isClaudeUnknownSessionError` regex to also match `--resume requires a valid session`, `is not a UUID`, and `does not match any session title`. The existing fresh-session retry at `execute.ts:612-625` now fires for this error class. - `packages/adapters/claude-local/src/server/parse.test.ts` — adds 4 new test cases for `isClaudeUnknownSessionError` covering the legacy and new patterns plus a negative case. **Dropped from the original PR on rebase** (already on master, would conflict): - `server/src/services/heartbeat.ts` runtimeSessionFallback gate — superseded by the stricter `isCanonicalSessionIdForAdapter` check on master (paperclipai#5972 lineage). - `packages/adapters/claude-local/vitest.config.ts` and `vitest.config.ts` projects entry — both already in master. ## Verification ```sh pnpm --filter @paperclipai/adapter-claude-local vitest run # 19/19 passed (3 files, includes 4 new isClaudeUnknownSessionError cases) ``` Pre-existing failure on `server/src/__tests__/heartbeat-process-recovery.test.ts > queues exactly one retry when the recorded local pid is dead` reproduces on `origin/master` — unrelated to this PR. ## Risks - **Low-to-medium.** The added regex fragments are narrow. `--resume requires a valid session` and `does not match any session title` are unambiguously session-related. `is not a UUID` is more generic; worst case is one extra retry on an unrelated CLI validation error that would also fail on the same root issue. Happy to drop `is not a UUID` if reviewers prefer. - **No DB migration; no schema change; no behavior change when adapter types match (the common path).** ## Model Used - Provider: Anthropic (Claude) - Model: `claude-opus-4-7` (Opus 4.7), 1M context window - Tool: Claude Code CLI with extended thinking + tool use; human review on the rebase and the regex narrowing tradeoffs ## Checklist - [x] I searched the GitHub PR list for similar PRs and confirmed this is not a duplicate (related: paperclipai#5972 already merged, complementary scope) - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass (19/19 claude-local) - [x] I have added or updated tests where applicable - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Devin Foley <devin@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing>
…n resume (paperclipai#3276) ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies. > - The Claude-local adapter resumes prior sessions via `claude --resume <session-id>` so work continues across heartbeats. > - When a resumed session contains an image whose content is no longer accessible, Claude returns a 400 "Could not process image" — but the session itself is poisoned and will keep returning the same error on every resume. > - The existing retry path only catches the "unknown session" 400 case; image-processing 400s on resume fall through and the run fails for the user. > - This PR adds an `isClaudeImageProcessingError` detector mirroring `isClaudeUnknownSessionError` and wires it into the same fresh-session retry branch in `execute.ts`. > - The benefit is that a poisoned-image resume self-recovers by retrying once with a fresh session, exactly like the existing unknown-session path. ## Linked Issues or Issue Description Fixes paperclipai#3275 Refs paperclipai#3123 ## What Changed - Added `isClaudeImageProcessingError()` in `packages/adapters/claude-local/src/server/parse.ts` that matches `Could not process image` in 400 error messages. - Wired the new detector into the existing session-resume retry branch in `packages/adapters/claude-local/src/server/execute.ts` alongside `isClaudeUnknownSessionError`. - Retry only fires when `sessionId` is present (i.e. we were resuming), so fresh-session runs that hit the same error are not retried (no infinite loop). ## Verification - `pnpm --filter @paperclipai/adapter-claude-local test` covers `parse.ts` patterns and the resume-retry decision branch. - `pnpm --filter @paperclipai/adapter-claude-local typecheck` ## Risks Low. Behavior change is narrowly additive: a previously-fatal 400 on resume now triggers a single fresh-session retry. No effect on fresh-session runs, unknown-session retries, or non-image 400s. ## Model Used Claude (Opus 4.6) — used to mirror the existing unknown-session pattern and verify the guard against infinite loops. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots (N/A — no UI changes) - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green (in progress) - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Devin Foley <devin@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing>
paperclipai#5028) (paperclipai#5240) ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - codex_local runs Codex CLI under a per-company "managed home" so multiple companies don't trample on each other's session state > - For `auth.json` specifically, the managed home keeps a SYMLINK to the user's real `~/.codex/auth.json` rather than a copy — Codex refresh tokens rotate and are single-use, so any copy goes stale the moment the source rotates and every subsequent run dies with `401 refresh_token_reused` > - Older Paperclip versions copied `auth.json` instead. After upgrading, `ensureSymlink()` saw a regular file at the target, hit `if (!existing.isSymbolicLink()) return;`, and silently kept the stale copy > - This pull request makes the upgrade path self-healing inside `ensureSymlink()` itself: when the target is a regular file, unlink it and create the symlink, since the target lives under the Paperclip-managed home and is safe to delete. Directories are skipped to avoid `EISDIR` on Unix (and inconsistent behavior on Windows) > - The benefit is operators who upgraded from a copy-based version stop getting refresh-token-reused failures without having to manually purge `companies/<id>/codex-home/auth.json`, and the healing is defense-in-depth even outside the `prepareManagedCodexHome` cleanup path ## What Changed - `packages/adapters/codex-local/src/server/codex-home.ts` — `ensureSymlink()` previously bailed out of the `!existing.isSymbolicLink()` branch, leaving any pre-existing regular file untouched. Now unlinks and recreates the symlink in that branch via the existing `createExpectedSymlink()` helper (preserves the EEXIST race-tolerance behavior added in paperclipai#5119). A guard skips directories so the call never throws `EISDIR` and aborts `prepareManagedCodexHome`. Inline comment explains the safety: target is always under the company-scoped managed home (`<paperclipHome>/instances/<id>/companies/<companyId>/codex-home/`), never the user's real `~/.codex`. - `packages/adapters/codex-local/src/server/codex-home.test.ts` — adds a regression test for paperclipai#5028: pre-seed a stale copy at the target, run `prepareManagedCodexHome`, assert the target is now a symlink and reads through to the fresh source. The existing concurrent-symlink test is preserved. ## Verification ``` pnpm --filter @paperclipai/adapter-codex-local exec vitest run # Test Files 8 passed (8) # Tests 26 passed (26) pnpm --filter @paperclipai/adapter-codex-local exec tsc --noEmit # clean ``` Manual repro flow that the regression test mirrors: 1. Create a stale copy: `echo '{"token":"old"}' > <managedHome>/auth.json`. 2. Rotate source: `echo '{"token":"new"}' > ~/.codex/auth.json`. 3. Trigger any codex_local run — `prepareManagedCodexHome` is called from the execute path, the managed file is now a symlink to the source, and the CLI sees the fresh token. ## Risks - **Low risk.** The new branch only fires when the target file is a regular file (the upgrade path) — a pure copy that Codex couldn't have written, since Codex never writes into the managed home. Operators in steady-state on the symlink-based version are unaffected. - The `fs.unlink` only runs against the per-company managed-home path, never the user's real `~/.codex`. Inline comment makes this guarantee explicit. - A directory at the auth.json path is left in place (no silent `EISDIR` crash) — this requires operator inspection rather than autonomous deletion. - The healing uses `createExpectedSymlink()` so it remains tolerant of EEXIST races with concurrent prepare calls (the concurrent-symlink test still passes). - No DB / migration / schema impact. ## Model Used - Anthropic Claude Opus 4.7 (claude-opus-4-7), via Claude Code CLI with extended tool use (Read / Edit / Bash / Grep). No extended-thinking budget consumed beyond default. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots — N/A, adapter-only - [x] I have updated relevant documentation to reflect my changes — inline comment explains the why and the safety of the unlink - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge - [x] I searched the GitHub PR list for similar PRs and confirmed this is not a duplicate Fixes paperclipai#5028. --------- Co-authored-by: Devin Foley <devin@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing>
paperclipai#6008) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The issue subsystem holds per-row lock columns (`checkoutRunId`, `executionRunId`, `executionAgentNameKey`, `executionLockedAt`) that gate checkout, ownership, and release > - When a heartbeat run terminates, `releaseIssueExecutionAndPromote` clears the execution-lock columns but stale checkout locks could remain attached to dead runs in edge paths > - The original fix closed the finalization, checkout, release, and sweeper paths, but PR CI exposed one more process-loss retry path where a queued retry advanced `executionRunId` while leaving `checkoutRunId` pinned to the failed run > - This pull request closes the asymmetry: terminal-run cleanup and process-loss retry recovery release dead checkout locks while preserving live execution ownership > - The benefit is permanent, automatic self-heal of stale lock columns and fewer false checkout 409s requiring board intervention > - Related upstream issue: paperclipai#6007 ## Linked Issues or Issue Description Refs paperclipai#6007. Duplicate/related PR search performed on 2026-06-10 with query `checkoutRunId process loss retry stale checkout lock repo:paperclipai/paperclip`. Related PRs found and reviewed for overlap: - paperclipai#7727 `fix(heartbeat): atomically advance checkoutRunId on process-loss retry` - paperclipai#7707 `test: cover same-agent stale checkout adoption` - paperclipai#3068 `fix: clear checkoutRunId when releasing issue execution lock` ## What Changed - `server/src/services/heartbeat.ts` `releaseIssueExecutionAndPromote`: extend the per-issue update to also null `checkoutRunId` when it matches the terminating run id. WHERE clause scoped to `executionRunId = run.id OR checkoutRunId = run.id` for idempotence. - `server/src/services/heartbeat.ts` process-loss retry: when queuing the retry run, move `executionRunId` to the retry and clear the failed run's `checkoutRunId` so the dead run no longer owns checkout. - `server/src/services/issues.ts`: add `clearCheckoutRunIfTerminal` helper, symmetric to `clearExecutionRunIfTerminal`. No assignee/status precondition. Wired into `checkout`, `assertCheckoutOwner`, and `release`. Exported on the issue service. - `server/src/services/recovery/service.ts`: add `sweepStaleIssueLocks`. Scans `issues` where `checkoutRunId IS NOT NULL OR executionRunId IS NOT NULL`, joins each referenced run, and clears all lock columns on issues whose referenced runs are all terminal or missing. Emits one `issue.stale_lock_cleared` activity log row per cleared issue. - `server/src/services/heartbeat.ts`: re-export the sweeper on the heartbeat facade. - `server/src/index.ts`: invoke `sweepStaleIssueLocks` in both the startup recovery sequence and the periodic heartbeat timer chain. - Tests: route-level coverage of the new self-heal path on the next checkout attempt, service-level sweeper coverage, and heartbeat recovery assertions that terminal process-loss cleanup releases `checkoutRunId`. ## Verification ```bash pnpm --filter @paperclipai/server typecheck pnpm --filter @paperclipai/server exec vitest run \ src/__tests__/recovery-stale-issue-lock-sweep.test.ts \ src/__tests__/issue-stale-execution-lock-routes.test.ts NODE_ENV=test pnpm exec vitest run src/__tests__/heartbeat-process-recovery.test.ts -t "queues exactly one retry when the recorded local pid is dead|does not block paused-tree work when immediate continuation recovery is suppressed by the hold" NODE_ENV=test pnpm exec vitest run src/__tests__/heartbeat-process-recovery.test.ts ``` All listed local checks pass. The new and updated tests cover: - Run termination clears `checkoutRunId` when it points at the terminating run. - Process-loss retry clears the failed run's `checkoutRunId` while assigning `executionRunId` to the queued retry. - A different agent calling `POST /api/issues/:id/checkout` on an issue whose prior owner died self-heals via `clearCheckoutRunIfTerminal` and succeeds. - Sweeper clears stale lock columns for issues whose run row is terminal. - Sweeper leaves issues alone while the referenced run is still running. - Sweeper leaves issues alone when `executionRunId` is still running even if `checkoutRunId` is terminal. - Sweeper is idempotent; second pass clears nothing. Manual reproduction of the original bug shape: 1. Create an issue assigned to agent A, set `status='in_progress'`, `checkoutRunId=R1`, `executionRunId=null`, where `heartbeat_runs.status = 'failed'` for `R1`. 2. Reassign to agent B and move to `status='todo'`. 3. Before this PR: agent B `POST /checkout` returns `409 Issue checkout conflict` indefinitely. After this PR: succeeds, lock columns rewritten to agent B's current run id. ## Risks - Low. All clears are scoped by run id, so they only fire when the lock column unambiguously points at the terminating or terminal run. No schema change. No migration. No API surface change. - Behavioral shift: an issue that previously stayed `in_progress` with a dead `checkoutRunId` after run termination now self-heals. Downstream code that reads stale `checkoutRunId` as a proxy for recent run history should already be reading `executionRunId` or the `heartbeat_runs` table. - Sweeper cost: one indexed scan per recovery tick over rows where `checkoutRunId IS NOT NULL OR executionRunId IS NOT NULL` plus a single batched `heartbeatRuns` lookup per candidate. Negligible at expected cardinality; further bounded by the existing recovery cadence. > 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`. This is a bug fix, not a feature. No roadmap overlap. ## Model Used - Claude (Anthropic), model ID `claude-opus-4-7`, extended-thinking off, tool use enabled. - OpenAI Codex, GPT-5-based coding agent, tool use enabled, used for the follow-up process-loss retry fix and PR body update. ## 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 - [ ] 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> Co-authored-by: Dotta <bippadotta@protonmail.com>
- Add shared types: MergeGateResult, GateResult - Create mergerGateService with evaluateGates function - Implement Gate 1 (issue_approval): checks for approved approvals on linked issue - Add GET /api/work-products/:id/merge-gates endpoint - Register workProductRoutes in app.ts - Add comprehensive tests for all gate states (including missing !issueId test) - Fix unnecessary type cast in route handler Closes CHRA-2443
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the Merger Gate service with Gate 1 (issue-level approval check).
Changes
Review
Closes CHRA-2443