Rebase onto upstream/master (2026-08-13): 272 commits (d5b9f6c8..f0e6c0f5) - #327
Draft
stubbi wants to merge 1016 commits into
Draft
Rebase onto upstream/master (2026-08-13): 272 commits (d5b9f6c8..f0e6c0f5)#327stubbi wants to merge 1016 commits into
stubbi wants to merge 1016 commits into
Conversation
…ting config secrets (paperclipai#10576) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents can run inside environments (SSH boxes, sandbox providers); a sandbox environment's config can reference stored company secrets (for example a provider API key) through `format: "secret-ref"` fields > - Environments are instance-scoped and shared by every company on an instance, but `company_secret_bindings` rows are company-scoped, and the environment routes synced config-derived bindings under one guessed "context company" resolved from the environment's existing bindings > - When a save re-pointed a secret-ref field at a secret owned by a different company, the binding sync threw after the config row had already been persisted: the config referenced the new secret, the binding still pointed at the old one, every later lease acquisition failed with `Secret is not bound to environment:<id> at apiKey`, and the stale cross-company binding made every later save fail with a company-context conflict — with no route-level way to recover > - This pull request makes config-derived bindings follow the company that owns each referenced secret, and makes the environment write and its binding syncs atomic > - The benefit is that environment saves can no longer strand an environment in a half-updated state that breaks all of its runs ## Linked Issues or Issue Description Refs paperclipai#10577 (companion UX change: the editor state that nudges operators into this sequence). **What happened?** Saving an environment whose secret-ref config field points at a secret owned by a different company than the environment's existing binding partially applied: the config row updated, the binding sync failed server-side, and the environment was left referencing a secret it has no binding for. Every run that leased the environment then failed with `lease_acquire_failed: ... Secret is not bound to environment:<id> at apiKey`, and every later save of the environment returned 409 `Environment secret bindings already use a different company context.` — with no route-level way to recover. **Steps to reproduce** 1. On an instance with two companies, create a sandbox environment from company A with a picker-bound API-key secret owned by A (the binding lands in A). 2. From company B, create a new secret and re-point the environment's API-key field at it, then save. 3. The save persists the config but the binding sync throws, so no binding for B's secret exists. 4. Run any agent that uses the environment, or try to save the environment again. **Expected behavior** The save either fully applies (config and bindings consistent) or fully fails. Re-pointing a config secret ref to a secret owned by another company moves the binding with the secret. **Paperclip version** Reproduced on current `master` (also present on recent release images). **Deployment mode** Multi-company server deployment (any mode with more than one company on the instance). ## What Changed - New `secretService.replaceSecretRefsForInstanceTarget`: writes each config-derived binding under the company that owns the referenced secret, replaces all non-`env.*` bindings of the target across every company, and validates every ref (secret exists, not deleted, config-path and projection-class rules) before any row is written. `env.*` env-var bindings stay company-scoped and untouched. - The environment create and update routes now run the environment write and its binding syncs inside one `db.transaction`, threading the transaction through new optional executor seams on `environmentService.create/update` and the existing `SecretBindingDb` seam pattern, so an invalid ref rolls the whole save back instead of leaving a half-updated environment. - `resolveEnvironmentSecretContextCompanyId` no longer lets existing bindings veto the caller's context (the 409s above); it now only picks where new raw-pasted secrets are created and how env-var bindings and probes resolve: explicit route/query company first, then the single company the bindings live in, then the actor's company. ## Verification - `cd server && pnpm vitest run src/__tests__/environment-routes.test.ts src/__tests__/environment-instance-routes.test.ts src/__tests__/secrets-service.test.ts src/__tests__/environment-custom-image-routes.test.ts` (165 tests, includes new coverage below) - New embedded-Postgres tests prove: a re-point moves the binding to the new secret's company and deletes the stale row; refs across several companies each bind under their own secret's company; an unknown secret ref rejects without touching existing bindings; `env.*` rows survive config-ref replacement. - New route tests prove: a cross-company re-point that previously 409'd now saves, with the update and binding replacement on the same transaction executor; a failing ref surfaces as 422. - `cd server && pnpm run typecheck` ## Risks - Behavioral shift: environment saves no longer 409 on a company-context mismatch between the caller and existing bindings; bindings follow the referenced secret's company instead. Environment routes are instance-admin gated, and instance admins already had access to every company's secrets by passing the company explicitly, so this removes an ordering trap rather than widening access. - Runtime lease resolution is unchanged: a run still resolves environment secrets under the run's own company, so an environment referencing company B's secret still only leases for company B runs (fail-closed as before). - The delete route's per-company binding cleanup is unchanged. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use (file edits, vitest/tsc runs). No other models involved. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
…f calling them missing (paperclipai#10577) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Environment configs (sandbox providers, SSH) can bind stored company secrets through `format: "secret-ref"` fields, picked in the environment editor's secret picker > - Environments are instance-scoped and shared by every company on an instance, but the picker lists only the current company's secrets, so a ref pointing at another company's secret renders as "Missing secret (…)" in destructive styling > - That state is indistinguishable from a genuinely deleted secret, so operators "fix" a healthy binding by creating a duplicate secret in their own company — the exact sequence that used to corrupt bindings before paperclipai#10576 > - This pull request adds an instance-gated metadata endpoint for an environment's secret refs and teaches the picker to name a cross-company secret and its owner honestly > - The benefit is that operators can tell a healthy cross-company binding from a broken one, and stop creating duplicate secrets ## Linked Issues or Issue Description **Is your feature request related to a problem? Please describe.** In the environment editor, a secret-ref field that points at a secret owned by a different company shows "Missing secret (22095402…)" in red, with "The previously selected secret is no longer available. Pick another or remove the binding." The binding is actually healthy — the current company's picker just cannot list the other company's secrets. Operators react by creating a duplicate secret and re-pointing the field. **Describe the solution you'd like** The editor should know the referenced secret's name, status, and owning company (metadata only, never the value) and present a cross-company ref neutrally, a deleted secret as deleted, and only an unknown id as missing. Related: paperclipai#10576 (fixes the binding corruption this UI state used to trigger). ## What Changed - New `GET /environments/:id/secret-refs` returns `{ refs: [{ configPath, secretId, name, status, companyId, companyName }] }` for the environment's config-derived secret refs. Values are never returned. The route sits behind `assertCanAccessInstanceEnvironments`, the same gate as environment editing. - New `secretService.describeSecretRefs` loads that metadata across companies; unknown ids are omitted. - `SecretBindingPicker` reads an optional `SecretRefHintsContext` (keyed by secret id). With a hint, a ref the company list cannot show renders as `NAME — Owning Company` with neutral styling and the note "Owned by the … company. The binding keeps working; selecting a secret from this list re-points it here." A hint with `status: "deleted"` reports the secret as deleted. Without hints, behavior is byte-identical to before — agent editors and other picker users are unaffected. - `CompanyEnvironments` fetches descriptors for the environment being edited and provides them through the context. ## Verification - `cd server && pnpm vitest run src/__tests__/environment-routes.test.ts src/__tests__/secrets-service.test.ts` — new endpoint happy path, agent 403 (descriptors never computed), and embedded-Postgres coverage proving cross-company names resolve and unknown ids drop out. - `cd ui && pnpm vitest run src/components/SecretBindingPicker.test.tsx src/components/JsonSchemaForm.test.tsx src/pages/CompanyEnvironments.test.tsx` — hinted cross-company rendering, hinted deleted secret, and unchanged no-hint fallback. - `pnpm run typecheck` in `server` and `ui`. - Manual: edit an environment whose secret-ref field references another company's secret; the field names the secret and its owning company instead of "Missing secret". ## Risks - The endpoint exposes secret names and company names across companies to instance-level environment editors. Those actors already manage instance-shared environments (and instance admins are implicit members of every company), so this reveals no secret material and no new reach; the service method documents that callers must sit behind an instance-level gate. - UI change is additive and context-gated; pickers without a provider render exactly as before. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use (file edits, vitest/tsc runs). No other models involved. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
<!-- Write all pull request text in Simplified Technical English (ASD-STE100): short sentences, one instruction per sentence, simple approved vocabulary, and the active voice. --> ## Thinking Path > - Paperclip coordinates AI agents through scheduled heartbeat runs. > - The heartbeat scheduler can call `tickTimers()` again before an earlier tick has finished. > - Each overlapping tick can read the same old `lastHeartbeatAt` value and decide that the same agent is due. > - The existing queue checks do not make that due-time decision atomic. > - This pull request atomically advances the timer baseline before it enqueues the wake. > - The benefit is that one timer interval can create at most one scheduled run for an agent. ## Linked Issues or Issue Description No public GitHub issue describes this exact scheduler race. Related pull requests address active-run overlap or queued-run buildup, but they do not atomically claim a due timer interval: paperclipai#9457, paperclipai#8416, and paperclipai#3858. **What happened?** Two overlapping calls to `tickTimers()` could both read the same due timer baseline. Both calls could enqueue a timer run for the same agent and interval. **Expected behavior** Only one scheduler tick must claim a due timer interval. A second overlapping tick must observe that the interval was already claimed and skip it. **Steps to reproduce** 1. Create an active agent with a 60-second timer interval. 2. Set `lastHeartbeatAt` to more than 60 seconds in the past. 3. Call `tickTimers(now)` twice with `Promise.all()`. 4. Observe that the old code can enqueue two runs for the same interval. **Paperclip version or commit** Reproduced on `master` before this branch. **Deployment mode** Local development with embedded PostgreSQL. ## What Changed - Added an atomic conditional update that claims a due timer interval by advancing `lastHeartbeatAt`. - Made `tickTimers()` enqueue only after that conditional update succeeds. - Preserved first-heartbeat telemetry when the timer claim advances `lastHeartbeatAt` before run completion. - Added regression tests for concurrent claims and first-heartbeat telemetry. ## Verification - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-stale-queue-invalidation.test.ts` — 24 tests passed on the final rebased commit. - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-process-recovery.test.ts -t "preserves first-heartbeat telemetry after a timer interval claim|tracks the first heartbeat with the agent role"` — 2 tests passed. - `pnpm --filter @paperclipai/server typecheck` — passed after the review fix. - `pnpm -r typecheck` — passed. - `pnpm build` — passed. - `pnpm test:run` — 3,121 tests passed and 2 tests skipped. One unrelated runtime-skills test exceeded its 5-second limit under full-suite load. - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-runtime-skills.test.ts` — the timed-out file passed in isolation, 2 tests passed. - `pnpm --filter @paperclipai/db exec vitest run src/status-card-migrations.test.ts` — the unrelated CI timeout passed in isolation. - The full PR CI matrix passed after one rerun of that unrelated timeout. - Greptile passed with zero new comments and no unresolved review threads. ## Risks - Low risk. The change only affects due timer claims. - If enqueue fails after the claim, the next timer attempt waits for one interval. This is safer than duplicate agent execution. - No schema, migration, API, UI, or dependency changes are included. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex based on GPT-5. The exact deployment ID and context-window size are not exposed to this runtime. Agentic reasoning, shell tools, code execution, and GitHub operations were enabled. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
…aperclipai#10582) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Codex agents can run inside sandbox environments, and operators can bake a Codex login into the sandbox image during interactive image setup > - Two credential gates (the control plane's pre-dispatch configuration-incomplete gate and the adapter's execute-time fail-fast) required host-side Codex credentials — a usable `auth.json` in the managed home or a configured `OPENAI_API_KEY` — regardless of where the run executes > - On managed cloud hosts a local Codex login never exists, so every sandbox run of a Codex agent failed immediately with "configuration incomplete: no Codex credentials available for managed home …", even though the adapter's inbound auth merge already supports the image-login case end to end > - This pull request makes the execute-time gate probe the sandbox for its own `~/.codex/auth.json` before failing, and exempts sandbox-destined runs from the pre-dispatch host check > - The benefit is that a sandbox image signed in to Codex is a first-class credential source, matching what the auth-merge, precedence-warning, and copy-back machinery were already built for ## Linked Issues or Issue Description **What happened?** Running a `codex_local` agent in a sandbox environment whose image carries a Codex login failed instantly with `configuration incomplete: no Codex credentials available for managed home "…/codex-home". Sign in to Codex on the host with a ChatGPT subscription, or bind a per-agent OPENAI_API_KEY secret for this agent.` The host has no Codex login and never will on a managed cloud deployment; the sandbox's own login was never consulted. **Steps to reproduce** 1. Configure a sandbox environment and capture a custom image after signing in to Codex inside the interactive image setup. 2. Create a `codex_local` agent that uses that environment, on a host with no Codex login and no `OPENAI_API_KEY` bound. 3. Start a run: it fails pre-dispatch with the configuration-incomplete blocker above. **Expected behavior** The run launches and Codex authenticates with the sandbox image's own login, the same way the adapter's host↔sandbox auth merge already keeps the sandbox credential when the host ships none. A run should only fail fast when neither the host, a bound `OPENAI_API_KEY`, nor the sandbox has credentials. **Paperclip version** Current `master` (cloud image deployments). **Deployment mode** Managed cloud stacks (any deployment where the server host has no local Codex login). ## What Changed - Extracted the adapter's execute-time gate into `assertCodexCredentialsLaunchable`: when host readiness fails and the target is a sandbox, it probes `~/.codex/auth.json` in the sandbox (same command the auth-precedence warning uses) and proceeds with a log line naming the credential source; when the sandbox has no login either, the error now names all three remediation options (sandbox image sign-in, per-agent `OPENAI_API_KEY`, host sign-in). Non-sandbox targets keep today's strict behavior byte-for-byte. - The control plane's pre-dispatch gate in `resolveExecutionRunAdapterConfig` now takes the selected environment's driver and skips the host-credential check for sandbox-destined runs — only the adapter can probe the sandbox once it is up, so the execute-time gate is the authority there. Non-sandbox runs keep the early, well-attributed configuration-incomplete blocker. - The codex Test flow needed no change: it already seeds host credentials only when they exist and otherwise leaves the sandbox's `CODEX_HOME` alone; this aligns the run path with it. ## Verification - `cd packages/adapters/codex-local && pnpm vitest run` — 210 tests, including new gate cases: sandbox login present (proceeds + logs source), sandbox and host both credential-less (fails with the extended message), non-sandbox target (strict host requirement kept, no sandbox probe), per-agent API key (no probe at all). - `cd server && pnpm vitest run src/__tests__/heartbeat-project-env.test.ts src/__tests__/codex-local-adapter-environment.test.ts` — includes the new sandbox-exemption case next to the existing blocker tests. - `pnpm run typecheck` in `server` and `packages/adapters/codex-local`. ## Risks - Sandbox-destined misconfigurations (no credentials anywhere) now surface at adapter execute time instead of pre-dispatch, so they read as an adapter failure with a precise message rather than a configuration-incomplete blocker. The trade-off is deliberate: the sandbox must be up to know whether credentials exist, and the failure message names the exact remediations. - The sandbox probe adds one short (5s-capped) shell command to sandbox runs whose host has no credentials; runs with host credentials or a bound key are untouched. - Self-hosted behavior is unchanged for local and SSH targets. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use (file edits, vitest/tsc runs). No other models involved. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
…aperclipai#10474) The Decisions queue ran five parallel colour/icon vocabularies chosen by source kind, plus a separate severity badge, so two rows needing the same response could look unrelated and none of it matched the task list. Every row now resolves to one of two kinds, each borrowing the task status it corresponds to: blocking renders as `blocked`, review as `in_review`, both through StatusGlyph and the existing --status-task-icon-* tokens. Source kinds keep their own wording; only colour and icon merge. Card anatomy follows the design mock: no left accent rail, rounded cards 16px apart, a "/"-separated meta breadcrumb, a named See more / See less control, and no separately tinted drawer when expanded. Verb order is fixed across both states. Severity moves from chrome to a toolbar filter. Four defects fixed along the way: - blocked rows reported themselves as their own blocker (server-side) - the task key was missing wherever the row's subject IS the task - the task quicklook stuck open, because closing handed focus back to a trigger that opens on focus - the card ring appeared on click, and only on cards with a toggle Also: the standard task preview is aligned to its trigger's text and scales out of it, the task eyebrow renders its project as a tile, and the first motion tokens land alongside the disclosure and crossfade. Supersedes paperclipai#9574 and paperclipai#9575. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lipai#10045) ## Thinking Path > - Paperclip is the open source control plane people use to manage AI-agent companies > - Operators need a predictable installation path that survives beyond an ephemeral `npx` process > - A durable installation needs an owned per-user payload store, stable command shim, safe shell integration, and supported service lifecycle > - Updates must preserve recoverability by backing up data, installing side-by-side, verifying the new payload, and retaining rollback state > - Bootstrap scripts and privileged service operations must fail closed across download, filesystem, ownership, and consent boundaries > - This pull request integrates managed install, update, rollback, service, uninstall, doctor, bootstrap-installer, and runtime-serving support into one workflow > - The benefit is a recoverable, inspectable, and documented installation lifecycle with explicit safety boundaries across Linux, macOS, containers, WSL, npm, npx, and source checkouts ## Linked Issues or Issue Description ### Problem Paperclip lacks a first-class durable installation and lifecycle workflow. Operators currently have to assemble npm/npx installation, PATH setup, background-service management, updates, rollback, diagnostics, and uninstall behavior themselves. That makes upgrades harder to recover, creates inconsistent behavior across platforms, and leaves shell/download/service trust boundaries without one documented implementation. ### Proposed Solution Add a managed per-user install store and stable shim, a verified shell bootstrap installer, service lifecycle commands, install-mode-aware update/rollback behavior, doctor checks, and documentation. Managed updates back up the database, install and smoke-test a side-by-side payload, atomically switch `current`, and retain prior payloads. The shell installer pins registry/download trust boundaries and requires explicit consent for non-interactive privileged actions. ### Alternatives Considered - Keep recommending `npx`: simple for evaluation, but ephemeral and unsuitable for stable services, atomic updates, or rollback. - Require global npm installation only: familiar, but cannot provide the owned side-by-side payload store and retained rollback semantics. - Split the capability across multiple PRs: rejected because install, update, service, uninstall, bootstrap, and serving behavior share contracts and security boundaries that need review together. ### Related Pull Requests - Supersedes paperclipai#10042 and paperclipai#10044 with one integrated final diff. - Incorporates and replaces the closed preparatory work in paperclipai#10032 and paperclipai#10034. ## What Changed - Added `paperclipai install`, `update`/`upgrade`, rollback, uninstall, service lifecycle, onboarding integration, and managed-install doctor checks. - Added a private managed payload store, verified manifest/marker ownership, exclusive mutation locks, atomic manifest/current/shim writes, retained previous payloads, and provenance validation. - Added npm and GitHub-ref install sources with exact target resolution, registry isolation, database backup, side-by-side verification, atomic activation, service restart coordination, and failure rollback. - Made managed-update backups report actionable service-start and `--no-backup` recovery guidance for unreachable databases, while clean never-onboarded instances skip an empty backup. - Added systemd user and launchd service definitions, status/health/log commands, single-instance coordination, stale-port recovery, and explicit sudo/lingering consent handling. - Added the `scripts/install.sh` bootstrap path with checked two-stage downloads, pinned public npm registry usage, platform checks, dry-run/non-interactive controls, and Docker fixtures. - Added embedded Postgres/native bootstrap integration, hot-restart/systemd-notify serving support, passive update notices, configuration contracts, README/CLI/install documentation, and focused regression tests. - Security re-review should explicitly re-verify: (1) `addManagedPathBlock`/`removeManagedPathBlock` reject symlinked or non-regular rc files, assert current-user ownership, preserve restrictive modes, and replace atomically; (2) managed shim replacement rejects unsafe parents, foreign-owned or multiply linked files, and uses checked atomic replacement; (3) the shell installer and sudo path preserve explicit consent and checked downloads; and (4) installed service/runtime serving remains bound to the validated managed shim and instance configuration. ## Verification - `bash -n scripts/install.sh scripts/clean-install-git.sh scripts/clean-install-npm.sh scripts/test-install-sh-docker.sh` - `pnpm exec vitest run cli/src/__tests__/install-store.test.ts cli/src/__tests__/install-command.test.ts cli/src/__tests__/managed-install-check.test.ts cli/src/__tests__/onboard-service.test.ts cli/src/__tests__/service-health-check.test.ts cli/src/__tests__/service-manager.test.ts cli/src/__tests__/update-command.test.ts cli/src/__tests__/update-notice.test.ts packages/db/src/embedded-postgres-native.test.ts` — 9 files, 66 tests passed - `pnpm --dir cli typecheck` - `pnpm --dir cli build` - Follow-up verification: `pnpm exec vitest run cli/src/__tests__/update-command.test.ts` (14/14), `pnpm --dir cli typecheck`, `pnpm --dir cli build`, and `pnpm --filter @paperclipai/server typecheck`. - `pnpm -r typecheck` - `pnpm build` - Full `pnpm test:run` exercised all suites; an injected static AWS credential changed one unrelated doctor expectation, which passed when those credentials were removed. A second run cleared that case and exposed stale pre-existing adapter-utils `dist` output; rebuilding `@paperclipai/adapter-utils` made the isolated test pass. The updated PR CI is the authoritative clean-workspace full-suite run. ## Risks - Installer/update code writes executable shims, symlinks, shell rc blocks, service definitions, and managed payloads; ownership, regular-file, symlink, hard-link, marker, and path-containment checks fail closed before destructive changes. - The bootstrap installer executes downloaded tooling; downloads are staged and checked before execution, npm traffic is pinned to the public registry, and non-interactive privileged behavior requires explicit consent. - Linux lingering may invoke `sudo`; the command is surfaced and confirmed before execution, and unsupported service managers fall back to foreground-run guidance. - Database migrations remain forward-only; payload rollback does not reverse migrations, so managed updates create a backup before activation unless explicitly disabled. - Service restart and runtime serving touch process/port ownership; lifecycle locks, health/version checks, and stable-shim service definitions reduce split-brain and stale-process risk. > 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 agents using GPT-5.5 and GPT-5.6-sol, with reasoning, repository/API access, shell execution, and test tooling. The runtime did not expose a reliable context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…-gt (paperclipai#10466) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The web UI uses one shared markdown editor for comments, issue descriptions, and documents. > - Users type `>` at the start of a line to insert a blockquote. > - The live editor shortcut does not always run in every browser and input method. > - The markdown exporter then changes the leading `>` to `\>` and saves literal text. > - The saved text does not render as a blockquote. > - This pull request restores the blockquote marker when markdown enters or leaves the editor. > - The benefit is reliable blockquote insertion on every surface that uses the shared editor. ## Linked Issues or Issue Description No public GitHub issue exists. Related prior attempt: paperclipai#10465. **What happened?** The shared markdown editor sometimes saved a blockquote as literal text. This happened when the live shortcut did not run. The exporter saved `\> text`, which rendered as literal `> text`. **Expected behavior** A line that starts with `>` must render as a blockquote in comments, issue descriptions, and documents. **Steps to reproduce** 1. Open a task comment composer, description editor, or document editor. 2. Add `> ` to an existing line, or use an input method that does not run the live shortcut. 3. Save the content. 4. Observe that the saved line renders as literal text instead of a blockquote. **Paperclip version or commit** `master` at `78f8c6c3d4`. **Deployment mode** Self-hosted server. ## What Changed - Add `unescapeBlockquoteMarkers()` to restore block-level `\>` markers. - Keep indented code, list content, nested content, and fenced code unchanged. - Apply the helper when markdown enters and leaves `MarkdownEditor`. - Add focused tests for line position, indentation, container prefixes, and CommonMark fence rules. ## Verification - `pnpm exec vitest run ui/src/lib/blockquote-markdown.test.ts` passes with 22 tests. - `pnpm exec vitest run ui/src/components/MarkdownEditor.test.tsx` passes with 37 tests. - `pnpm --filter @paperclipai/ui typecheck` passes. - `pnpm check:token-gates` passes. - `git diff --check origin/master...HEAD` passes. - A browser harness used the real `MarkdownEditor` and `IssueChatThread` composer. It confirmed that `> text` renders as a blockquote and exports as `> text`. - The [Cutter preview](paperclipai#10466 (comment)) supplies a task-page screenshot and an editor interaction video. ## Risks - Low risk. The helper returns the input unchanged when it contains no `\>`. - A paragraph that deliberately starts with literal `\>` now becomes a blockquote. The editor has no literal-marker control, so this matches the available input behavior. - There are no database, API, or migration changes. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Anthropic Claude Opus 4.8 (`claude-opus-4-8`, 1M context), extended thinking, with tool use and code execution. - OpenAI Codex with GPT-5 (`gpt-5`; runtime build and context-window metadata were not exposed), with reasoning, tool use, code execution, and GitHub review tools. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes (none needed) - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
<!-- Write all pull request text in Simplified Technical English (ASD-STE100): short sentences, one instruction per sentence, simple approved vocabulary, and the active voice. --> ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Agents update tasks through the issue API. > - The update response did not state which values changed. > - Blocker updates also did not echo the scalar blocker IDs. > - Agents therefore used an extra GET request to confirm a successful write. > - This pull request adds an authoritative change receipt and an optional small response. > - The benefit is fewer API calls with a clear and compatible write contract. ## Linked Issues or Issue Description No public GitHub issue exists for this change. ### Subsystem affected Cross-cutting: `server/`, `packages/shared`, and the UI issue cache. ### Problem or motivation A successful issue PATCH returned the updated issue, but it did not identify the effective changes. Blocker writes returned relation summaries without the scalar IDs. Agents could not distinguish a confirmed clear operation from missing data. The response must confirm committed field and blocker changes while existing UI clients continue to receive the full issue by default. ### Proposed solution Add a `changes` receipt. Add a conditional `blockedByIssueIds` echo. Support `Prefer: return=minimal`. Keep the full response as the default. ### Alternatives considered Make the small response the default for agent tokens. This would create different response contracts by actor type, so this pull request does not use that design. ### Roadmap alignment This is a focused control-plane reliability improvement. It does not duplicate an open roadmap milestone. ## What Changed - Compute committed issue row and relation changes in the issue service. - Omit no-op fields and truncate changed long text values to 200 characters. - Echo blocker ID arrays for blocker set and clear requests. - Add the opt-in `Prefer: return=minimal` response and `Preference-Applied` header. - Keep receipt metadata out of React Query issue caches. - Add route and embedded Postgres tests for the new contract. ## Verification - `pnpm exec vitest run server/src/__tests__/issue-activity-events-routes.test.ts` - `pnpm exec vitest run server/src/__tests__/issues-service.test.ts -t "returns authoritative update receipts for row fields and blocker relations"` - `pnpm --filter @paperclipai/shared typecheck` - `pnpm --filter @paperclipai/server typecheck` - `pnpm --filter @paperclipai/ui typecheck` - `pnpm check:token-gates` - `git diff --check` ## Risks - Low compatibility risk. The default response only adds receipt fields. - Minimal mode is opt-in. Existing clients do not receive a smaller body. - The receipt excludes `updatedAt` because the response already returns it as the freshness anchor. - Prose API and agent workflow guidance will follow after the server contract is available. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex based on GPT-5. The exact deployment ID, context window size, and reasoning mode are not exposed to the agent. The agent used repository tools, code execution, and test execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
…i#10586) ## Thinking Path > - Paperclip uses company skills to give agents repeatable operating workflows. > - The garden-inbox skill asks a user to confirm reversible archive candidates. > - A user can leave a candidate unchecked because they want to keep it visible. > - A later confirmation pass currently checks that candidate again by default. > - This pull request adds a repeatable `--unselect` option for candidates declined in an earlier pass. > - The benefit is that repeated confirmation cards preserve the user's prior choice and make that history visible. ## Linked Issues or Issue Description **What happened?** When an inbox gardening confirmation was created again, candidates declined in an earlier pass could start checked again. **Expected behavior** The caller can identify previously declined candidates. Those candidates start unchecked and explain why they are unchecked. **Steps to reproduce** 1. Create a garden-inbox scan with an archive candidate in bucket A or B. 2. Leave the candidate unchecked in a confirmation pass. 3. Create a later confirmation for the same candidate. 4. Observe that the default selection does not preserve the earlier decline. **Paperclip version or commit** `7301fae942c3d5826974335cb40d6f1e0d95d1e0` **Deployment mode** Built from source. ## What Changed - Added repeatable `--unselect ISSUE_ID` parsing to the garden-inbox confirmation command. - Removed those issue IDs from the default checked options. - Added a description note for candidates declined in an earlier pass. - Rejected `--unselect` values that are not offered by the current scan. - Documented the repeat-pass workflow and added regression coverage. ## Verification - `node --test .agents/skills/garden-inbox/scripts/garden-inbox.test.mjs` - `git diff --check origin/master...HEAD` ## Risks - Low risk. The new option is opt-in, and existing confirmation behavior is unchanged when it is omitted. - An invalid issue ID now fails before any confirmation card is posted. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5 family. The runtime does not expose the exact deployment model ID or context-window size. Reasoning, repository tools, shell execution, and GitHub tools were enabled. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
…0587) ## Thinking Path > - Paperclip is the open source control plane people use to manage AI-agent companies > - Managed updates can change both the application payload and its database schema > - Unit tests cannot prove that an older live install upgrades through a real migration and remains recoverable > - The managed-install work in paperclipai#10045 needs a repeatable cross-version system test > - This pull request adds an isolated end-to-end harness for update, migration, backup, service restart, and rollback behavior > - The benefit is a direct proof that managed upgrades preserve the existing database and service lifecycle across versions ## Linked Issues or Issue Description Refs paperclipai#10045 This test is a focused follow-up to the managed install integration. Merge paperclipai#10045 first so the tested install, update, service, backup, and rollback commands are available. ## What Changed - Added a cross-version managed-update E2E script. - Installed an older Git ref, initialized its embedded PostgreSQL database, and updated to a ref with one additional migration. - Verified the pre-update backup, payload switch, service recovery, migration result, database-cluster reuse, and rollback behavior. - Isolated Paperclip state under a dedicated test home and cleaned up the service and managed install on success or failure. - Added regression tests for shell syntax, required-ref validation, side-effect-free preflight failure, and complete failure cleanup. ## Verification - `node --test scripts/__tests__/e2e-update-migrations.test.mjs` - `bash -n scripts/e2e-update-migrations.sh` - GitHub latest-head CI: build, typecheck, release registry, canary dry-run, general tests, serialized suites, and both browser E2E shards passed. - Full harness execution needs an isolated macOS or Linux host with a real launchd or systemd user service. It is intentionally not run on a live Paperclip server host. ## Risks - The script manages a real user service and downloads two Git refs. Run it only on an isolated test host. - The test needs paperclipai#10045 because `origin/master` does not yet contain the managed install lifecycle. - The script uses a dedicated `PAPERCLIP_HOME`, refuses a pre-existing shim or test home, and removes its service and install during cleanup. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex based on GPT-5. The runtime did not expose a more specific deployment ID or context-window size. Reasoning, repository access, shell execution, and GitHub tooling were enabled. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
…10588) <!-- Write all pull request text in Simplified Technical English (ASD-STE100): short sentences, one instruction per sentence, simple approved vocabulary, and the active voice. --> ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Local agent wakes include a default execution contract > - That contract tells agents how issue-thread continuation policies behave > - The current text says `wake_assignee` resumes a confirmation only after acceptance > - The server actually wakes for every non-expired resolution and reserves acceptance-only behavior for `wake_assignee_on_accept` > - This pull request makes the default prompt match the server contract and strengthens the recovery follow-up regression case > - The benefit is that agents choose the correct continuation policy and recovery tests cover normalized agent name keys ## Linked Issues or Issue Description Related work: Refs paperclipai#5473, Refs paperclipai#5060, and Refs paperclipai#10562. **What happened?** The default local-agent prompt described `wake_assignee` as acceptance-only for `request_confirmation`. This conflicts with the server. The server wakes on every non-expired resolution. A recovery follow-up test also used an already-normalized execution agent name key, so it did not exercise the normalization seam. **Expected behavior** The prompt must state that `wake_assignee` resumes after acceptance or rejection. It must direct acceptance-only flows to `wake_assignee_on_accept`. The recovery regression must use a display-style agent name key and prove that the follow-up path still works after normalization. **Steps to reproduce** 1. Read the default local-agent prompt in `packages/adapter-utils/src/server-utils.ts`. 2. Compare its confirmation continuation text with `queueResolvedInteractionContinuationWakeup` in `server/src/routes/issues.ts`. 3. Observe that the prompt gives acceptance-only semantics to `wake_assignee`. 4. Inspect the recovery hand-back test and observe that its execution name key is already normalized. **Paperclip version or commit** `7301fae942` **Deployment mode** Local dev. The prompt and test behavior are not deployment-specific. ## What Changed - Corrected the default agent prompt for `wake_assignee` and `wake_assignee_on_accept`. - Added focused prompt assertions for both the required and obsolete text. - Changed the recovery follow-up fixture to use a display-style agent name key. ## Verification - `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts -t 'keeps the default local-agent prompt action-oriented'` passed: 1 test. - `pnpm exec vitest run server/src/__tests__/heartbeat-comment-wake-batching.test.ts -t 'defers recovery hand-back wakes until the resolving run exits'` passed: 1 test. - `pnpm --filter @paperclipai/adapter-utils typecheck` passed. - `pnpm --filter @paperclipai/server typecheck` passed. - `git diff --check origin/master...HEAD` passed. ## Risks - Low risk. The production change updates prompt text only. - Agents that followed the old text may now choose `wake_assignee_on_accept` for acceptance-only flows. - The server test change only broadens an existing regression fixture. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex with GPT-5. The exact serving model ID and context-window size are not exposed to the agent. The model used reasoning, repository tools, tests, Git, and GitHub CLI access. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
…10589) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Execution workspaces can inherit runtime services from a project workspace > - A project workspace keeps current and historical runtime service rows > - The execution workspace read path returned all current rows, including services removed from the current configuration > - This pull request matches inherited rows to the current service definitions > - The benefit is bounded workspace payloads and accurate service summaries ## Linked Issues or Issue Description **What happened?** Shared execution workspaces returned current historical service rows that no longer matched the project workspace configuration. The response size multiplied across every shared execution workspace. **Expected behavior** Shared execution workspaces must return only the newest runtime service row for each service in the current project workspace configuration. **Steps to reproduce** 1. Create one project workspace with many historical runtime service rows. 2. Create many shared execution workspaces that inherit that project workspace. 3. List the execution workspaces and inspect each `runtimeServices` array. **Paperclip version or commit** `7301fae942c3d5826974335cb40d6f1e0d95d1e0` **Deployment mode** Built from source. The defect is in the server read model and is not deployment-specific. No duplicate or related public issue or pull request was found. ## What Changed - Select only runtime service rows that match the current project workspace service definitions. - Preserve each matched service definition index in the API result. - Avoid loading direct execution service rows for workspaces that inherit project services. - Add unit, integration, and volume regression coverage. ## Verification - `pnpm --dir server exec vitest run src/services/workspace-runtime-read-model.test.ts src/__tests__/execution-workspaces-service.test.ts -t 'selectConfiguredRuntimeServiceRows|returns full details at the observed volume|inherits only runtime-service rows'` - `pnpm --filter @paperclipai/server typecheck` The focused test run passed 4 tests and skipped 27 unrelated tests. ## Risks The read path now omits service rows that do not match the current configuration. This is the intended behavior for inherited runtime services. The change does not alter service persistence or lifecycle transitions. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex with model ID `gpt-5`. The context-window size is not exposed to this run. The run used reasoning, repository tools, code execution, and GitHub tools. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The UI test suite protects the board route table > - The Cases routing regression test needs only the route table and sentinel pages > - The test initialized the full cloud access query flow for each route > - That unrelated setup made the two assertions spend several seconds polling > - This pull request isolates the routing dependency and removes the long timeout > - The benefit is faster and more focused route regression coverage ## Linked Issues or Issue Description **What happened?** The Cases routing regression test initialized cloud health, session, and board access queries. Its two route assertions spent about 6.69 seconds in test execution. **Expected behavior** The route regression test must bypass unrelated cloud access checks and resolve the two route assertions synchronously. **Steps to reproduce** 1. Run `pnpm --dir ui exec vitest run src/App.cases-routing.test.tsx` on the base commit. 2. Inspect the Vitest test duration. 3. Observe that the test waits through unrelated query transitions. **Paperclip version or commit** `7301fae942c3d5826974335cb40d6f1e0d95d1e0` **Deployment mode** Built from source. The defect affects the UI unit test suite. Related pull request: paperclipai#9198 introduced the Cases route regression coverage. ## What Changed - Mock `CloudAccessGate` at the routing boundary. - Import the app after hoisted CSS setup and module mocks. - Remove the query client and three unrelated API mocks. - Replace long polling with a bounded three-turn route wait. - Remove the custom 20-second test timeouts. ## Verification - `pnpm --dir ui exec vitest run src/App.cases-routing.test.tsx` - `pnpm --filter @paperclipai/ui typecheck` - `pnpm check:token-gates` The focused run passed both tests. Test execution changed from about 6.69 seconds on the base commit to 40 milliseconds on this branch. ## Risks Low risk. The production route table is unchanged. The test still renders the real `App` route table and the same sentinel pages. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex with model ID `gpt-5`. The context-window size is not exposed to this run. The run used reasoning, repository tools, code execution, and GitHub tools. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
…ai#9744) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Operators need an audit record of agent actions across tasks, comments, documents, approvals, and runs > - The permission-gated audit read API provides that record, but operators cannot inspect it in the product > - A readable UI must preserve company boundaries, server-side permission decisions, and redaction > - Audit exports must also be safe to open in spreadsheet software and must record the export itself > - This pull request adds company and per-agent audit views plus a guarded CSV export > - The benefit is a searchable, filterable, and reviewable agent action history with direct links back to work ## Linked Issues or Issue Description **Feature.** This change adds the frontend and CSV export for the agent action audit log. Refs paperclipai#9731 and paperclipai#9735. - Problem: agent actions are recorded, but operators have no readable product surface to inspect or export them. - Solution: add a company audit page and a per-agent Audit tab that use the permission-gated audit API. - Alternative: build a separate plugin-only surface. This was rejected because the existing permission model already supports a unified, server-authoritative view. This pull request targets the audit epic branch, which contains the merged paperclipai#9735 audit API. ## What Changed - Added a company Audit page and sidebar entry. - Added a per-agent Audit tab with a fixed agent filter. - Added filters for agent, responsible user, action domain, entity type, and date range. - Added task and run links, responsible-user context, cursor pagination, and readable action text. - Added a permission-denied Enterprise card for callers without `audit:view_agent_actions`. - Added a CSV export that is permission-gated, capped, self-audited, CSV-escaped, and protected against spreadsheet formula injection. - Preserved the merged audit API cursor validation, redaction, and sub-millisecond pagination behavior. ## Verification - `pnpm exec vitest run ui/src/pages/audit/AuditFeed.test.tsx` — 6 passed. - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/agent-action-audit-routes.test.ts` — 8 passed with embedded PostgreSQL. - `pnpm -r typecheck` — passed across all workspaces. - `pnpm build` — passed across all workspaces. - `pnpm test:run` — all completed shards passed except one environment-sensitive CLI assertion caused by injected static AWS credential variables; the exact test passes 8/8 with those variables unset. - Manual Chromium QA exercised the populated feed, active filters, permission-denied card, per-agent tab, and CSV export. ## Screenshots and Manual QA - [All audit states exercised in Chromium](paperclipai#9744 (comment)) - [Detailed browser report and per-agent tab root cause](paperclipai#9744 (comment)) The per-agent redirect defect found during QA is fixed in this branch. ## Risks Low to moderate risk. The UI and export route are additive and use the existing company-scoped permission gate. The main risks are large exports and spreadsheet interpretation. The export is capped at 10,000 rows, records truncation accurately, and prefixes formula-like cells as text. There are no schema changes or migrations. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Anthropic Claude Opus 4.8, 1M context, extended thinking, tool use, and code execution produced the original implementation. - OpenAI Codex, GPT-5 (deployment ID and context window not exposed), reasoning, tool use, code execution, browser-test orchestration, and GitHub review tooling repaired and verified the pull request. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents can currently perform many mutations directly, while humans often need a durable review point before cross-issue or destructive actions occur > - Existing approvals and issue-thread interactions do not provide a standalone, reusable object for presenting options, collecting typed inputs, detecting stale targets, and auditing effect execution > - The control plane therefore needs a first-class propose mode that separates an agent's recommendation from the governed mutation it may cause > - This pull request adds Decisions v1 across the database, shared contracts, server execution and telemetry, agent skill guidance, and operator UI > - The benefit is that agents can propose multi-option actions safely while operators get explicit provenance, fail-closed execution, per-effect results, and a focused attention workflow ## Linked Issues or Issue Description ### Subsystem affected Cross-cutting: `packages/db`, `packages/shared`, `server`, and `ui`. ### Problem or motivation Agents need a governed way to propose consequential work without immediately mutating issues, especially when one choice can affect several issue trees. Existing approvals and issue-thread interactions do not provide a standalone object with typed options, target snapshots, effect-level authorization, expiration, execution outcomes, and reusable attention-feed presentation. ### Proposed solution Add first-class Decisions that store options and typed inputs, surface open proposals in the operator attention feed, validate target freshness and the origin-agent/operator authorization intersection at decision time, execute a bounded set of auditable effects, and retain terminal outcomes. Decisions v1 supports comments, status and assignee changes, follow-up issue creation, blocker resolution, and issue-tree cancellation, plus bundle grouping, expiration/dismissal, rule-key telemetry, and agent-facing API guidance. ### Alternatives considered - Extend approvals with arbitrary effects: rejected because approvals represent governed yes/no actions and would become an unsafe generic mutation envelope. - Model every proposal as an issue-thread interaction: rejected because decisions can span several targets and need independent lifecycle, telemetry, idempotency, and effect results. - Let agents perform the mutation and ask for retrospective review: rejected because it removes the pre-execution governance boundary this feature is meant to provide. ### Roadmap alignment Aligns with `ROADMAP.md` sections **Agent Reviews and Approvals**, **Enforced Outcomes**, **MCP Tool Gateway & Apps (governed tool access)**, and **Activity History** by making explicit decisions, authorization gates, auditable execution, and terminal outcomes first-class control-plane objects. ### Additional context This does not replace existing approvals or issue-thread interactions, and it does not add an unrestricted generic mutation effect. ## What Changed - Added company-scoped decision, option, target, and effect-execution schema plus migration and shared TypeScript/Zod contracts. - Added decision routes and services for propose, list/get, decide, dismiss, cancel, target freshness checks, authorization intersection, idempotency, activity logging, and execution auditing. - Added rule-key decision telemetry and attention-feed metadata so open decisions are visible and measurable. - Added agent skill documentation for proposing and resolving decisions through the Paperclip API. - Added the Decisions UI: API client, query keys, inline attention resolver, bundle grouping, target-issue strip, terminal history, destructive confirmation, and per-effect result rendering. - Added server service coverage, DecisionCard state tests, and Storybook stories for the supported visual states. ## Verification - `pnpm -r typecheck` — passed. - `pnpm test:run` — 2,876 passed, 1 skipped, with one unrelated cross-suite cleanup-order failure in `heartbeat-responsible-user-invariant.test.ts`; the failing file passes in isolation (`6/6`). - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-responsible-user-invariant.test.ts` — passed. - `pnpm --filter @paperclipai/ui exec vitest run src/components/DecisionCard.test.tsx` — passed (`9/9`). - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/authz-existence-oracle-guard.test.ts src/__tests__/openapi-routes.test.ts` — passed (`5/5`). - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/decisions-service.test.ts` — passed (`16/16`). - `pnpm --filter paperclipai exec vitest run src/__tests__/company-import-export-e2e.test.ts` — passed (`1/1`). - `pnpm --filter @paperclipai/server typecheck` and `pnpm --filter paperclipai typecheck` — passed. - `pnpm build` — passed. - Rebased-head focused suite — passed (`6` files, `88` tests): shared decision contracts, Decisions service, OpenAPI routes, startup feedback export, DecisionCard states, and attention helpers. The follow-up stale-secondary-target regression passes in the DecisionCard suite (`10/10`). - Rebased-head scoped typechecks — passed for `@paperclipai/shared`, `@paperclipai/db`, `@paperclipai/server`, and `@paperclipai/ui`. - Rebased-head migration numbering and safety checks — passed after renumbering the additive migration to `0193` and making it replay-safe for environments that applied the earlier feature-branch number. - `pnpm check:token-gates` — passed with all gates clean. - GitHub PR workflow and Greptile review for `1f9f7645882d05dfdd9c99377c03a1f53f20e8be` — running after the stale-secondary-target fix and PR metadata refresh on July 27, 2026. - `pnpm --filter @paperclipai/ui build-storybook` exposes an existing Storybook version mismatch (`storybook` 10.4.6 vs `@storybook/addon-docs` 10.5.0); Decisions stories were validated with the docs addon temporarily disabled and the tracked config remains unchanged. ## Risks - **Migration:** Adds replay-safe migration `0193`; migration numbering and safety checks pass. The new tables and indexes are additive. - **Authorization:** Effect execution intersects the proposing agent's permissions with the responsible user context and fails closed; mistakes could reject a valid proposal rather than silently over-authorize it. - **Concurrency:** Target snapshots and idempotency keys protect against stale or duplicate execution, but reviewers should focus on mixed-effect partial outcomes and retry behavior. - **UI:** Decisions are integrated into the existing attention feed rather than a separate navigation surface, reducing routing risk but increasing the importance of attention-item metadata compatibility. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex CLI using `gpt-5.6-sol` for final PR preparation, review fixes, and verification; repository tools and code execution were enabled, and context-window size is not exposed in this runtime. - Anthropic Claude Opus 4.8 with 1M context assisted with the Decisions UI implementation, as recorded in the relevant commits. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ox runs (paperclipai#10595) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A Codex agent runs `codex exec`, and the adapter assembles its argument vector from the agent's config plus execution-context options > - For sandbox execution the adapter injects `--skip-git-repo-check`, because a headless remote workspace has no git trust prompt to answer > - The adapter also appends the operator's `extraArgs` verbatim, so an agent that already lists `--skip-git-repo-check` in its config gets the flag twice on a sandbox run > - `codex exec` rejects a repeated `--skip-git-repo-check` and exits with code 2, which the adapter surfaces as `adapter_failed` before any work runs > - This pull request skips the sandbox injection when the operator's args already carry the flag > - The benefit is that a common, harmless-looking config no longer crashes every sandbox run ## Linked Issues or Issue Description **What happened?** A `codex_local` agent configured with `extraArgs: ["--skip-git-repo-check"]` fails on every sandbox run: ``` error: the argument '--skip-git-repo-check' cannot be used multiple times Usage: codex exec [OPTIONS] [PROMPT] ``` The adapter reports `stopReason: "adapter_failed"` (Codex exited with code 2). The flag appears twice in the argv: once injected by the adapter for sandbox execution, once from the operator's `extraArgs`. **Steps to reproduce** 1. Configure a `codex_local` agent with `extraArgs: ["--skip-git-repo-check"]` (or the legacy `args` field). 2. Point it at a sandbox environment. 3. Start a run — `codex exec` aborts immediately on the duplicate flag. **Expected behavior** The run launches with a single `--skip-git-repo-check`. An operator listing the flag the adapter already injects should be a no-op, not a hard failure. **Paperclip version** Current `master`. **Deployment mode** Any deployment running Codex agents in sandbox environments. ## What Changed - `buildCodexExecArgs` no longer pushes the sandbox `--skip-git-repo-check` when the resolved args (`extraArgs`, or the legacy `args` fallback) already contain it. The operator's copy stands; the argv carries the flag exactly once. Non-sandbox runs and configs without the flag are unchanged. ## Verification - `cd packages/adapters/codex-local && pnpm vitest run src/server/codex-args.test.ts` — new cases: `extraArgs` already carrying the flag (single occurrence), the legacy `args` field carrying it (single occurrence), and the operator's flag preserved when the sandbox injection is not requested. Existing "adds --skip-git-repo-check when requested" case unchanged. - `cd packages/adapters/codex-local && pnpm vitest run` — full package suite (218 tests). - `pnpm run typecheck` in the package. ## Risks - Low. The change only suppresses a duplicate of a single, idempotent flag; it never removes an operator-supplied argument and never adds one that was not already going to be present. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use (file edits, vitest/tsc runs). No other models involved. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
…ipai#10593) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The server can preserve eligible agent runs during a controlled hot restart. > - A path change moved restart state from the Paperclip home root to the instance root. > - A staged update can therefore make the old server and the new server read different intent files. > - The old server then drains live runs, while the new server can start without a shutdown snapshot. > - This pull request adds a correlated compatibility handoff and records the live preflight set. > - It also verifies the target process instance on Linux, macOS, and Windows. > - The benefit is complete and safe run classification across the path upgrade. ## Linked Issues or Issue Description No public GitHub issue covers this defect. **What happened?** A staged hot restart can run an older server that reads `hot-restart-intent.json` from the Paperclip home root and a new server that writes the file under the instance root. The old server misses the request and uses graceful drain. The new server later finds its marker without a shutdown snapshot. Before this change, that state could produce an empty loss list even when live runs existed before restart. **Expected behavior** The old server must receive the PID-targeted restart request at its legacy path. The new server must correlate the legacy shutdown snapshot with its instance-scoped request. Every run that was live during preflight must appear as adopted, finalized while down, or lost. A reused PID must not let a stale marker claim a different process instance. **Steps to reproduce** 1. Start a server version from before the instance-root marker change. 2. Keep one or more local-agent heartbeat runs active. 3. Stage a current build and request a hot restart from that build. 4. Observe that the old server reads only the home-root path while the staged build writes only the instance-root path. 5. Observe graceful drain and a new-server intent that has no shutdown snapshot. **Paperclip version or commit** The path transition entered `master` in paperclipai#10045. The hot-restart adoption flow came from paperclipai#9647. This fix targets current `master` and compatibility with the immediately preceding home-root behavior. **Deployment mode** Self-hosted server built from source with controlled service hot restarts. Related work: paperclipai#9628 is the original broader hot-restart feature PR. paperclipai#10556 addresses embedded PostgreSQL lifecycle behavior and does not address marker-path compatibility. ## What Changed - Write an authoritative instance-scoped intent and a correlated legacy home-root handoff marker. - Merge a legacy shutdown snapshot only when immutable request identity fields match. - Prevent a non-default instance from consuming an uncorrelated legacy-only marker. - Record preflight running heartbeat IDs and reconcile snapshot omissions from current database state. - Serialize marker claims, snapshot writes, stale recovery, and matching cleanup with recoverable per-path filesystem leases. - Read process start identity on Linux, macOS, and Windows to distinguish a reused PID from the original server. - Require identity for new restart requests and fail closed when a supported platform cannot provide it. - Classify older markers by comparing the replacement server boot time or operating-system process start time with the request time. - Close the preflight database client explicitly and use a root-safe SQL query. - Add focused unit, platform-branch, database-backed, and CI regression coverage. - Document the compatibility handoff, process identity probes, and instance-scoped report path. ## Verification - `pnpm exec vitest run server/src/services/hot-restart.test.ts server/src/__tests__/heartbeat-process-recovery.test.ts -t "hot-restart|old-server legacy|preflight live|preflight run|spawn identity before hot restart"` — 24 tests passed and 90 tests were skipped across 2 files. - `pnpm exec vitest run server/src/services/hot-restart.test.ts` — 17 tests passed. - `pnpm exec vitest run server/src/__tests__/issue-watchdogs-routes.test.ts -t "restarts a stalled claimed run"` — 1 test passed and 10 tests were skipped. - `pnpm exec vitest run server/src/__tests__/agent-action-audit-routes.test.ts -t "allows an agent with issue:delegate"` — 1 test passed and 7 tests were skipped. - `pnpm --filter @paperclipai/server typecheck` — passed. - `git diff --check` — passed. - GitHub Actions — 26 of 26 checks passed at `55a79cb029be8b1dc89926d9d89ccd2181266d5c`. - Greptile — 5/5 at the same head with no unresolved current-head review threads. ## Risks - The legacy handoff path is shared across instances. Exclusive claims and per-path leases prevent overwrite and match-before-delete races. - Process identity uses platform commands as a fallback when the health endpoint has no identity. Linux reads `/proc`, macOS and BSD use `ps`, and Windows uses PowerShell. - A supported-platform identity probe failure aborts the restart. This fails closed instead of replacing an unknown live process. - Older intent files do not contain process identity. The server compares the replacement boot or process start time with the request time when those values are available. - A preflight database read can fail before the marker is written. The command fails closed instead of claiming a restart whose live-run set is unknown. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex with GPT-5. The exact deployment model ID and context-window size were not exposed by this runtime. Reasoning, repository editing, shell execution, test execution, GitHub CLI, and Paperclip API capabilities were enabled. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
…tup (paperclipai#10594) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The server signs decision specifications with an HMAC > - PR paperclipai#10010 made `PAPERCLIP_DECISION_SIGNING_SECRET` a hard startup requirement > - Existing installs do not have this new environment variable > - Those installs now stop during startup > - This pull request uses a secure persisted instance key when the override is absent > - The benefit is that existing installs start without new configuration and decision signing remains fail-closed ## Linked Issues or Issue Description **What happened?** After paperclipai#10010, `startServer()` throws when `PAPERCLIP_DECISION_SIGNING_SECRET` is unset or shorter than 32 characters. Existing installs without the new environment variable stop at startup. **Expected behavior** The server starts without manual configuration. A new optional feature must not add a required environment variable for existing installs. **Steps to reproduce** 1. Check out `master` at 9c1f8e7. 2. Unset `PAPERCLIP_DECISION_SIGNING_SECRET`. 3. Start the server. 4. Observe that startup stops with a missing-secret error. **Paperclip version or commit** `master` at 9c1f8e7. **Deployment mode** All deployment modes are affected when the environment variable is absent. ## What Changed - Treat `PAPERCLIP_DECISION_SIGNING_SECRET` as an optional override. - Generate a random per-instance key at `<instance>/secrets/decision-signing.key` when the override is absent. - Publish a complete first-time key with an atomic no-overwrite link so concurrent server starts use one key. - Repair permissive modes on process-owned secrets directories and regular key files, reject planted symlinks or foreign-owned paths, and fail startup if `0700`/`0600` cannot be enforced. - Keep an explicitly configured secret shorter than 32 characters as a startup error. - Add startup, permission, planted-symlink, fail-closed verification, and generated-key round-trip tests. ## Verification - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/decisions-service.test.ts src/__tests__/server-startup-feedback-export.test.ts` — 45 tests passed. - `pnpm --filter @paperclipai/server exec tsc --noEmit` — passed. - Eight simultaneous resolver processes returned the same persisted key. The secrets directory/key modes were `0700`/`0600`. - `git diff --check` — passed. ## Risks - Existing configured secrets remain unchanged. - Removing a configured secret after a proposal makes the prior signature fail verification. Restoring the secret restores verification. - A restored secrets directory or key with unsafe permissions now fails startup when the server cannot repair it to `0700`/`0600`; symlinks and paths owned by another local user are rejected rather than trusted. - The generated key uses an atomic hard link in the instance secrets directory. An unsupported file system fails startup instead of replacing an existing key. - Existing installs that failed at startup did not sign decisions with a missing key. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Anthropic Claude Fable 5, model ID `claude-fable-5`, produced the initial implementation with extended reasoning and tool use. - OpenAI Codex, model ID `gpt-5`, addressed review findings and prepared the PR with reasoning, repository editing, code execution, and GitHub tooling. The runtime did not expose the context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
…/reset workspaces still import (paperclipai#10601) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - An agent that runs in a sandbox has its workspace copied back to the host when the run ends, so its work persists — the copy-back ships a git bundle of the sandbox's commits > - The bundle is created as a thin delta, `git bundle create HEAD --not <baseSha>`, which records `baseSha` (the host workspace HEAD captured at export) as a prerequisite the host must already hold > - That assumption breaks when the sandbox HEAD has diverged from `baseSha`, or when a shared host workspace no longer holds `baseSha` at import time — then `git fetch` on the host hard-fails and the entire run is lost even though the agent finished its work > - This pull request bundles against the merge-base of `baseSha` and the sandbox HEAD (with a full-bundle fallback), which the host can satisfy in those cases > - The benefit is that copy-back no longer discards a completed run's work over a base the host can't reconcile ## Linked Issues or Issue Description **What happened?** A sandbox agent run completed its work, then failed during workspace finalize: ``` git -C <host workspace> fetch --force <git-delta.bundle> refs/…/export:refs/…/imported error: Could not read <baseSha> fatal: revision walk setup failed error: git-delta.bundle did not send all necessary objects ``` The run is reported as `adapter_failed` even though the agent produced output. The copy-back bundle names the host workspace's recorded HEAD (`baseSha`) as a prerequisite, but the host cannot satisfy it. **Steps to reproduce** Two independent triggers, both reproduced in tests: 1. The sandbox's HEAD has diverged from `baseSha` — e.g. the sandbox carries a local-only branch that forked from an older commit than the host's current HEAD. 2. The shared host workspace no longer holds `baseSha` at import time (it was reset / re-realized between export and import). In either case `git fetch` of the thin bundle fails with a missing prerequisite. **Expected behavior** Copy-back imports the sandbox's work as long as the host holds any common ancestor, instead of hard-failing and discarding the run. **Paperclip version** Current `master`. **Deployment mode** Any deployment running agents in sandbox environments with workspace sync (notably shared-workspace clones and custom images that carry a local-ahead branch). ## What Changed - `buildRemoteGitDeltaBundleScript` now computes `bundle_base = git merge-base <baseSha> HEAD` and bundles `HEAD --not <bundle_base>`. The merge-base is an ancestor of `baseSha`, so any host that holds `baseSha` (or an ancestor of it — e.g. after a reset) can satisfy the prerequisite, and the bundle stays a delta rather than a full-history transfer. - When `baseSha` is absent from the sandbox, or no merge-base exists, it falls back to a full, self-contained bundle (no prerequisites) so the import can always complete. - The existing empty-bundle no-op (no new commits) and the ordinary fast-forward path are unchanged; the `cat-file` base check no longer aborts the script under `set -e`. ## Verification - `pnpm vitest run packages/adapter-utils/src/git-workspace-sync.test.ts packages/adapter-utils/src/sandbox-managed-runtime.test.ts` — new cases: a diverged sandbox HEAD imports when the host holds only the merge-base (not `baseSha`), and the full-bundle fallback imports into a host that shares no history; existing thin-delta and empty-bundle cases still pass. - `pnpm --filter @paperclipai/adapter-utils exec tsc --noEmit`. - Standalone shell repro confirmed the old thin bundle fails with "Repository lacks these prerequisite commits" in both trigger cases, and the merge-base bundle imports successfully. ## Risks - Low. For the common case (sandbox HEAD descends from `baseSha`) the merge-base is `baseSha`, so the bundle is byte-for-byte the same delta as before. The change only alters behavior when the old code would have hard-failed. - This makes the copy-back import succeed on a diverged base; the subsequent reconciliation of divergent histories (`integrateImportedGitHead`) is unchanged and still owns how the imported head is merged into the host branch. Where a workspace's history has genuinely diverged (e.g. a stale custom image carrying a local-only branch), a clean re-clone/re-capture is still the right operational fix — this change prevents work loss, it does not reconcile intentional divergence. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use (file edits, shell repro, vitest/tsc runs). No other models involved. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
…ai#10606) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Operators spend most of their time on the issue detail page. They talk to the assigned agent there through comments. > - The current page reads as a ticket form. The thread sits below properties, the composer sits mid-page, and live agent activity renders as dense transcript logs. > - Talking to an agent is a conversation. A chat-first layout matches that mental model better than a ticket form. > - A layout change this large must not disrupt current users. It needs a safe opt-in path and full parity with the existing thread features. > - This pull request adds a chat-style task view behind a new "Chat-Style Tasks" experiment toggle. The flag is off by default and the existing page is unchanged when it is off. > - The benefit is a focused, readable conversation with the agent: live tool activity folds into compact summaries, the composer stays at the bottom, and properties, plan, and artifacts move into header tabs. ## Linked Issues or Issue Description Refs #49 (chat with agents is a much-wanted feature). Related PRs found in the dedup search: - paperclipai#4489 — an earlier, closed attempt to promote the conversation to the primary surface on issue detail. This PR is a fresh, flag-gated take on the same goal. - paperclipai#8837 — an open PR that proposes a two-column task layout. It restructures the same page but keeps the ticket paradigm; this PR is orthogonal because it is opt-in and chat-first. **Subsystem affected** UI (issue detail page). **Problem or motivation** The issue detail page presents agent conversations as a ticket: properties first, thread below, composer in the middle of the page, and raw transcript noise during live runs. Users who mainly converse with their agents must scroll past chrome to follow the conversation, and live activity is hard to read. **Proposed solution** An opt-in chat-style view of the issue detail page, gated by a new "Chat-Style Tasks" experiment toggle in Settings → Experimental. With the flag on, the thread fills the center pane, the composer docks to the bottom of the viewport, Properties / Plan / Artifacts become header tabs, live turns show a status pill with the current tool action and elapsed time, and settled turns collapse to a "Worked · N tools" summary that expands into per-tool rows. With the flag off, nothing changes. **Alternatives considered** Restyling the existing layout in place (rejected: too disruptive without an opt-out), and a separate chat page beside the issue page (rejected: splits the task's single source of truth). A per-request lab page (`/task-chat-lab`, dev-only) was kept for design iteration instead. **Roadmap alignment** ROADMAP.md "CEO Chat" wants lighter conversations that still resolve to real work objects. This PR keeps the core task-and-comments model — it only changes presentation, opt-in — so it does not duplicate that planned work. ## What Changed - New `enableTaskChatRedesign` instance setting, exposed as a "Chat-Style Tasks" experiment card in Settings → Experimental (shared feature catalog, validators, server instance-settings service, and UI settings page). - New `ui/src/components/task-chat/` component family: chat thread with turn grouping, agent reply bubbles, live status pill, collapsible turn summaries with per-tool rows, plan tab with a sticky CTA action bar, inline interaction cards, per-request mode chips, and a bottom-docked composer. - A shared tool taxonomy (`tool-taxonomy.ts`) maps tool names to verbs and icons; the status pill, tool rows, and the classic transcript view all use it. - A transcript adapter converts stored run logs into chat turns; it dedupes tool-call updates by `toolUseId` so tool counts match the expanded rows, and it keeps a tool row's first real name when later generic updates arrive. - Composer: posts on Cmd/Ctrl+Enter, supports image paste with object-URL thumbnail previews (revoked on clear/unmount), and uploads through the issue attachments route. - `IssueDetail.tsx`: with the flag on, pane tabs move to the header bar, the header is not sticky, and the chat fills the center; with the flag off, the previous layout renders unchanged. - Motion tokens for the new animations live in `ui/src/index.css` with a `motion-tokens.ts` catalog and a test that keeps the two in sync (the catalog now also covers the shared enter/exit/swap tokens that the decision/quicklook block declares). - A dev-only `/task-chat-lab` page with fixtures and a tweak panel for motion tuning. ## Verification - `pnpm typecheck` — clean across the workspace. - `pnpm check:token-gates` — 3/3 CLEAN. - `cd ui && pnpm vitest run` — 3,344 of 3,345 tests pass locally. The one failure is `IssueProperties.test.tsx` monitor-row time formatting, which is timezone-sensitive: it also fails on unmodified `origin/master` in a non-UTC timezone and passes with `TZ=UTC`. It is not related to this change. - `cd server && pnpm vitest run src/__tests__/instance-settings-service.test.ts` — 21/21 pass (covers the new setting). - Manual: start the dev server, open Settings → Experimental, enable "Chat-Style Tasks", and open any issue. The thread fills the page, the composer docks to the bottom, and Properties / Plan / Artifacts appear as header tabs. Assign an agent and comment to watch a live run: the status pill shows the current tool action with elapsed time, and the finished turn folds into a "Worked · N tools" summary. Disable the toggle and confirm the classic page is unchanged. - Visual snapshot baselines are intentionally not updated: per `doc/design/DECISION-SHEET.md`, "Per-change snapshot verification demoted to dormant (Jul 13 2026)". ## Risks - The flag-off path goes through the same `IssueDetail.tsx` file, so a regression there would affect current users. Mitigation: the classic markup renders through the same components as before behind explicit flag conditionals, and the full UI suite passes. - The transcript adapter interprets stored run-log formats, including legacy entries without `toolUseId`. Malformed logs degrade to generic tool rows rather than crashing. - The new view changes no server behavior other than one additive instance setting; it is additive and default-off. Overall risk with the flag off is low. ## Model Used - Claude (Anthropic), model id `claude-fable-5` (Claude Fable 5), extended thinking enabled, agentic tool use (file editing, shell, test execution) via Claude Code / Claude Agent SDK. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The heartbeat service is the control plane that runs agents through adapters and records each run's usage and cost in the finance/cost ledger > - Adapters report a provider cost (`costUsd`), but there is no way to represent the provider-billed cost *after* prompt-cache discounts, so cache-heavy runs are priced wrong and some paid runs end up exported with a zero/null cost > - A benchmark comparing Paperclip-orchestrated runs against direct harness invocation of the same tasks measured 1.5–3.1× higher apparent USD per pair, largely because cache-discounted billing was not represented in the exported cost data > - This pull request adds an optional `cacheAdjustedCostUsd` field to `AdapterExecutionResult` and a `resolveCacheAdjustedCostUsd` helper in the heartbeat service that prefers the explicit cache-adjusted figure and falls back to the reported `costUsd`, persisting it into the run's usage/ledger JSON > - The benefit is that paid runs are no longer exported as zero/null cost and cache-heavy runs can be priced correctly, so operators comparing orchestrated vs. direct costs see real numbers ## Linked Issues or Issue Description No single existing public issue covers this exactly; closely related cost-reporting issues: - Refs paperclipai#8947 — hermes adapter never reports usage/cost to Paperclip, so budget limits never trigger - Refs paperclipai#6716 — hermes_local cost/usage capture returns zero - Refs paperclipai#3320 — expose per-run token counts in activity log and dashboard **Problem (feature-request form):** Adapters can only report a single `costUsd`. Providers with prompt caching bill less than the nominal token cost, and the heartbeat cost accounting has no field for the cache-adjusted billed amount. As a result, cache-heavy paid runs are either priced at the undiscounted figure or, when the adapter withholds the ambiguous number, exported as zero/null. **Proposed solution:** an explicit optional `cacheAdjustedCostUsd` on the adapter execution result, resolved server-side with a safe fallback to `costUsd`. **Alternatives considered:** recomputing cache discounts server-side from token counts (rejected: provider pricing tables drift and cache billing rules are provider-specific; the adapter is the source of truth). ## What Changed - `packages/adapter-utils/src/types.ts`: added optional `cacheAdjustedCostUsd?: number | null` to `AdapterExecutionResult`, with a doc comment on adapter expectations - `server/src/services/heartbeat.ts`: added exported `resolveCacheAdjustedCostUsd()` (explicit cache-adjusted value wins when a finite non-negative number; otherwise falls back to a finite non-negative `costUsd`; otherwise `null`), and consistently uses the resolved billed value for ledger cents, cost status, and run usage JSON - `server/src/__tests__/heartbeat-cost-accounting.test.ts`: added unit coverage for explicit precedence, fallback, invalid values, adjusted-only pricing, and discounted ledger billing ## Verification - `pnpm exec vitest run server/src/__tests__/heartbeat-cost-accounting.test.ts` — 1 file, 7 tests passed - `pnpm --filter @paperclipai/adapter-utils typecheck` — passed - `pnpm --filter @paperclipai/server typecheck` — passed - GitHub Actions on head `dc9c3830bf694b9afb3b27c5c8c36bff38e7fdcb` — all 26 checks clean/skipped; one unrelated E2E checkout-contention flake passed on its single failed-job rerun - Greptile review on the same head — 5/5 with zero unresolved threads ## Risks - Low risk: the field is optional and additive; when absent, behavior falls back to the existing `costUsd` path - Ledger/usage JSON gains a new optional `cacheAdjustedCostUsd` key — consumers that strictly validate keys would need to tolerate it (usage JSON is already open-shaped) - No migrations, no API-breaking changes > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Implementation: Anthropic Claude Fable 5, model ID `claude-fable-5`, standard context window, agentic coding mode with shell/file tool use - PR preparation and verification: OpenAI Codex on GPT-5 (the runtime did not expose a more specific serving snapshot or context-window value), reasoning mode with shell and GitHub tool use ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes (doc comment on the new field; no user-facing docs affected) - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The pull request workflow protects changes with a Playwright e2e lane. > - That lane already uses a weighted file partition so slow specs do not cluster by test count. > - Recent green PR runs showed the two e2e shard jobs were slower than the next slow required lane. > - The largest spec is indivisible, so a third shard lets that spec run alone and lets the rest split by duration. > - This pull request changes only the PR e2e shard matrix and the guard test. > - The benefit is a shorter expected PR critical path while the required `e2e` aggregate check name stays stable. ## Linked Issues or Issue Description Refs paperclipai#9923 **What existing behavior does this improve?** The `pull_request` workflow Playwright e2e lane. **Subsystem affected** Cross-cutting: GitHub Actions CI and test scripts. **Current behavior** The PR workflow runs the weighted Playwright e2e partition across two jobs. Recent green runs showed those jobs as the slowest required checks. **Proposed behavior** The PR workflow runs the same e2e spec set across three weighted jobs. The aggregate required check stays named `e2e`. **Reason and benefit** The third shard lets the slow smoke-lab spec run alone while the rest of the catalog stays balanced. This should shorten the PR critical path. The win is bounded by fixed per-job setup time. **Breaking changes** None. The required aggregate check contract is preserved. ## What Changed - Change the PR e2e shard matrix from two entries to three entries. - Update the shard guard test to expect three shards. - Floor the balance bound at the largest single spec weight. - Assert that the workflow does not define more shard indexes than `SHARD_COUNT`. ## Verification - `node --test ./scripts/__tests__/e2e-shard.test.mjs` passes with 6 tests. - The recorded-weight partition is complete and non-overlapping: 168.0s, 116.5s, and 114.4s. - I checked `ROADMAP.md` and found no overlapping roadmap-level core feature. - I searched public GitHub PRs and issues for related e2e shard work. I found related PR paperclipai#9923 and no open duplicate for this branch or change. ## Risks - This adds one extra GitHub Actions runner to the PR e2e lane. - The wall-clock win is bounded by fixed per-job setup. - Behavior risk is low because the aggregate required check remains named `e2e`. ## Model Used OpenAI Codex, GPT-5, tool-enabled coding agent in this repository. The runtime did not expose the context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Cody <noreply@paperclip.ing>
…i#10636) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The server posts a workspace-ready comment after it prepares an execution workspace or runtime service. > - The full Markdown card uses too much space in the task thread. > - The existing system-notice presentation can show the same comment as a compact row. > - The server must keep the original body for API clients and expanded details. > - This pull request adds structured presentation data at both workspace-ready call sites. > - The benefit is a quieter thread with no data loss and no migration. ## Linked Issues or Issue Description **What existing behavior does this improve?** The server posts the workspace-ready task comment after workspace provisioning and adapter-managed runtime startup. **Current behavior** The task thread shows a full Markdown comment with strategy, branch, working directory, services, and warnings. Long branch names can make this card dominate the thread. **Proposed behavior** Show the comment as a compact system-notice row. Expand the row in place to show the original Markdown body and structured workspace, service, and warning details. Use a warning tone and open the details by default when warnings exist. **Reason and benefit** The same workspace data is available in the task properties. The compact row keeps the thread easy to scan while it preserves the full comment for API consumers and expanded inspection. **Breaking changes** None. The comment body stays unchanged. Existing comments without presentation data keep their current rendering. ## What Changed - Added workspace-ready presentation and metadata builders. - Added structured workspace, service, reuse, and warning details. - Wired both workspace-ready comment paths to send presentation and metadata options. - Added focused unit and heartbeat-level tests. Collapsed notice:  Expanded notice:  ## Verification - `pnpm exec vitest run server/src/services/workspace-runtime-ready-comment.test.ts server/src/__tests__/heartbeat-workspace-ready-comment.test.ts` — 8 tests passed. - `pnpm --filter @paperclipai/server typecheck` — passed. - `pnpm -r typecheck` — passed. - `pnpm check:token-gates` — passed. - `pnpm build` — passed. - `pnpm test:run` — 300 server files and 405 UI files passed. One unrelated CLI AWS doctor test detected static credentials from the runner environment. The same test passed with `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` removed. - Built the existing system-notice Storybook story and captured both compact and expanded states. - GitHub CI — all latest-head gates passed. One signoff-policy e2e shard hit a transient checkout-state race and passed on its single rerun. ## Risks Low risk. This change only adds optional comment presentation data in two server paths. The body, database schema, API contract, and old comments remain unchanged. Incorrect metadata would affect only expanded structured details; focused tests cover the shape and both warning states. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex with GPT-5 (`gpt-5`, the exact snapshot and context-window size are not exposed by this runtime). The agent used reasoning, repository tools, code execution, and visual inspection. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path > - Paperclip uses pull request CI to test changes before merge. > - The e2e PR lane runs Playwright specs in a shard matrix. > - Each shard builds a list of spec files for its matrix entry. > - The workflow passed that list after a literal `--` separator. > - Playwright did not receive the list as file filters. > - This pull request removes the separator and adds a guard test. > - The benefit is that each e2e shard runs only its assigned specs. ## Linked Issues or Issue Description Refs paperclipai#10629. **What happened?** The e2e shard step used `pnpm run test:e2e -- $specs`. The shard spec list was not applied as Playwright file filters. **Expected behavior** Each e2e shard should pass only its selected specs to Playwright. **Steps to reproduce** 1. Inspect `.github/workflows/pr.yml` at the merge commit for paperclipai#10629. 2. Find the `e2e_shards` command that invokes `pnpm run test:e2e`. 3. See the literal `--` before `$specs`. **Paperclip version or commit** `86767951` **Deployment mode** GitHub Actions PR CI. ## What Changed - Removed the literal `--` from the e2e shard `pnpm run test:e2e $specs` invocation. - Added a regression test that checks the workflow passes `$specs` without that separator. ## Verification - `node --test scripts/__tests__/e2e-shard.test.mjs` ## Risks Low risk. This changes one CI command and one workflow guard test. The main risk is shell argument handling in the workflow, and the guard now covers the expected command shape. ## Model Used OpenAI GPT-5 through Codex. The run used shell and GitHub CLI tool access. The runtime did not expose a context window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
…to the responsible human (paperclipai#10650) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Execution policies let one agent implement and another review, cycling through changes-requested → addressed rounds > - Nothing bounds that cycle: no round counter, no escalation, no termination signal — two agents can ping-pong indefinitely, especially when the review's success criteria drift to something the implementer cannot satisfy > - On a real multi-agent instance this produced 6+ unattended rounds (~8 runs) that continued even after the human had merged the PR under review > - This pull request counts consecutive agent-initiated changes-requested rounds and, at a configurable cap, hands the still-pending review to the responsible human instead of bouncing back to the implementer > - The benefit is that unattended review loops terminate in a human decision instead of burning runs forever ## Linked Issues or Issue Description Fixes paperclipai#10643 ## What Changed - `IssueExecutionState.changesRequestedCount` (schema + type, default 0): consecutive agent-initiated changes-requested rounds on the current stage. Carries through executor resubmissions, resets to 0 on approval, and resets when a **human** makes the changes-requested decision — the cap targets unattended agent↔agent ping-pong, never human review. - `IssueExecutionPolicy.maxReviewRounds` (optional, 1–50, default null → server default `DEFAULT_MAX_REVIEW_ROUNDS = 3`). - At the cap, the transition records the reviewer's changes-requested decision as usual but keeps the stage **pending** with the responsible human (`responsibleUserId`, falling back to `createdByUserId`) as the participant: the issue is assigned to that human and the pending review surfaces through the existing attention/review UI. The human then approves, requests changes (resetting the counter and handing back to the implementer), or re-scopes. - The escalated hold is sticky: transitions from anyone other than the escalated human no longer re-select a configured agent participant for the stage (which would have silently undone the escalation on the next unrelated PATCH). The escalated human's own decisions flow through the normal participant decision branch. - Issues with no responsible human keep today's hand-back behavior; the counter still accumulates so operators can see the churn. ## Verification - `pnpm vitest run server/src/__tests__/issue-execution-policy.test.ts` — 8 new cases: round counting on hand-back, count carried through resubmission, escalation at the default cap, sticky hold across unrelated transitions, human changes-requested resets the counter, human approval completes the stage, no-responsible-human fallback, and a `maxReviewRounds: 1` policy override. - `pnpm vitest run server/src/__tests__/issue-execution-policy-routes.test.ts` and the full `@paperclipai/shared` suite (387 tests) — schema additions are backward compatible (both fields optional with defaults; persisted states without the counter parse as 0). - `pnpm --filter @paperclipai/shared exec tsc --noEmit` and `cd server && pnpm run typecheck`. ## Risks - Behavior change: an agent-only review loop that previously ran forever now escalates to a human after 3 agent rounds by default. Instances that want longer loops can set `maxReviewRounds` per policy. Flows where a human participates are unaffected (human decisions reset the counter). - Escalation requires a `responsibleUserId`/`createdByUserId` on the issue; without one, behavior is unchanged. - Persisted execution states from before this change parse with `changesRequestedCount: 0` — no migration needed. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. No other models involved. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
…paperclipai#10648) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents can create and assign issues to other agents, and commonly escalate to their org-chart manager (`reports_to`) when they hit something outside their authority > - Issue assignment already refuses terminated and pending-approval assignees, but accepts paused assignees from any actor > - A paused agent never runs, so agent-initiated escalations to a paused manager become invisible dead letters — accepted silently, never picked up, never surfaced > - This pull request refuses paused assignees when the assigning actor is an agent, at the single normalization helper all four assignment paths flow through > - The benefit is that agent-routed work can no longer silently vanish into a paused agent's queue ## Linked Issues or Issue Description Fixes paperclipai#10641 ## What Changed - `normalizeIssueAssigneeAgentReference` (used by issue create, both child-create routes, and issue update) now throws a 409 when an **agent** actor assigns to a **paused** agent, with a message naming the alternatives: assign an invokable agent, leave the issue unassigned, or escalate to a board operator. - Board/user actors are unchanged and may still assign to paused agents deliberately — the pause state is visible in the UI, and staging work for a later unpause is a legitimate workflow. Terminated / pending-approval / invalid-org-chain refusals are unchanged for all actors. - This matches the existing precedent for watchdogs ("Cannot assign watchdog to an agent that is not invokable") using the same conflict-error shape. ## Verification - `pnpm vitest run server/src/__tests__/issue-assignee-invokability-routes.test.ts` — new coverage: agent PATCH → paused assignee 409 (no update call), agent child-create → paused assignee 409 (no create call), agent assignment to an invokable agent still 200, board assignment to a paused agent still 200. - Neighboring suites unchanged: `issue-update-comment-wakeup-routes`, `issue-agent-mutation-ownership-routes`, `issue-create-deduplication-routes`, `issue-watchdogs-routes` (97 tests). - `cd server && pnpm run typecheck`. ## Risks - Low. The only behavior change is a new 409 for agent actors assigning to paused agents — previously a silent dead-letter. Agents that relied on this (escalation flows) now get an actionable error instead; human workflows are untouched. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. No other models involved. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
paperclipai#10655) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The server enforces an issue execution policy. It gates status changes while a review or approval stage is active. > - A board user could not cancel a task while an agent reviewer held the active stage. The API returned "Only the active reviewer or approver can advance the current execution stage". > - Board users own the board. They must always be able to edit and cancel any task. > - This pull request adds a board override to the execution stage transition. A board cancel clears the pending stage state and proceeds instead of raising an error. > - The benefit is that board users can always stop work, even while a review is pending or the stored stage state has drifted. ## Linked Issues or Issue Description No public GitHub issue exists for this bug. Description follows `bug_report.yml`: **What happened?** A board user set a task to `cancelled` while the task had an active reviewer stage held by an agent. The PATCH failed with "Task Update Failed... only the assigned approver or reviewer...". The same failure occurred when the stored stage state had drifted: the server silently forced the task back to `in_review` instead of honoring the cancel. **Expected behavior** A board user can always edit and cancel any task. A board cancel must clear the pending review stage and apply the requested status. **Steps to reproduce** 1. Create a task assigned to agent A with agent B configured as reviewer in the execution policy. 2. Let agent A hand the task off so the review stage becomes active. 3. As the board user, set the task status to Cancelled. 4. The update fails with the reviewer-only error. Related work: paperclipai#5487 touches the execution-policy approver UI. It does not address the board cancel path. ## What Changed - `server/src/routes/issues.ts`: the issue PATCH route now passes `allowBoardOverride` when the actor is a board user. - `server/src/services/issue-execution-policy.ts`: when `allowBoardOverride` is set and the requested status is not `in_review` or `in_progress`, the transition clears `executionState` and proceeds. This applies both while a stage decision is pending and when the stage state has drifted, so a board cancel is no longer rejected or silently flipped back to `in_review`. - Reviewer gating is unchanged for everyone else: a board user who is the active participant still uses the normal approve / request-changes flow, and non-participant agents still receive the 422 guard. - Assignee-only board updates on an `in_review` task keep the stage state coherent: reassigning to an eligible stage participant re-pends the stage with them as the current participant, while reassigning to a non-participant (or unassigning) dissolves the review back to `in_progress` instead of persisting an `in_review` issue with no execution state or an ineligible participant. - New unit tests and route tests cover board cancellation of an active review stage and of a drifted pending review, plus reviewer swap, non-participant reassignment, and unassignment during an active review. ## Verification - In `server/`: `pnpm exec vitest run src/__tests__/issue-execution-policy.test.ts src/__tests__/issue-execution-policy-routes.test.ts` — 2 files, 73/73 tests pass on top of current `master`. - In `server/`: `pnpm run typecheck` passes. ## Risks - Low risk. The override branch runs only for board actors and only for target statuses other than `in_review` and `in_progress`. Cancelling clears `executionState`, so a later reopen starts from a fresh stage state. Agent-facing flows and reviewer gating are unchanged. ## Model Used - Claude Fable 5 (Anthropic), model ID `claude-fable-5`, running in Claude Code (Claude Agent SDK) with extended thinking and agentic tool use. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [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
…he latest activity (paperclipai#10656) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Operators can cancel a running agent from the board when a run is unwanted — most acutely while cleaning up a runaway loop > - The recovery machinery treats a cancelled run like any other unsuccessful terminal run: the stranded-issue sweep classifies the issue as stranded, creates a recovery action, and wakes the agent again > - So cancelling runs to stop a loop *fed* the loop: each operator cancel spawned a recovery action that re-woke the agent the operator had just stopped > - This pull request stamps board-initiated cancellations with operator attribution and makes the sweep stand down while such a run is the issue's latest activity > - The benefit is that an operator's cancel is final until something new happens, instead of being fought by automation ## Linked Issues or Issue Description Fixes paperclipai#10646 ## What Changed - `POST /heartbeat-runs/:runId/cancel` (board-only) now cancels with an explicit reason ("Cancelled by a board operator") and stamps `resultJson.cancelledByActorType: "user"` / `cancelledByUserId`. - `reconcileStrandedAssignedIssues` gains an early stand-down: when the issue's latest run is operator-cancelled (the new stamp, or the existing `operator_interrupted` error code from interrupt-by-comment), the issue is skipped entirely — no recovery action, no wake — and counted in a new `operatorCancelExempted` result field. The exemption is inherently self-limiting: any newer run or wake supersedes it because the gate only looks at the *latest* run. - System cancellations without operator attribution (lease expiry, assignee changes, terminal-status cancels, pause holds) keep today's recovery behavior unchanged. ## Verification - `pnpm vitest run server/src/__tests__/issue-recovery-actions.test.ts` (embedded Postgres) — 3 new cases: a stamped operator cancel produces zero recovery actions and zero wakes; an `operator_interrupted` cancel likewise; an unattributed system cancel still flows into pre-existing recovery (wake observed), proving the stand-down is scoped to operator attribution. - `pnpm vitest run server/src/__tests__/heartbeat-process-recovery.test.ts server/src/__tests__/issue-scheduled-retry-routes.test.ts` — unchanged (109 tests). - `cd server && pnpm run typecheck`. ## Risks - Low. The only suppressed behavior is recovery of runs a human explicitly cancelled from the board; everything else is byte-identical. If an operator cancels and walks away, the issue stays quiet until any new activity — which is the intent (the operator owns the next step). ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. No other models involved. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
… manager (paperclipai#10657) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents escalate work up the org chart (`reports_to`), and operators pause agents — notably, instance imports pause every agent by default > - A paused manager does not invalidate the chain (subordinates stay invokable), so nothing surfaces when an operator unpauses workers but leaves their manager paused > - Escalations then dead-letter silently: agent-created issues assigned to the paused manager sit in a queue nothing will ever run > - This pull request computes paused ancestors in the existing org-chain health model and surfaces a non-blocking warning on the agent read models and detail page > - The benefit is that the operator learns their escalation paths are dead before work vanishes into them ## Linked Issues or Issue Description Fixes paperclipai#10647 (companion to paperclipai#10648, which refuses agent-initiated assignment to paused agents at write time — this PR makes the standing hazard visible) ## What Changed - `AgentOrgChainHealth` gains two additive, optional fields: `pausedAncestors` (paused agents in the `reports_to` chain) and `escalationWarning` (human-readable, only set when the agent itself can work — a paused/terminated agent's escalation path is moot). Chain validity, invokability, and assignability are byte-identical. - No server route changes needed: the fields flow through every existing agent read model (list, detail, org chart) since they ride the same `getAgentWorkEligibility` computation. - Agent detail page shows an amber "Escalation path is paused" banner (same visual language as the invalid-chain banner, but non-blocking) with the warning text naming the paused manager and the two remedies. ## Verification - `pnpm vitest run packages/shared/src/agent-eligibility.test.ts` — 5 new cases: paused direct manager warns; paused grandparent through a healthy manager warns; the agent itself paused → no warning (but ancestors still reported); fully active chain → no warning, empty list; terminated ancestor keeps the invalid-chain classification without double-counting as paused. - Full `@paperclipai/shared` suite (392 tests) and `agent-eligibility-routes` (54) unchanged. - `tsc --noEmit` in shared, server, and ui. ## Risks - Low. Purely additive fields plus one UI banner; no behavior gates on the new data. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. No other models involved. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
…s creator (paperclipai#10658) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents decompose work by creating child issues assigned to other agents > - When two agents each lack a capability the other assumed (e.g. neither can push to GitHub), each can "resolve" its blocker by delegating the same step to the other: A creates a child for B, B creates a grandchild back for A > - Nothing detects the cycle; the chain of blocked issues grows and no signal reaches the human who could actually fix the capability gap > - This pull request refuses agent-initiated child creation when the child's assignee is the creator of a still-open ancestor in the same chain — a mechanical, semantics-free cycle signal > - The benefit is that the hot-potato dies at creation time with an actionable error instead of growing a dead chain ## Linked Issues or Issue Description Fixes paperclipai#10642 (write-time counterpart: paperclipai#10648 refuses assignment to paused agents; the credential-gap *preflight* side is tracked separately in paperclipai#10644) ## What Changed - `issueService.findOpenAncestorCreatedByAgent(parentIssueId, agentId, {maxDepth})`: bounded walk up the parent chain looking for a still-open (not done/cancelled) ancestor created by the given agent. - Agent-initiated issue creation with a parent (both the create-with-`parentId` route and `POST /issues/:id/children`) now refuses with a structured 409 (`code: delegation_cycle`, naming the ancestor) when the new child would be assigned to the agent that created a still-open ancestor: that agent delegated the work into this chain, so assigning it back is a cycle. The message states the alternatives — complete the work, leave the child unassigned, or escalate to a board operator. - Deliberately unaffected: human actors (deliberate re-routing is their call), closed ancestors (re-engaging the creator of finished work is normal), and accepted-plan decomposition (its children come from a human-approved plan). ## Verification - `pnpm vitest run server/src/__tests__/issue-assignee-invokability-routes.test.ts` — cycle refused with 409 and no create call; the same child allowed when no open ancestor matches; board actors never consult the guard. - `pnpm vitest run server/src/__tests__/issues-service.test.ts` — new embedded-Postgres coverage: ancestor found through the chain, closed ancestors ignored, depth bound honored (114 total). - `pnpm vitest run server/src/__tests__/issue-create-deduplication-routes.test.ts server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts` — unchanged (79). - `cd server && pnpm run typecheck`. ## Risks - Low-to-moderate: a new 409 for a creation shape that previously succeeded. The blocked shape (agent assigns new work to the creator of an open ancestor) is the cycle signature; the legitimate "hand a subtask to the parent's assignee" pattern is unaffected because it keys on assignee, not creator. Watchdog and plan-decomposition flows are exempt or unaffected as described. - The walk adds at most `maxDepth` (10) single-row lookups per agent child creation with an assignee. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. No other models involved. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
…erclipai#10294) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Agents run through adapters; the `opencode-local` adapter shells out to the OpenCode CLI and, before each run, does a pre-flight `opencode models` **availability probe** to fail fast on a misconfigured `provider/model`. > - That probe was written to **throw on any probe failure** — a timeout, a non-zero exit, or a transient `Unexpected error` from the CLI — which aborts the whole heartbeat run. > - In practice the CLI probe fails transiently (provider hiccup, cold cache, momentary CLI error). When that happens *after* the agent has already done its work, the run dies before its terminal disposition is written, so the platform reopens the issue and re-runs it — a spurious crash/re-run loop that affects every agent on the OpenCode adapter. > - This PR makes the probe **non-fatal when it cannot run**: it warns and proceeds with the configured model, letting the real invocation be authoritative. > - It deliberately **keeps** the genuine guard: when the probe *succeeds* and the configured model is absent from a non-empty list, it still throws (this is what catches misconfigured slugs). > - The benefit is that a best-effort pre-flight check can no longer take down an otherwise-healthy run, while the useful misconfiguration guard is retained. ## Linked Issues or Issue Description No public GitHub issue exists; describing inline (bug). **What happened:** an OpenCode-adapter agent run terminated at the adapter level with `` `opencode models` failed: Unexpected error ``. The failure landed after the agent had produced its work, so the terminal-status update never applied and the run was reopened and re-executed. **Expected:** a transient failure of the `opencode models` availability *probe* should not abort the run — the probe is a best-effort pre-flight guard, not a gate. **Actual:** the probe threw on timeout / non-zero exit / empty output, aborting the run and discarding the completed work + disposition. **Scope:** both the local (`models.ts`) and remote/SSH (`execute.ts`) probe paths; affects any agent on the `opencode_local` adapter. Related PRs (context / prior art): - Refs paperclipai#5119 — added the remote execution-target model-probe validation this PR softens. - Refs paperclipai#3291 — closed prior attempt to make the `opencode_local` model probe non-blocking (at agent-create time; different entry point). - Refs paperclipai#8014 — related open work raising the probe timeout (20s → 60s); complementary, not overlapping. ## What Changed - `models.ts` (`ensureOpenCodeModelConfiguredAndAvailable`): if discovery throws (probe can't run) or returns an empty list, **warn and proceed** with the configured model instead of throwing. The "model present in a non-empty list" check is unchanged and still throws when the configured model is genuinely absent. - `execute.ts` (`ensureRemoteOpenCodeModelConfiguredAndAvailable`): remote probe **timeout / non-zero exit / empty output** now warn and return (proceed) instead of throwing. The remote model-absent guard still throws. - `models.test.ts`: the local "discovery cannot run" case now asserts the probe **proceeds** with the configured model (was: asserts it rejects). - `execute.test.ts`: added remote regression tests — non-zero exit, timeout, and empty output all proceed; a successful probe missing the configured model still rejects. ## Verification ```bash pnpm --filter @paperclipai/adapter-opencode-local typecheck # clean # opencode-local server suite (default 5s per-test timeout is too tight for the # heavy SSH tests on some machines; use a realistic timeout): node node_modules/.pnpm/vitest@*/node_modules/vitest/vitest.mjs run \ packages/adapters/opencode-local/src/server/models.test.ts \ packages/adapters/opencode-local/src/server/execute.test.ts \ packages/adapters/opencode-local/src/server/execute.remote.test.ts \ --testTimeout=45000 ``` Result: typecheck clean; all opencode-local server tests pass, including the new remote fail-open tests and the retained "model unavailable on the remote target" guard test. ## Risks - **Fail-open behavior (intentional).** When the probe can't run, a genuinely misconfigured model is no longer caught at pre-flight — it surfaces at the real invocation instead. This is the accepted tradeoff: the probe is best-effort, and the real invocation is authoritative. The high-value guard (probe succeeds + model absent from a non-empty list) is retained, so the common misconfiguration — a bad `provider/model` slug — is still caught. - No API, schema, or migration changes. Behavior change is confined to the two probe helpers. Low risk overall. ## Model Used Anthropic **Claude Opus 4.8** (`claude-opus-4-8`), used via Claude Code with agentic tool use (repo search, file editing, shell/code execution) and extended reasoning. Used to diagnose the crash, implement the fix, and write the tests; the change was reviewed before submission. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links - [x] My branch name describes the change (`fix/opencode-model-probe-non-fatal`) and contains no internal ticket id - [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 (internal adapter behavior; no user-facing docs affected) - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green (functional gates: tests/build/e2e/typecheck/security). Review/Greptile gate re-running after this update. - [ ] Greptile is 5/5 with no open P2s — re-triggered after addressing both P2s (remote test coverage + this template-complete description) - [x] I will address all Greptile and reviewer comments before requesting merge
…ed runtime URL in run env (paperclipai#10339) ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - Every agent run gets a run-scoped bridge into the Paperclip API through the injected `PAPERCLIP_API_URL` / `PAPERCLIP_API_KEY` env vars, built by `buildPaperclipEnv` in `packages/adapter-utils/src/server-utils.ts` > - `buildPaperclipEnv` resolves that URL as `PAPERCLIP_RUNTIME_API_URL ?? PAPERCLIP_API_URL ?? http://<listen-host>:<port>`, and the server always exports `PAPERCLIP_RUNTIME_API_URL` derived from `authPublicBaseUrl` at boot > - When `authPublicBaseUrl` points at an address that is not reachable from inside the runtime container (e.g. a VPN/tailnet-only address used to keep the web UI off the public internet), every local run receives a dead API URL (`curl` exit 7) and agents only survive by hand-rolling a localhost fallback > - An operator-set `PAPERCLIP_API_URL` is the documented escape hatch — `docs/deploy/environment-variables.md` states the server "preserves the value" when set externally and that the run-level var "inherits the server-level value" — but the run env builder inverts the precedence, so the override never actually reaches runs > - This pull request swaps the precedence in `buildPaperclipEnv` so an explicit `PAPERCLIP_API_URL` wins over the derived runtime URL, aligning the behavior with the documented contract > - The benefit is that operators with split-horizon topologies (public auth URL != container-reachable URL) can point agent runs at a reachable endpoint with one env var, with zero behavior change for deployments that do not set it ## Underlying Issue No pre-existing public issue covers this, so per CONTRIBUTING ("Link Issues or Describe Them In-PR") here are the `bug_report.yml` fields inline: - **What happened:** with `PAPERCLIP_AUTH_PUBLIC_BASE_URL` on a tailnet-only address and `PAPERCLIP_API_URL=http://localhost:3100` explicitly set in the server environment, every agent run still received `PAPERCLIP_API_URL=http://100.x.y.z:3100` (the derived, container-unreachable URL); `curl` from inside the run exits 7 and agents can only reach the API by hand-rolling a localhost fallback - **Expected behavior:** the run env inherits the operator-configured `PAPERCLIP_API_URL`, as documented in `docs/deploy/environment-variables.md` ("preserves the value", run-level var "inherits the server-level value") - **Steps to reproduce:** (1) set `PAPERCLIP_AUTH_PUBLIC_BASE_URL` to an address not reachable from inside the server container, (2) set `PAPERCLIP_API_URL=http://localhost:3100` in the server env, (3) trigger any agent run and inspect the spawned process env: it carries the derived URL, not the override - **Version/commit:** reproduced on the `91e58acb` image (2026-07-19); the precedence is unchanged on current `master` (`a3b293e`) - **Deployment mode:** single-host Docker Compose, local adapters (`claude_local`/`codex_local`), web UI exposed via VPN/tailnet only ## Related PRs (dedup search) Several in-flight PRs touch the same pain point (runs receiving an unreachable injected API URL) — linked for reviewer context; none of them honors the documented explicit override, and the older ones appear stale: - paperclipai#9916 — reworks `PAPERCLIP_RUNTIME_API_URL` derivation and port preservation (server side); complementary, does not change run-env precedence - paperclipai#8130 — honors a pre-set `PAPERCLIP_RUNTIME_API_URL` (server side); a complementary escape hatch via the runtime var instead of the documented `PAPERCLIP_API_URL` override - paperclipai#8025 — heuristic: prefer loopback when the runtime bind is loopback (no activity since Jun 12) - paperclipai#5692 — heuristic loopback-safe URL inside `buildPaperclipEnv` (no activity since May 14) - paperclipai#4877 — broader same-host injection rework across 10 files (no activity since May 2) - paperclipai#4794 — always forces loopback for spawned agents (no activity since Apr 30; would break split-horizon setups where a reachable non-loopback URL is intended) This PR intentionally takes the Path-1 route from CONTRIBUTING: the smallest possible change (swap two lines so the documented operator override wins) plus regression tests, rather than a new heuristic. ## What Changed - `packages/adapter-utils/src/server-utils.ts`: `buildPaperclipEnv` now resolves the injected URL as `PAPERCLIP_API_URL ?? PAPERCLIP_RUNTIME_API_URL ?? http://<listen-host>:<port>` (explicit override first), with a short comment explaining why - `packages/adapter-utils/src/server-utils.test.ts`: three new tests covering the override precedence, the derived-URL fallback, and the listen-host default (including the `0.0.0.0` to `localhost` mapping) - `server/src/__tests__/paperclip-env.test.ts`: updated the expectation that encoded the old runtime-URL-first precedence and added the symmetric fallback case (runtime URL used when no explicit override is set) - No docs changes needed: `docs/deploy/environment-variables.md` already describes the fixed behavior ## Verification - `vitest run` on the new `buildPaperclipEnv` tests in `packages/adapter-utils`: 3/3 pass - `vitest run` on `server/src/__tests__/paperclip-env.test.ts` after the expectation update: 5/5 pass (the first CI run correctly flagged the one test that encoded the old precedence) - Reproduced and verified on a production deployment (single-host Docker, `PAPERCLIP_AUTH_PUBLIC_BASE_URL` on a tailnet-only address): - Before: freshly spawned runs received `PAPERCLIP_API_URL=http://100.x.y.z:3100` (verified in the spawned process `/proc/<pid>/environ`); `curl` to it from inside the container exits 7 - After (with `PAPERCLIP_API_URL=http://localhost:3100` in the compose environment): a fresh run received `http://localhost:3100`, and `curl $PAPERCLIP_API_URL/api/agents/me` with the run-scoped key returned HTTP 200; the run finished `succeeded` with usage telemetry recorded ## Risks - Low. Behavior changes only for deployments that explicitly set `PAPERCLIP_API_URL`; when unset (the default), `PAPERCLIP_RUNTIME_API_URL` is used exactly as before - The sandbox callback bridge (`execution-target.ts`) is intentionally untouched: remote sandboxes genuinely need the publicly reachable URL, and its `input.hostApiUrl || PAPERCLIP_RUNTIME_API_URL || ...` chain still provides it ## Model Used - Claude Fable 5 (Anthropic, model ID `claude-fable-5`), extended thinking + agentic tool use via Claude Code, operating over SSH against the affected deployment ## Checklist - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Sergio-LPA <204395363+Sergio-LPA@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ult (paperclipai#10176) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Local adapters (claude_local, codex_local) run agent heartbeats as child processes, with a short-lived run JWT injected as `PAPERCLIP_API_KEY` at spawn time > - That JWT is minted exactly once, when the adapter spawns the process — its TTL must therefore cover the entire wall-clock life of the run, not just a prompt startup > - On laptops the gap between spawn and first real execution can be huge: a timer heartbeat scheduled while the lid is closed fires during a ~2s macOS dark wake, the machine re-sleeps immediately, and the frozen child only executes during a later, longer wake — over an hour of wall-clock delay in observed runs > - The server's default TTL was 1h, so those sessions started with an already-expired `PAPERCLIP_API_KEY` and every control-plane call 401'd; the agent had to recover by manually minting a fresh key > - The 1h default was also a spec drift: the CLI `env` command (`DEFAULT_AGENT_JWT_TTL_SECONDS`) and the agent-authentication design doc both document 172800s (48h) > - This pull request realigns the server default to 48h and documents the host-suspension constraint at the mint site and in the regression test > - The benefit is that lid-closed/suspended-host heartbeat runs come up with a valid credential, and the three places that state the default now agree ## Linked Issues or Issue Description No public GitHub issue exists for this; per the bug-report template: - **What happened:** A timer-driven heartbeat run on a MacBook (lid closed, on battery) was invoked during a ~2s dark wake. The adapter spawned the CLI and logged init within 2s, then the host re-slept and the session sat frozen for ~64 minutes until a longer dark wake let it execute. By then the injected run JWT (1h TTL, minted at spawn) had expired, so every API call from the agent returned 401 and the run could only recover via a manually minted key. A second agent's run the same night showed the identical signature (output timestamps exactly matching `pmset -g log` dark-wake windows). - **Expected behavior:** A run that starts late because the host was suspended should still have a valid `PAPERCLIP_API_KEY` when it finally executes. - **Steps to reproduce:** Run Paperclip on a laptop with a `claude_local` agent on a timer heartbeat; close the lid on battery overnight; observe a run invoked during a dark wake whose session executes >1h later with an expired token (compare run-log timestamps to `pmset -g log` sleep/wake entries). - **Version/commit:** current `master` (14f20be); local trusted deployment mode. Related context: paperclipai#5864 introduced per-company signing keys in this same module (no TTL changes). ## What Changed - `server/src/agent-auth-jwt.ts`: default `ttlSeconds` for local agent run JWTs raised from `60 * 60` (1h) to `60 * 60 * 48` (48h), matching `DEFAULT_AGENT_JWT_TTL_SECONDS` in `cli/src/commands/env.ts` and `doc/plans/2026-02-18-agent-authentication-implementation.md`; comment documents why the TTL must cover host-suspension gaps - `server/src/agent-auth-jwt.ts`: stale "~1h by default" reference in the legacy-fallback guidance updated to 48h - `server/src/__tests__/agent-auth-jwt.test.ts`: default-TTL regression test updated to assert 48h and explain the constraint - `PAPERCLIP_AGENT_JWT_TTL_SECONDS` remains the explicit override knob; operators who set it see no behavior change ## Verification - `cd server && pnpm vitest run src/__tests__/agent-auth-jwt.test.ts src/__tests__/agent-auth-middleware.test.ts` — 24/24 pass locally - Review that the three default sources now agree: `server/src/agent-auth-jwt.ts` (`60 * 60 * 48`), `cli/src/commands/env.ts` (`DEFAULT_AGENT_JWT_TTL_SECONDS = "172800"`), design doc (`default: 172800`) - Manual: on a laptop, set no TTL env, trigger a heartbeat, `echo $PAPERCLIP_API_KEY` inside the run and decode the JWT — `exp - iat` is 172800 ## Risks - Longer-lived bearer tokens widen the leak window if a run token is exfiltrated. Mitigations already in place: tokens are per-company/per-instance signed (paperclipai#5864), bound to a `run_id`, and never persisted server-side. Operators wanting shorter tokens keep the `PAPERCLIP_AGENT_JWT_TTL_SECONDS` override. - The legacy master-secret fallback window guidance ("disable ~one TTL after deploy") lengthens accordingly; the comment now states 48h explicitly. - Follow-up ideas intentionally out of scope: rejecting run JWTs whose run has terminated (server-side revocation check), and holding a power assertion (`caffeinate`-style) for the duration of local adapter runs so dark-wake-spawned runs keep the host awake. ## Model Used - Claude (Anthropic) — Fable 5, model ID `claude-fable-5`, via Claude Code 2.1.x under Paperclip's `claude_local` adapter; extended thinking and full tool use (shell, file edits, test execution) enabled ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
Fixes paperclipai#7623 ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Company invites are part of the access subsystem and must produce URLs that recipients can open from outside the host machine. > - Paperclip already has public/auth base URL configuration for deployments behind a public hostname, Tailscale, or a reverse proxy. > - Invite URL composition was still deriving its origin from the incoming request host, so loopback-bound servers emitted `http://127.0.0.1:3100/invite/...`. > - A loopback invite URL is not shareable with a remote human or agent, even when the token itself is valid. > - This pull request makes invite URL builders prefer the configured public base URL and keep the existing request-host fallback when it is unset. > - The benefit is that copied invite links use the reachable deployment origin without changing local-only behavior. ## Linked Issues or Issue Description Fixes paperclipai#7623 No duplicate or related PRs/issues were found in a GitHub search for invite URL, loopback, public base URL, and `authPublicBaseUrl` terms. ## What Changed - Added base URL resolution in `server/src/routes/access.ts` that strips trailing slashes and prefers configured `authPublicBaseUrl` over the request-derived host. - Threaded `authPublicBaseUrl` through invite summary, invite onboarding manifest, onboarding text, access routes, `createApp`, and server startup wiring. - Added `server/src/__tests__/invite-url-public-base-url.test.ts` covering configured public-base precedence, unset fallback behavior, and trailing-slash normalization. - Registered the invite public-base URL test in the serialized Vitest server runner. ## Verification ```bash pnpm install --frozen-lockfile pnpm exec vitest run server/src/__tests__/invite-url-public-base-url.test.ts pnpm run test:run:serialized ``` Local results from the rebased PR branch: - `pnpm install --frozen-lockfile` exited 0. - Targeted invite URL test exited 0: 1 file, 3 tests passed. - Serialized server suite exited 0: 106 serialized suites completed; the new invite URL test passed inside that runner. Manual check after deployment: set `PAPERCLIP_AUTH_PUBLIC_BASE_URL` or equivalent public base URL config, create a company invite, and confirm the returned/copied invite URL uses that public origin instead of `127.0.0.1`. ## Risks Low risk. The new public base URL parameter is optional and falls back to existing request-derived behavior when unset. The main operational risk is misconfigured public base URL input; the implementation only trims trailing slashes and otherwise trusts the configured origin. ## Model Used - Original implementation: Anthropic `claude-sonnet-4-6`, 200k context, tool use and test execution. - Conflict repair and verification: OpenAI Codex GPT-5.5, coding agent with shell, git, GitHub CLI, and local test execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Coder (Claude) <coder-claude@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Paperclip Coder (Claude) <lad-agent@paperclip.ing>
…aperclipai#11307) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Dependabot keeps the npm dependency tree and the GitHub Actions workflows current with weekly update PRs > - The npm config ignores every major version bump with a wildcard `ignore` rule, and no other process reports pending majors > - Major-version debt grows silently, and ignore rules also suppress Dependabot security updates when the fix ships only in a newer major > - Individual major PRs are not a good replacement: the board decided in paperclipai#7560 to keep the PR list mergeable, and a flood of breaking bumps works against that > - This pull request removes the blanket ignore and groups all pending majors into one weekly PR, while minors and patches keep one PR per bump > - The benefit is a standing, visible signal of pending major updates, at a cost of at most one extra PR per week ## Linked Issues or Issue Description **What existing behavior does this improve?** The Dependabot npm update flow configured in `.github/dependabot.yml`. **Current behavior** Dependabot opens weekly PRs for minor and patch npm updates. A wildcard `ignore` rule suppresses every major version update. No report or reminder replaces the suppressed PRs — the comment says "review those manually", but nothing triggers that review. Ignore rules also apply to Dependabot security updates, so a security fix that ships only in a newer major is suppressed as well. **Proposed behavior** Dependabot opens one grouped weekly PR that contains every pending major npm update. Minor and patch updates keep their current one-PR-per-bump flow. A deliberate hold on a specific major can use a targeted per-dependency `ignore` entry instead of the wildcard. **Reason and benefit** Silent major-version drift compounds: each skipped major makes the eventual upgrade jump larger and riskier, especially across peer-dependency families. A single grouped PR makes the backlog visible in the PR list without flooding it. When the grouped PR is green, it merges cheaply. When it is red, it is a visible standing task instead of invisible debt. **Breaking changes** None. This changes repository automation only. Runtime behavior, response shapes, and outputs are unchanged. **Additional context** Related history: paperclipai#7483 grouped patch/minor updates by dependency type, and paperclipai#7560 reverted that grouping because the resulting 26-package PR was hard to merge. This PR does not touch the patch/minor flow. It only groups majors, which currently produce no PRs at all — it adds a signal that does not exist today rather than replacing individually mergeable PRs. ## What Changed - Removed the wildcard `ignore` rule for `version-update:semver-major` from the npm ecosystem in `.github/dependabot.yml`. - Added a `major-updates` group (`applies-to: version-updates`, `update-types: ["major"]`, `patterns: ["*"]`) so all pending majors land in one weekly grouped PR. - Left the schedule, labels, PR limits, and the github-actions ecosystem unchanged. ## Verification - `npx js-yaml .github/dependabot.yml` parses cleanly and the `groups` stanza follows the Dependabot v2 schema (`applies-to`, `update-types`, `patterns`). - After merge: check Insights → Dependency graph → Dependabot for config errors. The next weekly run (Monday 06:00) opens a single `major-updates` grouped PR that lists the pending majors. - No code changed, so the test suite is unaffected. ## Risks - Low risk. This is CI/automation configuration only. - The first grouped PR may be large, and red if several majors break the build. That is the intended visibility mechanism, and it does not block other work. A noisy or deliberately held-back dependency can be excluded from the group with `exclude-patterns` or a targeted per-dependency `ignore` entry. - This does not regroup minors or patches, so it does not reintroduce what paperclipai#7560 reverted. ## Model Used - Claude Fable 5 (Anthropic), model ID `claude-fable-5`, via Claude Code CLI, extended thinking and tool use enabled. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [ ] I have run tests locally and they pass — N/A, YAML-only CI config change; validated with `js-yaml` - [ ] I have added or updated tests where applicable — N/A, no code changed - [x] I have updated relevant documentation to reflect my changes — none reference the Dependabot config - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green — one e2e shard flaked on an unrelated MCP UI spec and passed on re-run with identical code - [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
…ve (paperclipai#11302) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Archiving a company hides it from the sidebar switcher, but remembered last-visited paths, browser history, bookmarks, and restored tabs keep depositing users onto its URLs long after archiving > - Since the selection ping-pong fix (paperclipai#11300) those arrivals render, but the user is stranded inside a workspace the sidebar refuses to show — and unarchiving had no UI anywhere, so the only way back was a hand-typed settings URL > - This pull request bounces cold arrivals at archived company URLs to an active company (with a toast naming why), lets deliberate visits stick, and adds an Unarchive action to the companies list > - The benefit is that stale URLs stop stranding users in retired workspaces, and archived companies become restorable from the one page that still lists them ## Linked Issues or Issue Description Follow-up to paperclipai#11300. No existing issue for the remaining gap; description follows the enhancement template: **What happened?** After paperclipai#11300, opening an archived company's URL (stale tab, history, bookmark, remembered path) renders that company's pages — but the sidebar switcher does not list it, so the user is stranded in a workspace they retired, and every stale URL pulls them back in. Separately, unarchiving a company has no UI: the archive button lives in company settings, which becomes unreachable through normal navigation once the company is archived. **Expected behavior** Arriving cold at an archived company's URL lands the user in an active workspace, with a toast explaining the redirect. Explicitly choosing the archived company (from the companies list) still works, so its pages remain reachable. Archived companies can be restored from the companies list. **Steps to reproduce** 1. Create two companies; archive one. 2. Open `/{archivedPrefix}/dashboard` directly — before: renders the archived workspace with no sidebar presence; after: bounces to the active company's dashboard with a toast. 3. On the companies list, open the archived company's row menu — before: no restore action anywhere; after: Unarchive. ## What Changed - `ui/src/lib/company-selection.ts`: `resolveArchivedCompanyBounce` — pure policy: bounce when the URL names an archived company that is not the current selection and an active company exists; prefer the currently selected active company as the destination. - `ui/src/components/Layout.tsx`: the route-sync effect applies the bounce (toast + selection + `replace` navigation) before syncing selection from the route. - `ui/src/pages/Companies.tsx`: Unarchive action (`PATCH status: "active"`) in the row menu for archived companies. - Tests: unit cases for the bounce policy; the e2e now drives all three behaviors (direct-load bounce with toast, re-arrival bounce, deliberate visit sticks) on top of the existing crash regression. ## Verification - `pnpm vitest run src/lib/company-selection.test.ts src/context/CompanyContext.test.tsx src/pages/Companies.test.tsx` in `ui/` — 20 tests pass. - `npx playwright test --config tests/e2e/playwright.config.ts archived-company-url` — passes, covering bounce, toast, and deliberate-visit paths. - `pnpm typecheck` in `ui/` — clean. ## Risks Low risk. The bounce only fires for archived-company URLs when the archived company is not already selected and an active company exists; all-archived instances render as before. Deliberate selection from the companies list is unaffected (selection equals the matched company, so no bounce). Unarchive reuses the existing `PATCH /api/companies/:id` status transition the server already supports. ## Model Used - Claude (Anthropic), Claude Fable 5 (`claude-fable-5`), via Claude Code CLI with extended thinking and tool use (code search, edit, test execution, Playwright e2e). ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
paperclipai#11313) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The `@paperclipai/db` package owns the database schema and its migrations > - Some migration tests start an embedded Postgres server and replay a migration against it > - An embedded Postgres server needs 7 to 12 seconds to start on a CI runner > - Vitest stops a test after 5 seconds unless the test sets its own timeout > - Two of these tests do not set a timeout, so they fail on CI before they assert anything > - This pull request gives both tests a 30 second timeout > - The benefit is that unrelated pull requests stop failing on a test they did not change ## Linked Issues or Issue Description No public issue exists for this. The problem follows. **What happened?** The test `packages/db/src/company-secret-proposals-migration.test.ts` fails on CI. The error is `Test timed out in 5000ms`. The test never reaches its assertions. The suite reports `1 failed | 104 passed`. The failure is not caused by the branch under test. It appeared on three different branches in a few hours: | Run | Head | Failing jobs | | --- | --- | --- | | 31630781317 | `95622fa3` | `General tests (workspaces-b)`, `verify`, `e2e shard (2/3)`, `e2e` | | 31652020976 | `feba90c9` | `General tests (workspaces-b)`, `verify`, `e2e shard (3/3)`, `e2e` | | 31651467721 | `f2115207` | `General tests (workspaces-a (1/2))`, `verify` | The `verify` job reads the result of the general tests. One timeout therefore turns into two red checks. A reviewer sees two failures and reads them as a regression. **Expected behavior** The test starts an embedded Postgres server, replays the migration, and asserts the schema. It must pass on a normal CI runner. **Steps to reproduce** 1. Open any pull request against `master`. 2. Wait for the job `General tests (workspaces-b)`. 3. Read the failure. The test times out after 5000 ms. The failure needs a slow runner. A fast development machine starts embedded Postgres in less than 5 seconds, so the test passes there. **Paperclip version or commit** `master` at `a09d7dcc0`. **Deployment mode** CI only. GitHub Actions, `ubuntu24` runner image. ## What Changed - `packages/db/src/company-secret-proposals-migration.test.ts` — the test now uses a 30 second timeout. The migration suites in this package already use 20 to 60 seconds. 30 seconds is the most common value. - `packages/db/src/status-card-migrations.test.ts` — the same change. This test has the same defect. It does not fail yet because it replays fewer statements. A fix to only one test moves the problem instead of removing it. - Both tests get a comment. The comment tells the next author why the 5 second default is too short. These two tests were the only embedded-Postgres migration tests in the package without a timeout. ## Verification - Run `pnpm vitest run src/company-secret-proposals-migration.test.ts src/status-card-migrations.test.ts` in `packages/db`. Both tests pass. - These suites skip themselves when the Postgres binaries are absent. A pass alone therefore proves nothing. Run the command with `--reporter=verbose`. The output contains Postgres `NOTICE` messages, for example `relation "status_cards" already exists, skipping`. These messages prove the tests ran real SQL. - Run the same command with `--testTimeout=1`. Both tests still pass. This proves the per-test timeout overrides the global timeout. Before this change, the same command fails immediately. - All CI jobs on this pull request pass. The job `General tests (workspaces-b)` passes. This job failed on the three runs listed above. Not done: no attempt to reproduce the timeout on a development machine. A fast machine starts embedded Postgres in less than 5 seconds, so the failure does not occur there. ## Risks Low risk. The change adds two timeout arguments to tests. It changes no source code, no schema, and no dependency. A longer timeout cannot hide a regression here. The tests assert the same conditions as before. A migration that truly hangs now fails after 30 seconds. Before, it failed after 5 seconds with a message that pointed at the wrong cause. The `e2e` failures on the runs above have a different cause. The spec `mcp-user-stories.spec.ts › US-9` fails with `502 — fetch failed` and `fetch failed: bad port`. These errors come from MCP tool-connection health checks. The failures hit different shards on different runs. This pull request does not change that behavior. `e2e shard (2/3)` passes here, which supports the view that those failures are unstable infrastructure. To revert, remove the two timeout arguments. ## Model Used Claude Opus 5 (`claude-opus-5`), through Claude Code. Extended thinking enabled. Tool use enabled: file read and edit, shell command execution for the local test runs, and the GitHub CLI to read the failing CI logs. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Paperclip <noreply@paperclip.ing>
…aperclipai#11098) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Paperclip Cloud provisions a dedicated tenant stack for each customer. During signup it asks for a mission, a name and role for the first agent, and a first task. > - Cloud pushes those answers into the new stack at activation, as `POST /api/companies/:companyId/onboarding-seed`. > - No route served that path. The tenant answered 404, so Cloud recorded the push as unacknowledged and retried on every portfolio fetch. > - The failure was soft. The answers stayed durable in Cloud and the stack still activated. But the stack opened on the empty first-run wizard, and it asked the customer again for what they had already given. > - This pull request adds the receiving endpoint. It validates the seed, applies it, and acknowledges it. > - The benefit is that a seeded stack opens with the mission, the agent and the first task already in place. ## Linked Issues or Issue Description No public GitHub issue covers this. The problem is described in-PR, following the feature template. **Subsystem affected** server/ — Express REST API and orchestration services. Also `packages/db` (one new table) and `packages/shared` (one new validator). **Problem or motivation** Paperclip Cloud collects onboarding answers at signup and pushes them to the tenant stack at activation. The tenant had no route for that request. It answered 404. Cloud treats a non-2xx as "not yet applied", so it kept the answers and retried, but the stack itself stayed unseeded. A customer who had already named their mission, their first agent and their first task arrived at an empty first-run wizard that asked for all three again. **Proposed solution** Serve `POST /api/companies/:companyId/onboarding-seed`. Validate the body, apply it to the company, then acknowledge it. The seed is customer free text, so it is bounded and validated in `packages/shared` and read from the JSON body only. It is never read from an `x-paperclip-cloud-*` header. That header set is the trusted identity envelope: every member is derived server-side from the host plus verified domain records, and that is exactly what makes it trustworthy. Mixing user content into it would remove the property. A test plants a mission on a cloud header and asserts that the body value wins. Application reuses the shapes the first-run wizard already produces, so a seeded stack and a manually onboarded one look the same afterwards: - The mission becomes the company-level goal. A multi-line mission splits into a title and a description, as the wizard does. - The agent becomes the company's first hire. Its free-text role ("Chief of Staff") lands on `title`. The structural `role` stays `ceo`, which is what the org chart and the default-instructions lookup read. - The first task becomes an issue in the Onboarding project, assigned to that agent. Cloud retries until it gets a 2xx, and it reads any 2xx as "the tenant holds this content". So the endpoint is idempotent per `revision`. A new `company_onboarding_seeds` table records the applied revision together with the goal, the agent and the issue it produced. A replay of a revision that already matches is a successful no-op. A later revision — the customer edited their answers — updates those three rows in place instead of creating a second agent and a second task. The record is written last, after every other write has landed, so a partial application cannot present itself as acknowledged. Everything is applied before the 200 is sent. This is an ordering guarantee, not eventual consistency. The tests read the database immediately after the response, with no waiting and no polling, so a lazy receiver fails them on a fast machine as well as a slow one. That matters because the redirect into the tenant dashboard is gated on this acknowledgement. **Alternatives considered** Store the seed and let the tenant UI apply it on first load. Rejected: the dashboard redirect is gated on the acknowledgement, so a background apply would let the dashboard open before the agent and the task exist. The whole point is that it must not. Reuse `POST /companies/:companyId/agents` and `POST /companies/:companyId/issues` over HTTP from Cloud. Rejected: it needs three round trips with no shared idempotency key, and it moves the "did all of it land?" decision to the caller. **Roadmap alignment** This completes an existing Cloud-to-tenant contract. It does not add a new user-facing surface. ## What Changed - Add `POST /api/companies/:companyId/onboarding-seed` in `server/src/routes/onboarding-seed.ts`. It authenticates exactly as `POST /api/companies/:companyId/logo` does, through `assertCompanyAccess`. - Add `server/src/services/onboarding-seed.ts`. It applies the mission, the agent and the first task, and records the applied revision last. - Add the `company_onboarding_seeds` table: schema, migration `0216`, and journal entry. It holds the applied revision and the ids of the goal, agent and issue the seed produced. - Add `applyOnboardingSeedSchema` in `packages/shared`. It bounds mission to 2000, agent name to 80, agent role to 120, task title to 200, and task details to 2000 — the same limits Cloud enforces before it sends. - Mount the router in `server/src/app.ts` and register the path in the OpenAPI document. - Add `server/src/__tests__/onboarding-seed-route.test.ts` with 13 tests. - The seeded agent is created on `claude_local`. This mirrors the teams-catalog default for agents created server-side, where no human runs an environment test first. `PAPERCLIP_ONBOARDING_SEED_ADAPTER_TYPE` overrides it. ## Verification ```sh pnpm typecheck # whole workspace, passes npx vitest run \ server/src/__tests__/onboarding-seed-route.test.ts \ server/src/__tests__/openapi-routes.test.ts # 15 passed ``` The suite runs against embedded Postgres with migrations applied, so migration `0216` is exercised by every test. The route tests cover: - the happy path — mission, agent and task all applied, read immediately after the 200 - replay of the same revision — no second agent, no second task, no second goal, no second project - a later revision — the goal, agent and task are updated in place - a multi-line mission splitting into a goal title and description - a revision-only seed - the activity log entry written once, and not again on a replay - a caller without access to the company — 403, and nothing written - a body with no revision — 400 - each field bound past its limit — 400 - a mission planted on an `x-paperclip-cloud-*` header — ignored, body wins - an existing Onboarding project — reused, not duplicated Not verified here: the full Cloud-to-tenant walk against a live stack. That needs a deployed Cloud and a provisioned tenant together, which is separate staging work. ## Risks Migration `0216` creates one new table. It adds no column to an existing table, rewrites nothing, and backfills nothing, so it is safe to apply online. The migration safety check passes. The endpoint writes to a company. Access is enforced by `assertCompanyAccess`, the same gate the company logo write uses, and a test covers the denial. Behavioral note for stacks that already hold data. If a company already has a non-built-in `ceo` agent, a first seed updates that agent's name and title rather than creating a second lead. Likewise a seed adopts an existing company-level goal rather than adding a parallel one. This is deliberate: the seed is the customer's own stated answer from signup, and two competing missions or two leads would be worse than one updated in place. In the intended case — a stack that Cloud has just activated — none of these exist yet. The seeded agent is created on `claude_local` with an empty adapter config. It is idle and needs the usual credential setup before it runs. Seeding it does not start it. ## Update — rebased onto master + review hardening Master moved on after this PR was cut, so it was **rebased onto `master`** and the seed migration was **renumbered from `0212` to `0216`** (the merged paperclipai#11101 took `0212_onboarding_first_task_unique`); the drizzle journal was re-stitched and `check:migrations` passes. Two things landed on top of the original receiver: - **Mission-only walk contract (PAP-67 r17.4).** The tenant now owns the first agent and the first task via paperclipai#11101's server-owned onboarding path, which stamps `ONBOARDING_FIRST_TASK_ORIGIN_KIND` and races safely on the partial unique index `issues_onboarding_first_task_uq`. A comment in the apply path documents why this receiver leaves the first task to that path on the cloud walk, and a paperclip-cloud `node:test` (`src/onboarding/walk-seed.test.ts`) asserts the walk's seed carries no `agent`/`firstTask`. The receiver retains the agent/first-task code for its documented body contract, kept inert on the cloud path by the mission-only seed. - **Three Greptile P1 fixes** (`95622fa37`): concurrent application is now serialized under a per-company `pg_advisory_xact_lock` (no duplicate goal/agent/project/task on overlapping pushes); a revised first task carries its resolved `assigneeAgentId`/`goalId`; and the `company.onboarding_seed_applied` audit write is best-effort so a logging failure can't leave the entry permanently absent. Two new regression tests cover the first two. ## Model Used Claude Opus 5 (`claude-opus-5`), 1M context window, extended thinking, with tool use and code execution. Used for the original codebase investigation, the implementation, and the tests. The rebase, migration renumber, mission-only contract, and the three P1 fixes were done with Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, with tool use and code execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings in upstream d5b9f6c..f0e6c0f — 272 commits including cloud upstream table removal, decisions v1 schema, onboarding seeds, company archival bounces, heartbeat context indexes, interaction resolver governance, dependabot grouping, sandbox exec retry on worker restart, HMAC webhook replay rejection, built-in agent sidebar/summary feature gates, agent run JWT TTL alignment, and dependency bumps. Migrations 0196–0198 (upstream 0196–0198) renumbered to 0199–0201, shared range 0199–0216 shifted to 0202–0219 (offset +3 for fork-specific 0125_activation_events, 0127_agent_managed_instructions_snapshot, 0183_instance_settings_visibility). Journal validated: 219 entries, no duplicate idx, every tag has a .sql and every .sql has a tag. Cloud-upstreams files (server/src/routes/cloud-upstreams.ts, server/src/services/cloud-upstreams.ts, cli/src/commands/client/cloud.ts, cli/src/__tests__/cloud.test.ts, ui/src/pages/CloudUpstream.tsx, ui/src/pages/CloudUpstream.test.tsx) accepted upstream deletion — upstream dropped the cloud-upstreams concept entirely. No pnpm-lock.yaml in the diff, per fork policy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
- Remove enableCloudSync from PublicFeatureFlags (upstream removed from
InstanceExperimentalSettings)
- Add enableManagedSandboxOnly and enableClassicTaskInterface to
PublicFeatureFlags (needed by upstream UI components)
- Fix codex-local execute.ts: close restore arrow function body, remove
duplicate firstMeaningfulStderrLine (now in adapter-utils)
- Fix auth.ts: repair broken ternary expression in membership upsert
- Fix cloud-tenant-actor.test.ts: pass required {} arg to createFakeDb
- Fix live-events-ws.ts: remove resolveCloudTenantWsAuth import
(superseded by resolveCloudActor callback)
- Delete orphaned cloud-upstreams-routes-authz.test.ts
- Adapt test mocks from mockInstanceSettingsApi to fork's
mockAccessApi.getCurrentBoardAccess pattern
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
- Add missing fs/os/path imports to health.test.ts - Update OnboardingWizard.test.tsx button text from "Give it a heartbeat" to "Get started" (upstream changed the label) - Fix hardcoded migration filenames in db test files to match +3 offset: 0205_narrow_shiva → 0208, 0207_moaning_amazoness → 0210, decision queue migrations 0198-0200 → 0201-0203 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
…zard tests - Add back inspectDatabaseBackupHealth logic to health route handler (lost during merge conflict resolution) - Fix OnboardingWizard test button texts: step 4 uses "Connect" (not "Get started"), step 3 heading is "Create your first agent" - All 28 OnboardingWizard tests pass Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
- Add missing createHealthyDb() function to health.test.ts (lost in merge) - Fix heartbeat context snapshot migration test: 0209/0210 → 0212/0213 to match the +3 fork offset Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
- Add createHealthyDb() helper to health.test.ts - Add instanceSettingsService mock to invite-url-public-base-url.test.ts - Add instanceSettingsApi mock to IssueDetail.test.tsx for useClassicTaskInterfaceEnabled hook - Fix heartbeat context snapshot migration test path (0209→0212) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
- BuiltInAgentGate.tsx: replace instanceSettingsApi with useFeatures() - SidebarAgents.tsx: replace instanceSettingsApi with useFeatures() - useClassicTaskInterfaceEnabled.ts: rewrite to use useFeatures() instead of admin-only instanceSettingsApi - invite-url-public-base-url.test.ts: add instanceSettingsService mock - IssueDetail.test.tsx: add instanceSettingsApi mock for classic task interface test These files were introduced by upstream and use admin-only instance settings reads. The fork's features-migration-guard.test.ts enforces that non-admin surfaces use useFeatures() (board capabilities) instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
…ures - Remove stale experimentalQuery reference in BuiltInAgentGate.tsx - SidebarAgents.test.tsx: replace instanceSettingsApi mock with accessApi/buildCurrentBoardAccess pattern - ProjectProperties.concurrency.test.tsx: seed currentBoardAccess query instead of experimentalSettings for useFeatures() compat Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
Replace instanceSettingsApi/getExperimental mock with accessApi/ getCurrentBoardAccess using buildCurrentBoardAccess helper, matching the component's migration to useFeatures(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
Restore the fork's custom session-init error handling in the ACPX engine catch block: compose rich error messages with child stderr, redact secrets, and throw AcpxSessionInitError for lane-fallback- eligible runs instead of returning a generic "Internal error". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
Replace instanceSettingsApi mock with accessApi/buildCurrentBoardAccess so the useClassicTaskInterfaceEnabled hook (now backed by useFeatures) receives the enableClassicTaskInterface flag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
The properties panel now opens for all non-onboarding issues in chat-style mode (upstream change). Update the test expectation to match: openPanel IS called instead of being withheld. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
Missed the second describe block (classic off) when migrating to mockAccessApi. Same pattern: buildCurrentBoardAccess with features. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
Combine the fork's drain/polling loop (injectable sleep, hasInflightRuns, drainTimeoutMs, pollIntervalMs) with upstream's runIds filtering. The runIds parameter moves inside the options object so it's compatible with both the fork's test patterns and upstream's selective-drain callers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
- hello-probe.ts: add usageLimited classification using isClaudeProviderQuotaError, emitting claude_hello_probe_usage_limited warning instead of falling through to failure - test.ts: update resolveClaudeAuthAdvice to return claude_oauth_token_configured code with correct detail text - test.auth.test.ts: update expected values to match Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
- heartbeat-process-recovery.test.ts: update error message assertion to
match current wording ("sandbox plugin workers are unavailable")
- codex-local acp.ts: restore callerControlsHost guard for hosted
tenants in testCodexAcpEnvironment so tenants get tenant-appropriate
credential hints instead of host-operator hints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
The isSandboxProviderWorkerUnavailableFailureMessage regex only
matched plugin-installed providers ("worker is not running"). Add a
second pattern for built-in providers ("workers are unavailable") so
the heartbeat retries runs that hit a transient sandbox worker restart
regardless of provider type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
drainRunningRunsForShutdown callers pass either a raw string[] (hot restart path) or an options object (test harness with injectable sleep/clock). Accept both via a union parameter with runtime dispatch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
The merge lost the fork's live-output forwarding (onOutput, runId) and the streamed flag in the sandbox execution result. Restore them so the plugin-backed sandbox driver can bridge worker output chunks and the caller knows whether buffered log dump should be suppressed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
When result.streamed is true the driver already delivered output via onOutput; skip the onLog suffix dump to avoid double-logging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WJhKAZmVbvVMZuAYceick
This was referenced Aug 16, 2026
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.
Thinking Path
Linked Issues or Issue Description
Supersedes the upstream range after #320. Continues the periodic rebase cadence.
What Changed
d5b9f6c8..f0e6c0f5— 272 commits. Notable: cloud upstream table removal, decisions v1 schema, onboarding seeds, company archival bounces, heartbeat context indexes, interaction resolver governance, dependabot grouping, sandbox exec retry on worker restart, HMAC webhook replay rejection, built-in agent sidebar/summary feature gates, agent run JWT TTL alignment, and dependency bumps.0199–0201(upstream0196–0198renumbered +3 for the fork offset). Shared range0199–0216shifted to0202–0219. Journal validated: 219 entries, no duplicate idx, every tag has a.sqland every.sqlhas a tag.server/src/routes/cloud-upstreams.ts,server/src/services/cloud-upstreams.ts,cli/src/commands/client/cloud.ts,cli/src/__tests__/cloud.test.ts,ui/src/pages/CloudUpstream.tsx,ui/src/pages/CloudUpstream.test.tsx).pnpm-lock.yamlin the diff, per fork policy.Conflict resolution highlights
52 files had merge conflicts, all resolved:
server/src/middleware/auth.ts): took upstream's company auto-creation and membership union approach. Removed fork'sresolveCloudTenantWsAuth(superseded by upstream'scloudActorHeaderSourceFromHeaders).isNonRetryableAdapterSetupFailure,isPermanentAuthFailureRun,CONSECUTIVE_IDENTICAL_FAILURE_PAUSE_THRESHOLD. Added upstream'sterminalizeRunOnLeaseReleaseandresolveCacheAdjustedCostUsd.decideCodexAuthMergefunction.useFeatures()hook pattern,useBoardCapabilitiesaccess control, andmockAccessApitest patterns. Removed CloudUpstream nav items. Added upstream's Export/Import sidebar links, sign-out hooks, and cloud-stacks patterns.streamed,prebakedRuntimefields alongside upstream'sfinishedAt/durationMs/streamAgentSessionOutputadditions.onOutput/runId/streamOutputand upstream'sbypassSession/useSessionfields.Verification
idx, no orphan tags, no orphan.sqlfiles.pnpm-lock.yamlin the diff.CI is the real gate. Please wait for all gates to pass before merging.
Risks
Moderate, inherent to a 272-commit rebase:
CloudUpstreampage route now redirects to/company/export.0199–0219must not collide with anything landing between now and merge.Model Used
Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template#NNN/github.com/paperclipai/paperclipURLs)docs/...,fix/...) and contains no internal Paperclip ticket id or instance-derived detailsGenerated by Claude Code