Commit Graph
267 Commits
Author SHA1 Message Date
scotttongandClaude Fable 5 eac5d3ee10 feat(server): emit recovery/stranded escalation comments as structured system notices
Migrate the run-recovery/watchdog escalation comments onto the structured
system-notice mechanism the successful-run-handoff notices already use.

- New recovery/stranded-notice.ts: per-family notice seeds (short body +
  title + tone) for the no-live-execution-path / workspace-validation /
  configuration-incomplete / execution-review-participant families, and
  buildStrandedRecoveryEscalationNotice which assembles presentation
  {kind: system_notice} plus metadata.sections (Recovery action key_value,
  Recovery owner agent_link or board-escalation key_value, Next action,
  Source run run_link, Failure code key_value).
- escalateStrandedAssignedIssue posts the structured notice instead of
  appending the plain Recovery action/owner/Next action footer; plain
  comment callers keep working via fallbackBody + cause-derived titles.
- Escalation dedupe still checks the legacy body marker for old comments
  and relies on noticeMetadataReferencesRecoveryAction for new ones.
- heartbeat.ts builders replaced by the shared seeds; inline failure
  fragments dropped from bodies (failure code now lives in metadata; raw
  error text stays withheld from the thread).
- Shared row helpers extracted to recovery/notice-format.ts.
- Tests: unit coverage for every family seed + escalation notice shape +
  metadata dedupe matcher; embedded-Postgres dedupe regressions updated to
  assert the structured shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 16:58:40 -07:00
Michael NguyenandPaperclip 42c73562c5 fix(heartbeat): backfill projectWorkspaceId when restoring a reused execution workspace (#10171)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Each run executes inside a persisted **execution workspace** (a row
in `execution_workspaces`) that is either freshly created or
**restored/reused** across runs of the same issue
> - Before adapter launch, a guard rejects a restored workspace whose
`projectWorkspaceId` is null while the issue resolves a concrete project
workspace (`persisted_workspace_missing_project_workspace_id`) — a
safety check against binding a run to a workspace with no
project-workspace link
> - The reuse/**restore** path updated the existing row (cwd, branch,
status, metadata…) but **never set `projectWorkspaceId`**, so a row
persisted with a null value stayed null on every restore
> - Result: for an issue that resolves a project workspace, the guard
fires, `reuse_existing` re-selects and re-binds the *same* stale null
row on the next attempt, and the run crash-loops forever with no
self-heal
> - This pull request backfills `projectWorkspaceId` during restore
(prefer the existing binding, fall back to the resolved one) so the row
heals on first reuse and the guard stops firing
> - The benefit is that reused workspaces created before their project
had a primary project workspace self-repair on next use instead of
crash-looping, while genuine mismatches are still surfaced by the guard

## Linked Issues or Issue Description

No public GitHub issue exists — describing the bug inline per the bug
report template (`.github/ISSUE_TEMPLATE/bug_report.yml`):

### What happened?
In `heartbeatService`, the execution-workspace reuse/restore branch
calls
`executionWorkspacesSvc.update(reusableExistingExecutionWorkspace.id, {
… })` without a `projectWorkspaceId` field. Only the sibling CREATE
branch sets `projectWorkspaceId`. So an execution workspace that was
persisted with a null `projectWorkspaceId` (e.g. created before its
project had a primary project workspace) is never backfilled on restore.
When such a workspace is later reused for a run whose issue resolves a
concrete project workspace, the pre-launch guard throws
`persisted_workspace_missing_project_workspace_id`, the run fails, and
`reuse_existing` re-binds the identical stale row on the next attempt —
an unbounded crash-loop with no self-heal.

### Expected behavior
On restore, the reused workspace's `projectWorkspaceId` is backfilled
from the resolved project workspace when it is currently null, so the
guard passes and the run launches. An existing non-null binding is never
overwritten (a genuine mismatch is still surfaced by the separate
`project_workspace_mismatch` guard).

### Steps to reproduce
1. Have an `execution_workspaces` row with `project_workspace_id = NULL`
that is eligible for reuse.
2. Give its project a primary project workspace (so the issue now
resolves a concrete `projectWorkspaceId`).
3. Dispatch a run for an issue in that project that reuses the
workspace. The restore `update()` leaves `project_workspace_id` null,
the launch guard throws
`persisted_workspace_missing_project_workspace_id`, and every subsequent
reuse re-binds the same null row and fails identically.

### Paperclip version or commit
`master` (branched from `14f20be92`); reproduced on a live self-hosted
instance.

### Deployment mode
Self-hosted, embedded Postgres, local adapters.

## What Changed

- New exported pure helper
`reconcileReusedExecutionWorkspaceProjectWorkspaceId(existing,
resolved)` in `server/src/services/heartbeat.ts`, returning `existing ??
resolved ?? null`. It prefers an existing binding (never nulls out a
good value or silently rebinds a genuine mismatch — the guard still
surfaces those), backfills a null binding from the resolved value, and
stays null when neither is present.
- Wire the helper into the reuse/restore
`executionWorkspacesSvc.update(...)` call so the restored row's
`projectWorkspaceId` is set to
`reconcileReusedExecutionWorkspaceProjectWorkspaceId(reusableExistingExecutionWorkspace.projectWorkspaceId,
resolvedProjectWorkspaceId)`. The CREATE branch already set
`projectWorkspaceId`; this brings the restore branch to parity.

## Verification

- Added 3-case unit coverage in
`server/src/__tests__/heartbeat-workspace-session.test.ts` for the
helper: (a) backfills a null existing binding from the resolved value,
(b) never overwrites an existing binding even when a resolved value is
present, (c) returns null when both existing and resolved are absent
(null and undefined inputs).
- Confirmed the `update()` patch type accepts the field:
`executionWorkspacesSvc.update` takes `Partial<typeof
executionWorkspaces.$inferInsert>`, and `projectWorkspaceId` is a column
on that table; both
`reusableExistingExecutionWorkspace.projectWorkspaceId` and
`resolvedProjectWorkspaceId` are `string | null`, matching the helper's
`string | null | undefined` params / `string | null` return.
- Live-instance exposure check (embedded Postgres): 354
`execution_workspaces` rows carry a null `project_workspace_id`; all of
them belong to projects with **no** project workspace, so
`expectedProjectWorkspaceId` currently resolves null and the guard does
not fire today. The fix is durable heal-on-reuse protection for the
moment any such project gains a primary project workspace (or a null row
is reused for an issue that resolves one).
- CI (full pnpm workspace install) runs the authoritative test +
typecheck for this change on this PR.

## Risks

- Low risk; scoped to the execution-workspace restore path, no schema or
API change.
- The helper only ever *adds* a `projectWorkspaceId` where the row had
none; it never overwrites an existing binding, so it cannot mask a real
`project_workspace_mismatch` (that guard still runs after).
- Complementary to (not overlapping with) #10130, which escalates a
terminal `workspace_validation_failed` run to `blocked` from the
recovery side; this PR prevents the guard from firing on reuse in the
first place. Neither depends on the other.

## Model Used

Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, with tool use /
code execution (repo edit, embedded-Postgres exposure query, unit-logic
verification).

## Checklist

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 09:06:36 -07:00
Nicky LeachandPaperclip 01b51dc0e5 fix(runtime): stop Live badge and Working shimmer after task teardown (#10985)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task list and the chat views show a Live badge and a Working
shimmer for an issue that has an active run.
> - A finished task kept the Live badge and the Working shimmer after
the run ended and the sandbox stopped.
> - The user interface reads run liveness from the
`heartbeat_runs.status` row. The run finalizer writes the terminal
status in a step that is separate from the agent `status=done` update.
When the sandbox or the run process stops between the two steps,
`heartbeat_runs.status` stays `running` forever.
> - A run row that stays `running` makes a finished task look
perpetually Live, and the user interface has no guard for an issue that
already reached a terminal status.
> - This pull request closes the invariant "environment lease released
implies the run is terminal" on the server, and adds a user interface
guard that suppresses live state for a terminal issue.
> - The benefit is that a finished task stops showing Live and Working,
both at the source (the run row) and at the surface (the badge and the
shimmer).

## Linked Issues or Issue Description

**Bug description**

- A completed task kept the Live badge and the Working shimmer after its
run ended and the sandbox was torn down.

**Steps to reproduce**

- Run an agent task to completion. Let the sandbox tear down while the
run finalizer is between the `status=done` update and the terminal
run-status write.
- Open the task list or the chat view for the finished task.

**Expected behavior**

- A finished task shows no Live badge and no Working shimmer.

**Actual behavior (before this change)**

- The finished task showed the Live badge and the Working shimmer
because its `heartbeat_runs.status` row stayed `running`.

This pull request supersedes the two separate pull requests #10954
(frontend) and #10955 (backend). It carries all of their changes for the
same race.

## What Changed

Server:

- Run teardown terminalizes a still-running or still-queued run before
it releases the environment lease. It writes `succeeded` when the issue
already reached `done`, `cancelled` when the issue is `cancelled`, and
`interrupted` otherwise. It never overwrites a status that another path
already made terminal.
- The recovery stale-lock sweep terminalizes an orphaned running run to
`interrupted` after it confirms the process and the sandbox are both
gone. It requires recorded process metadata, so it never terminalizes a
live run, a queued run, or a scheduled retry.
- Each terminal transition writes a run event.
- The stale-lock sweep continues and clears the lock when the audit
write fails. It logs the failure loudly.
- New server tests cover both invariants.

User interface:

- A shared guard suppresses the Live badge and the Working shimmer when
the issue status is terminal.
- The guard keeps non-terminal `queued` and `running` issues live.
- The guard prefers the newest issue live-status snapshot.
- New user interface tests cover the guard and the snapshot preference.

## Verification

Server:

- `pnpm --filter @paperclipai/plugin-sdk ensure-build-deps && tsc
--noEmit` in `server/` — 0 errors.
- `pnpm --filter server test
heartbeat-run-lease-release-terminalization.test.ts
recovery-stale-issue-lock-sweep.test.ts` — 12 tests pass.

User interface:

- `pnpm --filter @paperclipai/ui typecheck` — 0 errors.
- `pnpm exec vitest run ui/src/lib/liveIssueIds.test.ts
ui/src/lib/issue-chat-messages.test.ts` — 40 tests pass.

## Risks

- Low risk. The server change only forces a still-live run row to a
terminal status when the lease releases or when the recovery sweep
confirms the process is dead. It never overwrites an existing terminal
status, and it guards the recovery path with process metadata to avoid
terminalizing a live run.
- The user interface change is additive. The guard only suppresses live
state for a terminal issue and keeps queued and running issues live.
- No database migration. No change to any external endpoint.

## Model Used

- Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, tool use, and
code execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 07:23:56 -07:00
DottaandPaperclip 5b62a3883f feat(settings): add experimental Simplified English Interactions flag (#10934)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents ask humans for decisions through interaction blocks: plan
confirmations, structured questions, suggested tasks, and checkbox
prompts
> - Each agent writes these decision prompts in its own style, so
operators can get long or unclear text at the exact moment they must
decide
> - There is no instance-level control that makes agents use a
controlled language for these decision points only
> - This pull request adds an experimental setting that tells agents to
write all user-interaction content in ASD-STE100 Simplified Technical
English, with the context the user needs and the effect of each choice
> - The benefit is faster, clearer human decisions, with agent thinking
and normal responses unchanged

## Linked Issues or Issue Description

Refs #10410 (optional `/simplified-english` skill in the skills catalog;
this PR adds the instance-level toggle for interactions).

**Problem or motivation**

Agent-posted user interactions (plan confirmations, structured
questions, suggested-task proposals, checkbox prompts) are written in
each agent's default style. Operators who want fast, unambiguous
decisions have no way to ask agents to use a controlled language for
exactly those decision points.

**Proposed solution**

Add an experimental instance setting,
`enableSimplifiedEnglishInteractions` ("Simplified English
Interactions"). When it is on, the server sets
`simplifiedEnglishInteractions: true` in the heartbeat wake payload. The
shared wake-prompt renderer, used by every adapter, then emits a
directive: write all user-interaction content in ASD-STE100 Simplified
Technical English, state what information the user needs to decide, and
state what happens for each choice. The directive applies to interaction
content only. Thinking, comments, documents, and other responses keep
their usual style.

**Alternatives considered**

Per-agent instructions work today, but someone must maintain them on
every agent. An instance-level toggle applies uniformly and turns off in
one place. Server-side rewriting of interaction payloads was rejected:
post-hoc translation is lossy and cannot add the decision context that
only the agent has.

**Roadmap alignment**

Extends the experimental settings surface with another opt-in
agent-behavior refinement, consistent with existing prompt-side flags.

## What Changed

- Added `enableSimplifiedEnglishInteractions` to the experimental
instance-settings zod schema, mirror type, and feature catalog (default
off, tier preference) in `packages/shared`.
- Server `instance-settings.ts` normalizes the flag on both read
branches; `heartbeat.ts` reads it once and passes
`simplifiedEnglishInteractions` into `buildPaperclipWakePayload`.
- Shared adapter renderer
(`packages/adapter-utils/src/server-utils.ts`): added the field to
`PaperclipWakePayload`, normalization, and an `- interaction language
(experimental): ...` directive emitted in both fresh and resume prompt
lanes, so one injection point covers all adapters.
- UI: new experimental settings card "Simplified English Interactions"
in `ui/src/pages/InstanceExperimentalSettings.tsx`, in alphabetical card
order; fixtures updated.
- Tests: renderer coverage for flag on/off in both lanes, plus
schema/catalog/UI fixture updates.

## Verification

- From the repo root: `node_modules/.bin/vitest run
packages/adapter-utils` (88/88), `packages/shared` validators (25/25),
server instance-settings + heartbeat suites (47/47 and 70/70 consumer
tests), `ui` settings tests (32/32).
- Typecheck is clean in all four touched packages.
- Manual check: turn the flag on in Settings → Experimental, wake an
agent, and confirm the wake prompt contains the interaction-language
directive; turn it off and confirm the directive is absent.

## Risks

- Low risk: the flag defaults to off, and the only behavior change is
one extra directive line in the wake prompt when an operator turns it
on.
- The directive is advisory to the agent; models can still deviate from
STE. No data or API shape changes; no migration.

## Model Used

- Claude Fable 5 (Anthropic, model ID `claude-fable-5`), extended
thinking, agentic tool use via 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-05 21:50:41 -05:00
e43f187cad feat(secrets): add human-approved secret proposals (#9934)
## Thinking Path

> - Paperclip is the control plane people use to manage AI-agent
companies.
> - Agents can encounter credentials during work.
> - Directly creating live secrets or bindings would bypass human
governance.
> - Proposal records must remain inert and separate from live secret
resolution until an authorized human approves them.
> - Approval must reuse the existing secret-create and protected
agent-config write paths.
> - This pull request adds the propose, review, approve, and reject
lifecycle.
> - The benefit is that agents can safely hand credentials into
Paperclip without exposing plaintext or gaining authority to activate
them.

## Linked Issues or Issue Description

Follow-on to #9921, which established run-bound agent secret access.

**Problem / motivation:**

Agents can receive credentials during work. There is no governed way for
them to propose a credential or binding without exposing plaintext in
work artifacts or immediately creating live access.

**Proposed solution:**

Store agent-authored proposals outside live secret tables. Encrypt each
proposed value and register exact-value redaction when Paperclip
receives it. Require an authorized human to approve or reject each
proposal. Approval executes through the normal write paths as the human
approver. Binding proposals can target only the proposer or its downward
reporting chain under the restrictive V1 policy.

**Alternatives considered:**

We rejected live secrets with a `proposed` status. That design would put
untrusted rows in resolver, list, and sync paths. It would also allow
uniqueness squatting. We rejected direct agent binding writes because a
binding is an agent-config write and must keep the existing human
permission gate.

**Roadmap alignment:**

This change extends the run-bound agent secret-access foundation in
#9921 with a governed proposal workflow.

## Security Verdict

Q0 SecEng verdict: **PASS-with-required-changes**. The review accepted
the separate proposal-table design and required the implementation to:

- fail closed unless both encryption and exact-value run redaction
registration succeed;
- scrub ciphertext idempotently on reject, withdraw, and expiry, with
audit-visible state;
- treat agent justification as hostile input and foreground action,
target, provenance, and approver permissions;
- snapshot and re-check the target agent plus reports-to chain at
approval to prevent org-chart laundering;
- make cascade approval atomic and fail closed if either secret creation
or binding authorization fails;
- deny low-trust, `skill_test`, `task_bridge`, and non-run-bound sources
consistently; and
- execute approval through the normal human secret/config write paths,
including protected-change gates.

Those requirements are implemented and covered by focused service,
route, and UI tests. Residual V1 risk remains the accepted 14-day
encrypted retention window. Proposal-time redaction also cannot clean a
value that leaked before the propose call.

## What Changed

- Added `company_secret_proposals`, migration `0207`, shared proposal
contracts, and a state-machine service for create, approve, reject,
withdraw, cascade, expiry, and ciphertext scrubbing.
- Added run-bound agent proposal routes and board review routes. The
routes derive provenance from authentication and enforce source
restrictions, company isolation, chain-of-command checks,
approval-as-approver, wake-on-resolution, and dual audit trails.
- Added durable per-run exact-value redaction registration so proposal
values remain redacted on later read surfaces.
- Added the Secrets **Proposals** tab and agent configuration **Proposed
access** rows. The UI shows fingerprint and length only. It also frames
agent justification as untrusted input, runs permission preflight,
supports approve and reject actions, and confirms cascades.
- Updated OpenAPI, agent skill guidance, API reference documentation,
and focused server and UI regression coverage.
- Rebased the branch onto current `master` and renumbered the proposal
migration after `0206`.

## QA Acceptance Results

Q5 QA verdict: **PASS — 9/9 acceptance criteria met**, with one Minor
non-blocking follow-up.

- **AC1:** proposed values never echo, never appear in live
lists/resolvers, and expose only fingerprint + length to board
reviewers.
- **AC2:** restrictive `self_and_reports` matrix passes: self/downward
allowed; upward/lateral denied.
- **AC3:** secret approval uses the normal create path, honors rename
overrides, records proposer/approver provenance, and scrubs ciphertext.
- **AC4:** approved bindings materialize and resolve through the target
agent's runtime list/fetch routes.
- **AC5:** pending-secret bindings require cascade; cascade succeeds
atomically and permission failures leave nothing applied.
- **AC6:** reject, withdraw, dependent rejection, and expiry paths scrub
ciphertext and preserve reasons/audit state.
- **AC7:** token/source and approver denial matrix passes through live
checks plus focused route tests.
- **AC8:** proposal lifecycle events and reused
`secret.created`/config-write events form the required dual audit trail;
origin-issue notification and wake are queued.
- **AC9:** both review surfaces render and execute correctly; UI
approval materializes the binding.

QA also confirmed zero plaintext occurrences for all exercised proposal
values in server logs. The single finding is that the company-level
`bindingTargetPolicy` toggle is not wired yet. V1 is hardcoded to the
restrictive `self_and_reports` policy. The matrix is correct and the
follow-up is tracked separately, so QA classified it as non-blocking.

## Verification

- Focused server proposal and redaction suite: 83 tests pass.
- Focused proposal review UI suite: 54 tests pass.
- Embedded-Postgres migration reapply test: 1 test passes with the
documented 30-second timeout.
- `pnpm --filter @paperclipai/db typecheck` passes, including migration
numbering and safety checks.
- `pnpm --filter @paperclipai/shared typecheck` passes.
- `pnpm --filter @paperclipai/ui typecheck` passes.
- `pnpm check:token-gates` passes with all gates clean.
- Q5 exercised the complete propose, review, approve, bind, and
runtime-resolve flow over real HTTP, JWT, and database paths. It
verified 9/9 acceptance criteria.

## Risks

- Proposal ciphertext is retained encrypted for up to 14 days while
pending. Terminal-state and expiry scrub paths reduce but do not remove
server-compromise risk during that window.
- The V1 target policy is restrictive but not yet company-configurable.
A separate follow-up owns that change.
- A new migration can require another renumber if another migration
lands before maintainers merge this pull request.

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

## Model Used

- OpenAI Codex coding agent. The exact runtime model ID and
context-window size are not exposed. The agent used reasoning,
repository editing, terminal execution, Paperclip API, and GitHub CLI
capabilities. Q3 UI work also records Claude Opus 4.8 assistance in its
commit trailers.

## 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 or instance-local Paperclip issues
or links
- [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>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 21:49:40 -05:00
678728f650 feat: maintained in_review review-path contract + stalled-review actions (#10675)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents move issues to `in_review` and rely on a "review path" (an
interaction, an approval, a monitor, or a named reviewer) to tell them
who decides next.
> - That review path can silently disappear. A user comment supersedes
the pending interaction, a monitor is exhausted, or a run ends without
restoring a path. The issue then sits in `in_review` with nobody
reviewing it and no visible action.
> - Such issues become invisible zombies. Nobody knows a decision is
owed, so the work stalls forever.
> - This pull request makes the review path a maintained invariant,
exposes a `reviewAttention` surface, and gives every stalled review
three inline actions in the UI.
> - The benefit is that an `in_review` issue always shows who reviews
it, or shows an amber "nobody is reviewing this" notice with one-click
Approve, Request changes, and Send back to work.

## Linked Issues or Issue Description

This pull request describes the problem inline. The tracking issue is
internal.

**Subsystem affected**

The review and attention loop that agents and humans share: the
`in_review` status, the `reviewAttention` surface, the /decisions
attention feed, and the issue-page review panel.

**Problem or motivation**

Agent-owned issues in `in_review` can lose their last review path. A
user comment supersedes the pending interaction. A monitor is exhausted.
A run ends without restoring a path. The issue then sits in `in_review`
with no reviewer and no visible action. It becomes an invisible zombie
and the work never progresses.

**Proposed solution**

Maintain the review path as a server invariant. Expose a
`reviewAttention` field that says what is under review, who decides, and
since when. Render a persistent review panel on the issue page and
inline actions on the /decisions feed. Keep human PATCHes into
`in_review` ungated, but record the requesting user so the panel never
renders empty.

**Alternatives considered**

A pure background auto-recovery sweep. This stays opt-in and is not
enough on its own, because it is invisible to the human. A bare status
banner. This is rejected, because it gives no action to resolve the
stall.

**Roadmap alignment**

This improves the core review and attention loop that both agents and
humans use every day.

## What Changed

- **Server — maintained review-path invariant:** when an issue enters or
sits in `in_review`, the server derives and persists a review path
(interaction, approval, monitor, or the requesting user) and recovers a
stale path with one bounded wake instead of leaving the issue pathless.
- **Server — `reviewAttention` surface:** a new field describes what is
under review (bound target with links), who decides, since when, and
whether the review is stalled. Stalled agent-assigned reviews are now
included in the attention feed.
- **Server — inline stalled-review decisions:** secured routes let a
permitted responder Approve (→ `done`), Request changes (→ `todo` + wake
carrying the note), or Send back to work (→ `todo` + wake) directly from
the attention feed.
- **Server — resume-intent wake:** an `in_review -> todo` transition now
wakes the assigned agent so a resumed review is not dropped.
- **Server — user-entry symmetry:** user PATCHes into `in_review` stay
ungated (no 422 for humans) and record the requesting user, who becomes
the named responder when no other path exists.
- **UI — review panel:** a persistent `IssueReviewPanel` renders above
the thread whenever status is `in_review`. The covered state shows the
bound target, responder, and outcomes and hoists the pending
interaction/approval card. The stalled state shows the amber notice plus
the three actions.
- **UI — decisions card actions:** the same three actions render inline
on the /decisions `AttentionQueueRow`.
- **UI — responsive fix:** the stalled action row stacks to full-width
buttons at phone width and returns to a horizontal row at `sm` and up.
New 390px stories capture the phone layout.

## Verification

- `cd ui && npx vitest run src/components/IssueReviewPanel.test.tsx
src/components/AttentionQueueRow.test.tsx src/lib/attention.test.ts
src/api/issues.test.ts` — 91 tests pass.
- Server suites added and updated: `issue-review-attention`,
`issue-stalled-review-decision-routes`, `review-path-recovery`,
`recovery-observability`, and related route/liveness tests (run by CI).
- A designer reviewed the UI at 390px and desktop in light and dark
themes on both the issue-page panel and the /decisions card. The stalled
action row stacks cleanly at phone width with no overlap and keeps the
horizontal row on desktop.

## Risks

- **Migration:** adds migration `0200` (next after master `0199`, no
renumber). It extends the agent-wakeup-requests schema and is additive.
- **Behavioral shift:** `in_review -> todo` now dispatches a wake. This
is intended (resume intent) and covered by tests.
- **Authz:** the inline decision routes are permission-gated. Only a
permitted responder sees and can trigger the actions.
- Overall risk is moderate and contained to the review and attention
loop.

## Model Used

- Claude, Opus 4.8 (`claude-opus-4-8`), extended thinking, 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
- [ ] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 13:54:40 -05:00
DottaandPaperclip f91a6e27c0 feat(issues): contain cross-issue agent side effects (#10837)
## Thinking Path

> - Paperclip is the control plane that coordinates autonomous agent
work.
> - Agents need to collaborate on issues beyond their current
assignment.
> - Cross-issue comments and updates are useful, but an unbounded run
can create cascading side effects.
> - The control plane must preserve company-wide collaboration while
containing each run's influence.
> - Comment attribution must also show the responsible user and the
acting agent in audits.
> - This pull request adds run-bound cross-issue containment,
attribution, and agent-class wake rules.
> - The benefit is safer collaboration without restoring issue-assignee
ownership restrictions.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Agent-authenticated issue comments, updates, reopen behavior, and
assignee wake routing.

**Subsystem affected**

Cross-cutting: server routes and services, shared contracts, database
schema and migration, and implementation documentation.

**Current behavior**

An authenticated agent can collaborate across company issues, but one
heartbeat run has no per-run side-effect boundary. Comment records also
do not persist the responsible user separately from the acting agent.

**Proposed behavior**

Require a valid heartbeat run for agent cross-issue comments and
updates. Audit each attempt and cap a run at 20 cross-issue effects.
Keep the cap in log-only mode until it automatically changes to
enforcement at 2026-08-11 00:00 UTC. Preserve same-issue writes. Use
agent-class wakes for agent comments. Keep same-run completion comments
from reopening completed work. Record the responsible user on
agent-authored comments and activity.

**Reason and benefit**

Agents can collaborate on other issues without an assignment gate, while
each run has an atomic and inspectable side-effect limit. Operators can
identify both the acting agent and the responsible user.

**Breaking changes**

After 2026-08-11 00:00 UTC, the twenty-first cross-issue comment or
update from one heartbeat run returns a containment error. Agent
cross-issue writes without valid run context are rejected. The migration
is additive and backfills existing agent-authored comment attribution
where the source data is available.

## What Changed

- Added an atomic per-run counter for cross-issue agent comments and
updates.
- Added audit events for allowed and rejected cross-issue effects.
- Added the automatic log-only to enforcement flip at 2026-08-11 00:00
UTC.
- Added responsible-user attribution to agent-authored comments,
activity records, shared types, and validators.
- Added an additive migration and migration coverage for existing
comments.
- Updated reopen, resume, and wake behavior so agent comments create
agent-class wakes and same-run completion comments remain inert.
- Updated the implementation specification and regression coverage.

## Verification

- `pnpm exec vitest run
server/src/__tests__/cross-issue-influence-limit.test.ts
server/src/__tests__/issue-comment-attribution-audit-routes.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
packages/db/src/issue-comment-on-behalf-migration.test.ts` — 97 tests
passed.
- `pnpm -r typecheck` — passed, including migration safety checks.
- `pnpm test:run` — server batch: 3,364 passed and 2 skipped; UI batch:
3,504 passed. One unrelated CLI doctor test warned because this agent
runtime injects static AWS credentials.
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm exec vitest
run cli/src/__tests__/secrets.test.ts` — 8 tests passed and confirmed
the CLI failure was ambient-environment sensitive.
- `pnpm build` — passed.

## Risks

- The fixed enforcement timestamp changes production behavior
automatically on 2026-08-11 00:00 UTC. Audit logs before that time
provide rollout visibility.
- The per-run counter serializes on the heartbeat-run row. This prevents
concurrent attempts from racing past the cap but adds a small lock scope
for cross-issue writes.
- Existing comments can only be backfilled when their acting run or
agent attribution is recoverable.

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

## Model Used

OpenAI GPT-5 in the Codex agent runtime. The runtime did not expose a
context-window size. Reasoning, shell tools, code editing, and test
execution were enabled.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-04 13:17:49 -05:00
DottaandPaperclip ca6416da81 fix(server): preserve hot-restart shutdown snapshots (#10815)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A guarded hot restart must preserve or finalize every active agent
run.
> - The server uses embedded PostgreSQL when `DATABASE_URL` is not set.
> - The database dependency installs signal handlers before Paperclip
installs its coordinated shutdown handler.
> - Those handlers can stop PostgreSQL before Paperclip writes the
shutdown snapshot.
> - ACP runs also use server-owned stdio and cannot be adopted after
that server exits.
> - This pull request keeps PostgreSQL available through snapshot and
drain, then uses the existing ordered stop.
> - The benefit is a complete restart report with no false adoption and
no missing snapshot loss.

## Linked Issues or Issue Description

**What happened?**

A guarded hot restart with a valid marker can report a live preflight
run as lost with reason `missing_shutdown_snapshot`. The
`embedded-postgres` package imports `async-exit-hook`. That package
registers `SIGINT` and `SIGTERM` listeners before Paperclip registers
its own shutdown listener. The dependency can close PostgreSQL while
Paperclip queries active heartbeat runs and writes the snapshot.

**Expected behavior**

Paperclip must keep its database available until it persists the
shutdown snapshot and completes any required run drain. A detached CLI
run must remain eligible for adoption. An ACP run must finish as
interrupted and queue a retry because its server-owned stdio cannot
survive the server.

**Steps to reproduce**

1. Run Paperclip from source with embedded PostgreSQL.
2. Start a local ACP-backed agent run.
3. Write a valid hot-restart marker for the current server process.
4. send `SIGTERM` through the service manager.
5. Inspect the restart report and server log.
6. Observe that PostgreSQL can close before the shutdown snapshot query
completes.

**Paperclip version or commit**

The defect reproduces on `2ab797dcbed0031c45c7335a0f497fea2a20bd9a`.

**Deployment mode**

Self-hosted server built from source, with embedded PostgreSQL and a
systemd service.

Related work: #9628 introduced hot-restart continuity. #10556 explores a
broader database ownership transfer. #10775 addresses ACP continuity
after replacement startup. This pull request uses a smaller path: it
keeps the current database owner alive through snapshot and drain, then
performs the existing explicit database stop.

## What Changed

- Remove only the `SIGINT` and `SIGTERM` listeners added by the embedded
PostgreSQL import.
- Preserve Paperclip's existing ordered database stop after heartbeat
snapshot and drain.
- Detect active ACP and server-stdio local runs before shutdown.
- Persist their complete snapshot before changing the marker to an ACP
drain request.
- Drain only ACP runs to an interrupted terminal state and queue their
retry.
- Keep detached CLI runs eligible for adoption in the same mixed
restart.
- Quiesce already-running scheduler queue claims before capturing the
snapshot and selective drain set.
- Report a selected ACP run as lost if process termination succeeds but
its terminal database write does not persist.
- Add the drain reason to the restart report.
- Document the normal path and the one-time recovery path across an
older affected build.

## Verification

- `PAPERCLIP_TEST_DATABASE_MODE=native pnpm --filter @paperclipai/server
exec vitest run src/__tests__/heartbeat-process-recovery.test.ts` — 100
passed.
- `pnpm exec vitest run server/src/shutdown.test.ts
server/src/services/hot-restart.test.ts` — 24 passed.
- The shutdown suite imports the real `embedded-postgres` package and
verifies that its eager signal listeners are absent after the guarded
import.
- The embedded PostgreSQL recovery suite verifies snapshot, pre-snapshot
scheduler quiescence, selective ACP drain, detached CLI adoption, queued
retry, original-run finalization, `lostRunIds=[]` in a mixed restart,
and fail-closed reporting when terminal persistence fails.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `git diff --check` — passed.
- A full workspace typecheck reached the UI and stopped because the
shared local install does not contain its declared `@base-ui/react`
dependency. All server and preceding package checks passed. CI uses a
clean install and remains the authoritative full gate.

## Risks

- Low to moderate risk. This changes shutdown signal ownership and local
run behavior during guarded restarts.
- Paperclip already stops its managed embedded database explicitly. The
change removes only the dependency listeners that race the coordinated
path.
- ACP runs now retry instead of receiving an unsafe bare-process
adoption. Detached CLI runs keep their existing adoption behavior.
- The report adds one field. There is no schema migration or breaking
API change.

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

## Model Used

- OpenAI Codex with GPT-5. The runtime did not expose a more specific
model revision or context-window size. Reasoning, repository editing,
shell execution, and test execution were enabled.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-04 11:44:43 -05:00
DottaandPaperclip ba396c608c feat(server): configure shared workspace concurrency (#10759)
<!-- 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.
> - The heartbeat service starts agent runs in local, SSH, sandbox,
plugin, or Kubernetes environments.
> - Runs can share one project working tree.
> - PR #10699 made every shared working tree single-file, including
trusted local and SSH hosts that support coordinated concurrent work.
> - Operators need a policy that keeps remote environments safe and
restores local multi-agent work.
> - This pull request adds an `auto`, `serialize`, or `allow`
concurrency policy and applies it to the final execution environment.
> - The benefit is safe serialization by default for sandboxed targets
and useful concurrency by default for persistent local targets.

## Linked Issues or Issue Description

Related work: #10699 introduced the busy gate that this change makes
configurable. #7852 covers a separate environment-lease race and does
not provide this dispatch policy.

**Subsystem affected**

`server/` heartbeat orchestration and `packages/shared/` workspace
policy contracts.

**Problem or motivation**

The shared-workspace busy gate always defers a second run. This behavior
prevents local multi-agent projects from running concurrently even when
operators expect agents to coordinate through commits.

**Proposed solution**

Add `sharedWorkspaceConcurrency` with `auto`, `serialize`, and `allow`
values. Default `auto` permits local and SSH concurrency. It serializes
sandbox, plugin, and forced Kubernetes execution.

**Alternatives considered**

Keeping unconditional serialization is too restrictive for persistent
host working trees. Always allowing overlap removes the protection that
sandboxed and remote targets need.

**Roadmap alignment**

This is a focused correction to the shipped cloud and sandbox execution
milestone. It does not add a new roadmap feature.

## What Changed

- Added the optional tri-state field to project policy and issue
override contracts and validators.
- Added a pure resolver with issue override, project policy, and `auto`
default precedence.
- Moved final environment and Kubernetes resolution before the
shared-workspace busy gate.
- Kept the existing deferral and retry behavior for every path that
resolves to serialization.
- Added a task-context warning and a structured log when a run
dispatches beside a live holder.
- Added policy and heartbeat coverage for all requested policy and
environment combinations.
- Documented the new policy and its default behavior.

## Verification

- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/execution-workspace-policy.test.ts
src/__tests__/heartbeat-workspace-busy.test.ts` — 32 tests passed.
- `pnpm -r typecheck` — passed.
- `PAPERCLIP_IN_WORKTREE=false
PAPERCLIP_DATABASE_RESTORE_IN_PROGRESS=false
PAPERCLIP_RESTORE_IN_PROGRESS=false pnpm test:run` — all 3,528 server
tests passed. The later UI/workspace phase passed 3,384 tests and hit
one unrelated `CompanyEnvironments` navigation timing failure.
- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/CompanyEnvironments.test.tsx -t "opens the edit form on a
standalone page with existing values and closes after save"` — the
unrelated UI test passed in isolation.
- `pnpm build` — passed.

Policy matrix:

| Policy | Final target | Expected result | Result |
| --- | --- | --- | --- |
| `auto` | local | Dispatch with holder note | Passed |
| `auto` | sandbox | Defer with `workspace_busy` | Passed |
| `auto` | instance-forced Kubernetes | Defer with `workspace_busy` |
Passed |
| `serialize` | local | Defer with `workspace_busy` | Passed |
| `allow` | sandbox | Dispatch with holder note | Passed |

Existing serialization, retry, and stale-holder tests also pass.

## Risks

- `auto` changes the post-#10699 local and SSH behavior back to
concurrent dispatch. Concurrent agents can mutate the same working tree,
so each dispatched run receives an explicit coordination warning.
- `allow` is an operator override and can permit overlap in sandbox or
plugin environments.
- Unknown environment drivers serialize in `auto` mode. This keeps the
fallback conservative.
- There is no database migration. An absent field resolves to `auto`.

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

## Model Used

OpenAI GPT-5 (Codex). The exact API snapshot and context-window size are
not exposed to the agent. Reasoning, repository editing, terminal tool
use, and local code execution were enabled.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-03 14:25:05 -05:00
DottaandPaperclip bc5c392331 fix(server): stop inferring PR credential preflight from issue text (#10755)
## Thinking Path

> - Paperclip coordinates agent runs and checks required runtime
credentials before dispatch.
> - The push-capability preflight protects runs that use the GitHub PR
workflow skill.
> - PR #10659 also made issue title and description text trigger this
preflight.
> - That text heuristic blocks tasks that mention a pull request but do
not need a bound GitHub token at dispatch time.
> - This pull request removes the text heuristic and keeps the explicit
skill trigger.
> - The benefit is that ordinary task wording no longer causes a false
configuration failure.

## Linked Issues or Issue Description

Refs: #10659

**What happened?**

An issue title or description that said to open a pull request or push a
branch could trigger the push-capability credential preflight. The run
then failed before dispatch when no project-level or agent-level GitHub
token was bound, even when the task could proceed without that
preflight.

**Expected behavior**

The preflight must run only when the issue explicitly selects the GitHub
PR workflow skill. Issue prose alone must not enable the guard.

**Steps to reproduce**

1. Assign a local Codex or Claude agent an issue that says to open a
pull request.
2. Do not attach the GitHub PR workflow skill to the issue.
3. Start the run without a project-level or agent-level GitHub token
binding.
4. Observe the false `push_write_credential_missing` failure before this
fix.

**Paperclip version or commit**

`master` after PR #10659.

**Deployment mode**

Local dev with a git-sensitive local adapter.

## What Changed

- Removed `issueTextImpliesPrDeliverable` and its text-pattern matcher.
- Restored `requiresPushCapabilityPreflight` to use only explicit GitHub
PR workflow skill keys.
- Removed the obsolete issue-text tests while keeping coverage for the
explicit skill, adapter, and issue gates.

## Verification

- `PAPERCLIP_LOG_DIR=<run-owned-dir> pnpm vitest run
server/src/__tests__/heartbeat-workspace-session.test.ts` — 134 tests
passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `git diff --check origin/master...HEAD` — passed.

## Risks

- Low risk. A task that states a PR deliverable but does not select the
GitHub PR workflow skill will no longer receive the early credential
preflight. This is the intended temporary behavior.
- Tasks that explicitly select the skill still receive the existing
credential and checkout checks.

> This change does not add a core feature and does not overlap with
ROADMAP.md work.

## Model Used

OpenAI Codex with model ID `gpt-5`. The runtime did not expose the
context-window size. The model used agentic reasoning, tool use, and
code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [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>
2026-08-03 10:21:33 -05:00
Devin Foley 2c90cf0f2c fix(server): serialize managed-checkout materialization and stop misattributing clone failures (#10723)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Repo-only project workspaces are materialized by a server-side
managed `git clone` into a per-project directory (#10720 added
credentials for private repos)
> - Two issues on the same project routinely wake seconds apart, and
both runs race the same clone target
> - The loser fails with "destination path already exists", and its
failure cleanup removes the directory out from under the winner's
in-progress clone — both runs then fail every round
> - This pull request serializes materialization per target directory
and makes the clone land atomically via a temp sibling + rename, so the
shared target is never partial and never removed
> - The benefit is that concurrent runs on the same project converge:
one clone happens, everyone adopts it, and unrelated failures no longer
blame the GitHub credential

## Linked Issues or Issue Description

**What happened?**

With isolated workspaces enabled on a project whose only workspace is
repo-only, unblocking two issues at once produced lockstep mutual
destruction (observed live, two consecutive rounds): both runs called
the managed-checkout materialization concurrently; one clone created the
target directory, the other's `git clone` failed with `fatal:
destination path '…' already exists and is not an empty directory`, and
that run's failure cleanup deleted the directory while the first clone
was still writing into it (`fatal: could not set 'core…'`). Both runs
failed `workspace_validation_failed`; their staggered retries could race
again. The failure message also wrongly claimed the GitHub credential
"was rejected or lacks access" — the collision had nothing to do with
auth.

**Expected behavior**

Concurrent materializations of the same project checkout share one
clone; a completed checkout is never removed by a failing sibling; the
credential is only blamed for auth-shaped failures.

**Steps to reproduce**

1. Project with a repo-only workspace (private repo, isolated workspaces
on).
2. Move two issues on that project to `todo` at the same time so both
runs start within seconds.
3. Both runs fail workspace validation with "destination path already
exists" / "could not set 'core…'" instead of one clone succeeding.

**Paperclip version or commit**

`master` (75f6256b76).

## What Changed

- `ensureManagedProjectWorkspace` serializes in-flight materializations
per target cwd (a module-level promise map): concurrent callers share
one attempt.
- The clone lands in a `<target>.clone-XXXXXX` temp sibling created with
`mkdtemp`, then moves into place with an atomic `rename`. Clone failure
removes only the temp directory; the shared target is never created
partially and never deleted. If the target appears between the emptiness
check and the rename (another process won), the completed checkout is
adopted instead of failing the run.
- `describeGitAuthFailure` attributes the GitHub credential only when
the error matches the auth-failure pattern; unrelated failures (path
collisions, network errors) no longer claim the token was rejected.

## Verification

- `cd server && npx vitest run
src/__tests__/heartbeat-managed-clone-credentials.test.ts
src/__tests__/git-credentials.test.ts
src/__tests__/heartbeat-workspace-session.test.ts` — 169 tests pass,
including new cases: concurrent materializations of the same checkout
succeed with one shared result and no temp litter; failed clones leave
neither target nor temp directories; an authenticated clone failing for
non-auth reasons does not blame the credential.
- `pnpm --filter @paperclipai/server typecheck` — clean.

## Risks

- Low risk. The serialization is in-process and keyed by exact target
path; the temp+rename pattern stays on the same filesystem (sibling
path) so the rename is atomic. Single-run behavior is byte-identical
apart from the temp-dir intermediate.
- The rename-conflict adoption path accepts a checkout another
materialization completed; the pre-existing `gitDirExists` adoption
semantics are unchanged.

## Model Used

Claude Fable 5 (`claude-fable-5`, extended thinking, agentic tool use
via Claude Code CLI).

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - #10719 and #10720 both hardened credential scrubbing for server-side
git operations
> - #10719 defined `scrubGitCredentialText` locally in `heartbeat.ts`;
#10720 imported the identical function from the new `git-credentials.ts`
> - The two merged cleanly textually, but together they leave
`heartbeat.ts` with an import that conflicts with a local declaration
(TS2440)
> - This pull request keeps the `git-credentials.ts` copy as canonical,
drops the heartbeat-local duplicate, and re-exports the import so
existing importers are unchanged
> - The benefit is that master typechecks, builds, and produces Docker
images again

## Linked Issues or Issue Description

**What happened?**

After #10719 and #10720 merged, master fails typecheck, the server
build, and both Docker image builds with
`src/services/heartbeat.ts(76,3): error TS2440: Import declaration
conflicts with local declaration of 'scrubGitCredentialText'`. The
canary release run for e0c2448267 failed for the same reason, so no
`@paperclipai/db` canary carrying migrations 0201/0202 can publish.

**Expected behavior**

Master typechecks and builds; one canonical `scrubGitCredentialText`
lives in `git-credentials.ts`.

**Steps to reproduce**

`pnpm --filter @paperclipai/server typecheck` on e0c2448267.

**Paperclip version or commit**

`master` (e0c2448267).

## What Changed

- Removed the heartbeat-local `scrubGitCredentialText` definition
(byte-identical to the `git-credentials.ts` copy).
- Re-exported the imported function from `heartbeat.ts` so existing
importers, including `heartbeat-workspace-session.test.ts`, keep
working.

## Verification

- `pnpm --filter @paperclipai/server typecheck` — clean.
- `cd server && npx vitest run
src/__tests__/heartbeat-workspace-session.test.ts
src/__tests__/git-credentials.test.ts
src/__tests__/heartbeat-managed-clone-credentials.test.ts` — 167 tests
pass.

## Risks

Low risk — deletes one of two identical implementations and preserves
the public import surface via a re-export.

## Model Used

Claude Fable 5 (`claude-fable-5`, extended thinking, agentic tool use
via Claude Code CLI).

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - Repo-only project workspaces are materialized by a server-side `git
clone`, and isolated `git_worktree` runs refresh their base ref with
server-side `git fetch`
> - Both operations run outside the agent process with no credentials,
so private GitHub repositories can never be cloned or refreshed —
agent-scoped credential env bindings do not reach them
> - The company secret store already has a well-known GitHub token
convention (`GITHUB_TOKEN` / `GH_TOKEN` / `PAPERCLIP_GITHUB_TOKEN`,
consumed by the external-object provider for API reads), but nothing
server-side consults it for git
> - This pull request resolves that token per run and authenticates the
managed clone and every base-ref refresh with it through an ephemeral
credential helper
> - The benefit is that isolated workspaces work on private repositories
with one company secret, while public repositories and self-hosted
ambient git configuration keep working unchanged

## Linked Issues or Issue Description

**Subsystem affected**

Server workspace materialization (`server/src/services/heartbeat.ts`)
and execution-workspace realization
(`server/src/services/workspace-runtime.ts`).

**Problem or motivation**

A project workspace configured with only a private GitHub `repoUrl`
cannot be used for isolated `git_worktree` runs: the managed `git clone`
runs with a sanitized, credential-less environment, and plain git cannot
consume a bare token env variable without a credential helper. There is
no way to give the server a git credential — storing a `GH_TOKEN`
company secret has no effect on server-side git, and a credential-less
private clone hangs on a terminal prompt until the ten-minute clone
timeout. Base-ref refreshes (`git fetch`) during worktree realization
have the same gap.

**Proposed solution**

A `git-credentials` module resolves a token per run — company secret by
well-known name (`GITHUB_TOKEN`, `GH_TOKEN`, `PAPERCLIP_GITHUB_TOKEN`),
then `GITHUB_TOKEN`/`GH_TOKEN` in the server process environment for
self-hosted deployments, then none — and builds a git invocation that
authenticates via an inline credential helper. The token travels in an
env variable; it never appears in argv, URLs, or on disk. Only
`https://github.com` remotes are authenticated; everything else keeps
ambient behavior. The provider is a single factory seam so a future
brokered credential source can replace it without touching call sites.

**Alternatives considered**

- A GitHub OAuth "connect your account" flow: heavier product surface,
needs app registration and callback custody; out of scope for a server
credential and better served by a dedicated connector later. The
provider seam keeps that path open.
- `gh auth setup-git`: writes helper configuration to disk and requires
a global token env; rejected in favor of per-invocation config with no
persistent state.
- Embedding the token in the clone URL: leaks into argv, error messages,
and `.git/config`; rejected.

## What Changed

- New `server/src/services/git-credentials.ts`:
`createGitRemoteAuthProvider` (memoized per run, one secret resolution
and one audit event), `buildGitAuthInvocation` (helper-reset + inline
helper, `x-access-token` username, `GIT_TERMINAL_PROMPT=0`),
`isGitHubHttpsRemoteUrl` host gating (rejects ssh/GHES/http/other
hosts/userinfo URLs), `describeGitAuthFailure`, and the canonical
`scrubGitCredentialText`. Secret resolutions pass a `system` consumer
access context so they are recorded as secret access events.
- `ensureManagedProjectWorkspace` (now exported) accepts an optional
auth provider; the clone env spreads the token after
`sanitizeRuntimeServiceBaseEnv` (which strips `PAPERCLIP_*`), always
sets `GIT_TERMINAL_PROMPT=0`, distinguishes "credential rejected" from
"no credential configured — add a GITHUB_TOKEN or GH_TOKEN company
secret" in the error, and removes the partially created directory on
clone failure so a timeout-killed clone cannot be adopted as a broken
checkout by the next run.
- `refreshRemoteTrackingBaseRef` (now exported) captures the remote URL
it already looked up, asks the provider for an invocation, and
attributes failed authenticated fetches to the credential in a scrubbed
warning. The optional provider threads through `detectDefaultBranch`,
`resolveAuthoritativeBaseRef`, `inspectExecutionWorkspaceBaseDrift`,
`realizeExecutionWorkspace`, and
`ensurePersistedExecutionWorkspaceAvailable`; heartbeat builds one
provider per run for both the anchor-resolution clone path and workspace
realization/restore.
- `github-external-object-provider.ts` imports the shared secret-name
list; `isGitHubDotCom` is exported from `github-fetch.ts`.
- Docs: "Private repositories and repo-only project workspaces" section
in the execution-workspaces guide, cross-linked from the secrets deploy
doc.

## Verification

- `cd server && npx vitest run src/__tests__/git-credentials.test.ts` —
resolution chain order and precedence, env fallback, memoization,
audited access context, host-gating matrix, invocation shape (token
absent from argv), scrubber, failure descriptions, and a real-git `git
credential fill` round trip that proves the helper executes and answers
with the env-carried token (no network).
- `cd server && npx vitest run
src/__tests__/heartbeat-managed-clone-credentials.test.ts` — clones
behave byte-identically with no provider or a null-returning provider
(local repos, no network), authenticated-failure errors name the
credential, non-auth failures do not mention credentials, partial clone
directories are removed, pre-existing non-git directories keep the
"Using it as-is" path, and the sanitizer spread order keeps the token
env alive.
- `cd server && npx vitest run src/__tests__/workspace-runtime.test.ts`
— new `refreshRemoteTrackingBaseRef` cases: provider offered the remote
URL and null keeps behavior identical; failed authenticated fetch
warning names the credential; unauthenticated failure warning stays
credential-free.
- `pnpm --filter @paperclipai/server typecheck` is clean.
- Manual (optional, networked): store a `GH_TOKEN` company secret,
configure a repo-only project workspace pointing at a private GitHub
repository, run an isolated-workspace issue — the managed clone succeeds
and the worktree run proceeds.

## Risks

- Every new parameter is optional; with no provider the git invocations
are byte-identical to before. Public repos and ambient credential
helpers keep working whenever no token resolves.
- Precedence change when a token exists: a stored company secret now
wins over ambient helpers for `https://github.com` remotes (the helper
list is reset for that invocation). The rejected-credential error names
the secret so an operator can fix or remove it.
- `GIT_TERMINAL_PROMPT=0` on the managed clone is the one always-on
change: a credential-less private clone now fails fast with a clear
message instead of hanging until the ten-minute timeout (it could only
ever "succeed" interactively on a TTY dev server).
- The token is scoped to the git process env for one invocation; it is
never written to agent env, run context, disk, or logs, and error text
is scrubbed of URL userinfo.
- No migrations, no image changes (git ships in the image).

## Model Used

Claude Fable 5 (`claude-fable-5`, extended thinking, agentic tool use
via Claude Code CLI).

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - Heartbeat runs prepare an execution workspace for each issue; with
isolated workspaces (or low-trust runs), the `git_worktree` strategy
needs a real git checkout as its base
> - For repo-only project workspaces, the server materializes the
checkout with a managed `git clone`; when that clone fails, the resolver
drops the error and silently falls back to the agent home directory with
`source: "project_primary"`
> - The pre-dispatch guard then reports
`git_worktree_base_not_git_checkout`, which hides the real cause; if the
fallback directory happens to be a git checkout, the run silently builds
worktrees off the wrong repository
> - This pull request records materialization failures on the resolved
workspace, marks the fallback explicitly, and fails the guard with a
truthful `git_worktree_base_materialization_failed` reason that carries
the clone error
> - The benefit is that operators see the real cause (a failed clone) in
the run error, the blocked-issue comment, and the recovery next action,
instead of a misleading symptom

## Linked Issues or Issue Description

**What happened?**

An issue configured for isolated `git_worktree` execution on a project
whose only workspace is repo-only (a `repoUrl` with no local path) fails
every run with `workspace_validation_failed` and reason
`git_worktree_base_not_git_checkout`, pointing at the agent home
directory. The message does not mention that the managed `git clone` of
the project repository failed (for a private repository the clone can
never succeed without credentials). The recovery flow then blocks the
issue with the same misleading explanation. Run warnings claim "Project
workspace has no local cwd configured" even though the workspace is
configured and the clone failed.

**Expected behavior**

The run failure, the blocked-issue comment, and the recovery next action
should state the real cause: the project workspace checkout could not be
prepared, including the clone error, so the operator can repair the
repository URL, clone access, or configured local cwd. A fallback
directory that happens to be a git checkout must not let the run proceed
against the wrong repository.

**Steps to reproduce**

1. Create a project whose primary workspace has a `repoUrl` pointing at
a private GitHub repository and no local path.
2. Enable the Isolated Workspaces experimental setting (or use a
low-trust run, which forces isolation).
3. Run any issue in that project.
4. The run fails with `git_worktree_base_not_git_checkout` on the agent
home directory; the clone failure appears nowhere.

**Paperclip version or commit**

`master` (bd86dbe41b).

## What Changed

- `resolveAnchorWorkspaceForRun` collects every failed project-workspace
materialization attempt (previously the error was dropped unless the row
was the preferred workspace) and returns two new fields on
`ResolvedWorkspaceForRun`: `baseCwdFallback` and
`materializationFailures`. The `source` label is unchanged because
session migration keys off `source === "project_primary"`.
- `assertGitWorktreeBaseWorkspaceReady` accepts the anchor facts and
fails with the new reason `git_worktree_base_materialization_failed` —
checked before the git-checkout probe, so a fallback directory that
happens to be a git repo can no longer host worktrees for the wrong
repository. The message carries the first scrubbed clone error and
remediation, and lands in `run.error`, the persisted
`workspaceValidation` payload, and the blocked-issue comment.
- New `scrubGitCredentialText` masks URL userinfo (a `repoUrl` can
legitimately embed credentials today) before errors reach warnings or
persisted payloads.
- Fallback warning assembly moved into the pure helper
`buildAnchorFallbackWorkspaceNotes`; a clone failure now produces
"Failed to prepare the project workspace checkout: …" instead of the
false "no local cwd configured", with the existing warning texts
preserved byte-for-byte when nothing failed to materialize.
- The workspace-validation recovery comment and the recovery service's
next action explain the new reason specifically.

## Verification

- `cd server && npx vitest run
src/__tests__/heartbeat-workspace-session.test.ts` — new cases: the new
reason takes precedence over the git-checkout probe (fallback cwd is a
real git repo), payload carries the scrubbed failures, anchor-absent
legacy behavior unchanged, scrubber unit tests, and warning-assembly
unit tests that pin the existing texts.
- `cd server && npx vitest run
src/__tests__/issue-recovery-actions.test.ts
src/__tests__/heartbeat-process-recovery.test.ts
src/__tests__/heartbeat-retry-scheduling.test.ts` — recovery surfaces
still pass.
- `pnpm --filter @paperclipai/server typecheck` is clean.

## Risks

- Additive persisted-payload fields and a new reason string; recovery
reason handling falls through to generic text for unknown reasons, and
no UI consumes the `git_worktree_base_*` strings.
- Intentional behavior change: a repo-only project whose clone fails and
whose agent-home fallback happened to be a git checkout previously ran
in that unrelated repository; it now fails with the truthful reason. A
test locks this.
- Runs without isolated workspaces (the default) never reach the guard;
their fallback behavior is unchanged.

## Model Used

Claude Fable 5 (`claude-fable-5`, extended thinking, agentic tool use
via Claude Code CLI).

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The heartbeat service dispatches agent runs, and issues in a project
can share one project workspace (one working tree on disk).
> - Two runs can execute in the same shared workspace at the same time.
Each run mutates the same uncommitted files and branches, and the runs
corrupt each other's state.
> - Multi-agent projects hit this as soon as two issues in one project
become active together, so the platform needs to serialize
shared-workspace execution instead of relying on luck.
> - This pull request adds a pre-dispatch gate: a run whose issue
targets a busy shared workspace is deferred with a bounded scheduled
retry instead of dispatched.
> - The benefit is that concurrent issue runs in one project take turns
in the shared working tree, while isolated-workspace runs and unrelated
workspaces stay fully parallel.

## Linked Issues or Issue Description

Fixes #10645

## What Changed

- `server/src/services/heartbeat.ts`:
- New pre-dispatch gate in the run executor. Before adapter dispatch,
when the run's issue has a `projectWorkspaceId` and the effective
execution workspace mode is `shared_workspace`, the executor looks for a
holder: another `running` run whose context issue shares the same
project workspace. The gate covers every run shape that reaches adapter
dispatch with issue context — assignee execution runs, comment/mention
interaction wakes, and review-participant runs.
- When a holder exists, the run throws `WorkspaceBusyDeferral` instead
of dispatching. The outer catch recognizes the deferral: it cancels the
run with `errorCode: "workspace_busy"` (contention is not a failure),
cancels its wakeup, schedules a retry through the existing
`scheduleBoundedRetryForRun` primitive (`workspace_busy` reason, 60–120
s jittered delay), and returns the agent to idle. The issue execution
lock transfers to the scheduled retry run, so the issue keeps an active
execution path and stranded-issue recovery does not fire.
- An adapter never dispatches alongside a live holder: deferral has no
attempt ceiling, so a deferred run keeps rescheduling until the
workspace frees. Deadlock safety comes from holder liveness, not a
counter — a holder silent past
`ACTIVE_RUN_OUTPUT_SUSPICION_THRESHOLD_MS` (recovery's own "suspicious
silence" bar, 1 h) stops counting as a holder, so a zombie run can only
delay work, never park it forever, and recovery's silent-run escalation
is already reaping it in parallel. If no retry can be scheduled (agent
paused, issue reassigned), the deferral releases the issue execution
lock so the issue does not strand.
- Holder detection honors isolation: when the isolated-workspaces
experiment is enabled, holders whose issue settings select
`isolated_workspace` / `operator_branch` (or the legacy `isolated`
alias) are not counted, because they never touch the shared tree. A NULL
or `agent_default` mode counts as a holder — over-serializing is the
safe direction.
- Non-assignee deferrals survive replay: the deferral stamps
`workspaceBusyDeferredWhileAssignee` into the run context (inherited by
the scheduled retry), and both the retry promotion gate and the
claim-time staleness check exempt a non-assignee `workspace_busy` retry
from the reassignment cancellation — for such a retry an assignee
mismatch is the expected state, not a reassignment race. An assignee
run's retry keeps the full protection: if the issue is reassigned while
the retry pends, it still cancels with `issue_reassigned`.
- `server/src/__tests__/heartbeat-workspace-busy.test.ts` (new):
embedded-Postgres coverage of the full lifecycle plus unit coverage of
the delay window.

## Verification

- `cd server && pnpm vitest run
src/__tests__/heartbeat-workspace-busy.test.ts` — 10 tests:
- a run whose issue targets a busy shared workspace is cancelled with
`workspace_busy`, its adapter never executes, a `scheduled_retry` run
exists with the 60–120 s window, the issue execution lock points at the
retry run, the holder run is untouched, and the agent returns to idle;
- after the holder finishes, `promoteDueScheduledRetries` +
`resumeQueuedRuns` execute the retry run to success;
- a non-assignee comment-mention wake defers, does not touch the issue
execution lock, and its retry promotes, survives the claim-time
staleness check, and executes despite the assignee mismatch;
- an assignee retry is still cancelled with `issue_reassigned` when the
issue is reassigned while the retry pends;
- a holder issue with `executionWorkspaceSettings.mode =
"isolated_workspace"` does not cause deferral;
- a running run in a different project workspace does not cause
deferral;
- a holder silent past the staleness threshold does not cause deferral
(the run executes);
- a retry with ten prior deferrals still defers again — never dispatches
— while the holder is live;
- delay jitter stays inside the base-to-base-plus-jitter window and
clamps out-of-range random sources.
- `cd server && pnpm vitest run src/__tests__/heartbeat-` — full
heartbeat suite sweep.
- `cd server && pnpm run typecheck`.

## Risks

- Behavioral shift: shared-workspace runs that used to start immediately
now wait for the workspace to free. Against a long-running live holder
the wait is unbounded by design — the alternative is dispatching into a
held working tree, which is the corruption this PR removes. Every
deferral is visible in the run timeline (lifecycle event with the holder
run, issue, and attempt number), and the wake is parked, never dropped.
- A zombie holder (a `running` row whose process died) delays contending
runs by up to the 1 h staleness threshold before it stops counting.
Recovery's silent-run escalation targets the same run on the same clock,
so this window matches what the system already tolerates for silent
active runs.
- The holder check and the dispatch are not atomic; two runs that pass
the gate in the same instant can still race. The gate closes the common
window (a second run waking while the first is mid-execution); the
pre-existing sync-conflict handling remains the backstop for the rare
simultaneous start.
- No schema change, no API change, no new configuration.

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended
thinking, tool use, full repository access; implementation, tests, and
verification runs.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Isolated workspaces give each task a safe and reproducible checkout.
> - The existing setup cloned the development database before an agent
needed to run the app.
> - This made worktree creation slower and heavier for tasks that never
start a service.
> - Runtime services already use one server start path for heartbeat,
operator, and startup recovery flows.
> - This pull request moves heavy setup to that start path and keeps
worktree creation lean.
> - The benefit is faster isolated workspace creation with the same
reliable runtime setup when a service starts.

## Linked Issues or Issue Description

Related pull request: #10652 covers the initial deferred
database-seeding slice. This pull request supersedes it with end-to-end
runtime provisioning and safe cleanup.

**What existing behavior does this improve?**

This improves isolated worktree creation, runtime service startup, and
isolated instance cleanup.

**Subsystem affected**

Cross-cutting: CLI worktree setup, server runtime orchestration, shared
workspace contracts, and development scripts.

**Current behavior**

Paperclip seeds an isolated development database during worktree
creation. It can also leave an isolated instance directory after
workspace teardown. This work happens even when no runtime service
starts.

**Proposed behavior**

Paperclip creates the worktree with a lean eager setup. It runs an
idempotent runtime provision command before the first managed service
spawn. Concurrent starts share one provision attempt. Teardown removes
the isolated instance safely.

**Reason and benefit**

Many agent tasks only edit and test code. They do not need a running
Paperclip instance. Deferring the database seed reduces workspace
startup cost while preserving automatic setup for tasks that start the
app.

**Breaking changes**

None. The new runtime provision command is optional. Existing workspace
behavior is unchanged when it is absent.

## What Changed

- Split Paperclip worktree setup into a lean eager script and an
idempotent runtime provision script.
- Added `runtimeProvisionCommand` to project, issue, realized workspace,
and persisted workspace contracts.
- Added a per-workspace provision mutex before local service spawn for
heartbeat, operator, and startup recovery flows.
- Added a persisted `provisioning` service state and the
`workspace_runtime_provision` operation phase.
- Kept provision time outside the service readiness timeout and made
failed attempts visible and retryable.
- Reclaimed isolated instance data during safe workspace teardown.
- Serialized deferred database seeding across processes and bound
teardown to the instance root captured in persisted workspace metadata.
- Added tests for config flow, concurrency, retry, no-op behavior,
readiness timing, scripts, CLI commands, and cleanup.
- Documented the eager and runtime provisioning contracts.

## Verification

- `pnpm -r typecheck`
- `pnpm build`
- `pnpm test:run` (server: 3,201 passed; UI: 3,345 passed; the CLI phase
exposed one environment-sensitive AWS doctor assertion because the agent
runtime injects static AWS credentials)
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm exec vitest
run cli/src/__tests__/secrets.test.ts -t 'passes AWS doctor checks when
non-secret provider config is present'`
- Focused runtime tests cover serialized provisioning, retry after
stderr failure, absent-command no-op behavior, operation logging,
persisted state order, and readiness timeout exclusion.
- Focused CLI and cleanup tests cover concurrent seed serialization,
stale-lock fail-closed behavior, persisted instance ownership, and
rewritten sibling pointers.

## Risks

- A faulty runtime provision script blocks service startup. Paperclip
records stderr, marks the service failed, and retries on the next start.
- Concurrent service requests share an in-process provision attempt,
while the seed command uses an atomic filesystem lock across processes.
A stale lock fails closed and requires an operator to verify no seed is
running before removing it.
- Isolated instance cleanup is destructive. The cleanup service
validates ownership and path containment before removal.

> 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.6-sol`, with agentic reasoning, tool use, and
code execution. The service does 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>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 10:37:10 -05:00
Devin Foley e4b0152ca3 fix(server): keep the agent invokable when a run fails on a workspace sync conflict (#10660)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents on the same project can share one `shared_workspace` clone,
and each sandbox run reconciles its git history back into it
> - When histories written by different runs genuinely diverge,
reconciliation can hit a real merge conflict — a property of the
*workspace*, not of whichever agent happened to run last
> - That agent nonetheless finalized into a sticky `error` state,
removing a healthy agent from rotation while the workspace stayed broken
— and on a shared workspace this serially knocks out every agent that
touches it
> - This pull request classifies workspace-reconciliation failure
signatures as workspace-scoped, so the run still fails with the full
message but the agent stays invokable
> - The benefit is that one bad workspace state no longer disables
agents one by one

## Linked Issues or Issue Description

Refs #10645 — this addresses the sticky-agent-error clause of that
issue. Workspace run serialization / per-agent worktrees remain tracked
there (design sketch on the issue).

## What Changed

- New exported `isWorkspaceSyncConflictFailure(message)` matching the
reconciliation failure signatures: `merge-tree` conflict ("Failed to
merge concurrent remote git histories"), integrate-retry exhaustion
("Failed to integrate concurrent remote git history"), and bundle
prerequisite failures ("did not send all necessary objects", "lacks
these prerequisite commits").
- Both run-failure finalization paths (adapter returned a failed result;
adapter threw) pass `keepIdleOnFailure` for these signatures — the same
mechanism already used for provider-quota failures — so the agent
finalizes to `idle` instead of `error`. The run itself still fails and
carries the full message; nothing about run reporting changes.

## Verification

- `pnpm vitest run
server/src/__tests__/heartbeat-workspace-session.test.ts` — new
signature matrix (4 positive signatures, negatives for unrelated adapter
failures and null/empty); 121 tests total.
- `cd server && pnpm run typecheck`.

## Risks

- Low. The only change is which failure families put the agent into
`error`; behavior for every other failure is untouched. A workspace
stuck in conflict still fails every run against it (visible on the runs
surface) — it just no longer takes agents down with it.

## 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
2026-08-01 17:53:20 -07:00
Devin Foley 592cade5a6 feat(server): trigger the push-capability preflight from the issue's stated PR deliverable (#10659)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Runs that must push to GitHub have a pre-dispatch credential
preflight (`push_write_credential_missing`) so the missing token
surfaces as a configuration-incomplete blocker instead of a late runtime
failure
> - The preflight only triggers when the issue mentions the GitHub PR
workflow *skill* — routine-created issues and agent-to-agent handoffs
rarely do, even when their text literally says "push the branch and open
a PR"
> - In practice the credential gap then surfaced only after
implementation and review were complete, stranding finished work
> - This pull request adds a conservative, verb-anchored text heuristic
over the issue title and description as a second preflight trigger
> - The benefit is that the credential ask reaches the human before any
work is burned

## Linked Issues or Issue Description

Fixes #10644 (completes the prevention set with #10648, #10650, #10658)

## What Changed

- `issueTextImpliesPrDeliverable(text)`: matches verb-anchored
deliverable statements — "open/create/raise/submit a (draft) pull
request/PR", "push … branch/remote/origin/upstream". Verb anchoring
deliberately ignores passing mentions ("the PR merged yesterday", "PR
feedback addressed").
- `requiresPushCapabilityPreflight` takes the issue's title+description
and ORs the text heuristic with the existing skill-mention trigger;
adapter-type and issue gating are unchanged. The run-dispatch call site
threads the already-loaded issue text — no extra query.

## Verification

- `pnpm vitest run
server/src/__tests__/heartbeat-workspace-session.test.ts` — new
heuristic matrix (4 positive, 6 negative including null/empty) and
preflight-by-text cases (text triggers, passing mention does not, no
issue → no preflight); 122 tests total.
- `cd server && pnpm run typecheck`.

## Risks

- A false positive turns into a configuration-incomplete blocker asking
for a GitHub token on an issue that didn't need one — the heuristic is
intentionally conservative (verb-anchored) to keep that rare, and the
blocker names the exact remediation.
- No behavior change for issues that neither mention the skill nor state
a PR deliverable.

## 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
2026-08-01 17:43:42 -07:00
DottaandPaperclip a388ea1cac Render workspace-ready comments as compact system notices (#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:

![Collapsed Workspace Ready compact
notice](https://pages.paperclip.ing/pap-16051-workspace-ready-notice-20260801/collapsed.png)

Expanded notice:

![Expanded Workspace Ready compact
notice](https://pages.paperclip.ing/pap-16051-workspace-ready-notice-20260801/expanded.png)

## 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>
2026-08-01 16:04:43 -05:00
DottaandPaperclip ee851fc364 feat(heartbeat): expose cache-adjusted run cost (#10349)
## 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 #8947 — hermes adapter never reports usage/cost to Paperclip, so
budget limits never trigger
- Refs #6716 — hermes_local cost/usage capture returns zero
- Refs #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>
2026-08-01 07:15:52 -07:00
DottaandPaperclip b163c6c473 fix(server): preserve hot restart intent across path upgrade (#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 #10045. The hot-restart adoption
flow came from #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: #9628 is the original broader hot-restart feature PR.
#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>
2026-07-31 21:55:59 -07:00
Devin Foley c0b875c46c fix(codex): let sandbox runs use the sandbox image's own Codex login (#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
2026-07-31 18:36:36 -07:00
DottaandPaperclip 7301fae942 fix(heartbeat): atomically claim due timer intervals (#10584)
<!-- 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: #9457, #8416, and #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>
2026-07-31 17:30:41 -07:00
Nicky LeachandPaperclip 53bcf3897f feat: sync @-mentioned projects into remote sandboxes (confined sandbox transport, flag ON) (#10564)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The run layer must move project context into the sandbox that
executes the agent
> - Local sandboxes already stage referenced projects for @-mentions
> - Remote confined sandboxes dropped the whole referenced set, so the
agent lost needed files and paths
> - This pull request keeps the confined sandbox transport aligned with
the local behavior for referenced projects
> - It does this behind a remote-only flag that defaults on, while SSH
keeps the old drop-only path
> - The benefit is that remote runs can read the same referenced project
context that local runs already provide

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting. This change touches server orchestration, sandbox
transport, and observability.

**Problem or motivation**

A run can @-mention another project. Local targets stage each referenced
project and give the agent a path. Remote confined sandboxes dropped the
full referenced set, so the agent could not read those project files or
paths.

**Proposed solution**

Enable referenced-project sync for the confined sandbox transport behind
`PAPERCLIP_MULTI_PROJECT_WORKSPACE_SYNC_REMOTE`, which defaults on. Keep
the SSH transport out of scope and keep it dropping referenced projects.
Repoint each referenced workspace hint at its staged
`project-<projectId>` sandbox directory. Publish
`PAPERCLIP_WORKSPACES_JSON` on the confined sandbox lane. Count each
per-project remote staging failure as a `staging` failure in the
requested-vs-synced metrics.

**Alternatives considered**

Keep the remote path drop-only. That keeps the gap open. Move the change
into SSH too. That expands scope beyond the target transport and adds
risk.

**Roadmap alignment**

No matching item in `ROADMAP.md` showed up in this review.

**Additional context**

The change lands in three commits. The first commit opens the gate for
the confined sandbox transport. The second commit repoints the workspace
hints and publishes the workspace map. The third commit records
per-project staging failure data.

## What Changed

- Opened remote referenced-project sync for the confined sandbox
transport behind `PAPERCLIP_MULTI_PROJECT_WORKSPACE_SYNC_REMOTE`.
- Repointed referenced workspace hints to the staged
`project-<projectId>` sandbox directories and published
`PAPERCLIP_WORKSPACES_JSON`.
- Counted per-project remote staging failures as first-class `staging`
failures in the requested-vs-synced observability.

## Verification

- The pushed ref
`refs/heads/feat/sync-referenced-projects-remote-sandbox` resolves to
the authorized submit SHA.
- `git log --oneline
origin/master..origin/feat/sync-referenced-projects-remote-sandbox`
shows exactly the three expected commits.
- The handoff reports server typecheck clean, adapter-utils typecheck
clean, and the listed unit tests passing.
- The handoff also reports no open review comments and no Greptile score
yet.

## Risks

- The change touches authorization and sandbox path handling, so
regressions could block remote runs or expose the wrong project context.
- The new flag defaults on, so any bug in the remote path affects normal
remote use.
- SSH stays out of scope, so the two transport paths must remain
distinct.

## Model Used

OpenAI GPT-5 via Codex. Tool use enabled. Context window not reported in
this run.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-31 13:31:17 -07:00
DottaandPaperclip cd7f84965c fix(recovery): preserve hand-back wake liveness (#10562)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The heartbeat service delivers issue work to assigned agents.
> - Recovery can hand an issue back to its agent while the recovery run
is still active.
> - The hand-back wake can merge into that active run and disappear when
the run exits.
> - The stranded-work scan also treats the successful recovery run as
proof that the handed-back issue is live.
> - This pull request keeps the hand-back wake for follow-up delivery
and lets the scan repair a lost wake.
> - The benefit is that an assigned issue continues after recovery
without manual operator action.

## Linked Issues or Issue Description

No public issue exists. This is related to the wake reconciliation work
in #8943.

**What happened?**

A recovery action could hand an assigned issue back from `blocked` to
`todo`. The `issue_recovery_action_restored` wake then merged into the
recovery run that made the change. The wake disappeared when that run
exited. The stranded-work scan did not repair the issue because it
treated the successful recovery run as current liveness.

**Expected behavior**

Paperclip must dispatch the hand-back wake after the recovery run exits.
If that delivery is lost, the stranded-work scan must enqueue the
assigned `todo` issue again.

**Steps to reproduce**

1. Start a recovery run for an assigned blocked issue.
2. Resolve a recovery action with the `handed_back` outcome.
3. Move the issue to `todo` while the recovery run is still active.
4. Observe that the wake merges into the active run and no new run
starts after it exits.
5. Run the stranded-work scan and observe that the successful latest run
prevents repair.

**Paperclip version or commit**

`131d476a7e`

**Deployment mode**

Local dev (`pnpm dev`). The defect is in the core server and is not
deployment-specific.

**Agent adapter(s) involved**

Not adapter-specific. This is a core heartbeat and recovery defect.

## What Changed

- Added `issue_recovery_action_restored` to the wake reasons that
require follow-up delivery when an issue run is active.
- Made the stranded-work scan detect a resolved hand-back that occurred
during or after the latest successful run.
- Added focused regression tests for the heartbeat coalescing seam and
the stranded-work repair shape.
- Documented the hand-back liveness guarantee in execution semantics
section 9.1.

## Verification

- `pnpm exec vitest run
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts` passed: 104
tests.
- `pnpm --filter @paperclipai/server typecheck` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `pnpm test:run` passed the server shard (3,095 passed, 2 skipped) and
UI shard (3,182 passed). One unrelated CLI test failed because the agent
environment exports static AWS credentials. `env -u AWS_ACCESS_KEY_ID -u
AWS_SECRET_ACCESS_KEY -u AWS_SESSION_TOKEN pnpm exec vitest run
cli/src/__tests__/secrets.test.ts` passed all 8 tests.
- `git diff --check` passed.
- All GitHub checks passed on commit `8b380e67e6`.
- Greptile gave 5/5 confidence with no comments or unresolved threads.

## Risks

- Low risk. The follow-up rule affects only a recovery hand-back wake
that arrives while the same issue already has an active run.
- The backstop adds one indexed recovery-action lookup for an assigned
`todo` issue whose latest run succeeded.
- The timestamp check uses the latest run start time. This includes
hand-backs made by that run and later hand-backs, but excludes older
resolved actions.

## Model Used

- OpenAI Codex with GPT-5 (`gpt-5`), agentic reasoning, tool use, and
code execution. The serving context-window size is not exposed to the
agent.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-31 11:03:30 -07:00
Nicky LeachandPaperclip 9f7565f4ce feat: activate OpenTelemetry spans on the sandbox start path (#10536)
## Thinking Path

> - Paperclip moves agent work through sandboxed execution and
control-plane services.
> - The sandbox start path now has a no-op span seam.
> - This change turns that seam on when OTLP export is configured.
> - It keeps the default path unchanged when export is off.
> - The result is structured startup traces with low-cardinality
attributes and explicit parent links.
> - The benefit is better observability without changing normal
behavior.

## Linked Issues or Issue Description

No public GitHub issue exists for this change.

### Problem
The sandbox start path has a tracer seam, but it stays a no-op unless
the OTLP export path is active.

### Proposed solution
Enable the server tracer on sandbox bring-up, open a root span, parent
each startup boundary to that root, and keep the export path opt-in
behind `OTEL_EXPORTER_OTLP_ENDPOINT`.

### Alternatives considered
- Keep the start path as a no-op. I rejected that path because it leaves
sandbox start opaque when OTLP export is already configured.
- Add broad attributes for commands and paths. I rejected that path
because the span allowlist must stay low-cardinality.

### Roadmap alignment
This follows the current OTel sandbox-start work and keeps the default
path unchanged.

## What Changed

- Add a root sandbox startup span and child spans for each named startup
boundary.
- Keep concurrent bridge spans parented to the root span.
- Inject the server tracer through the adapter deps without
OpenTelemetry imports in the engine.
- Attach host-received provider duration attributes only when the values
are finite.
- Keep span attributes inside the allowlist and keep command, path, id,
and error text out of span data.

## Verification

- The pushed branch already passed `pnpm --filter
@paperclipai/adapter-utils exec tsc --noEmit`.
- The pushed branch already passed `pnpm exec vitest run
packages/adapter-utils/src/acpx-engine/`.
- The pushed branch already passed `pnpm --filter @paperclipai/server
exec vitest run src/__tests__/environment-execution-target.test.ts
src/__tests__/instrumentation.test.ts`.
- The pushed branch already passed `pnpm --filter @paperclipai/server
exec tsc --noEmit`.

## Risks

- OTel export changes trace volume when the endpoint is set.
- The allowlist limits trace detail, so new fields need care.
- The change stays no-op when OTLP export is off.

## Model Used

- OpenAI GPT-5, 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 capability details
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either linked existing issues or described the issue in-PR
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change and contains no internal
ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-30 21:20:58 -07:00
Nicky LeachandPaperclip a93a74f91a feat(server): surface referenced-project sync warnings and enable multi-project sync by default (#10473)
## Thinking Path

> - Paperclip helps people manage AI agent work for a company
> - Workspace sync must keep one primary path and any referenced project
paths in step
> - A partial sync must not fail in silence
> - Operators also need a clear signal when the feature uses the new
default path
> - This pull request surfaces referenced-project warnings on the run
and turns the feature flag default on
> - The benefit is better visibility and a live multi-project sync path
by default

## Linked Issues or Issue Description

This pull request completes the multi-project workspace sync go-live
work.

Related pull requests:

- Refs: #10380
- Refs: #10448
- Refs: #10469

## What Changed

- Surface referenced-project warnings on the run when a project drops
during authorization or resolution.
- Record a structured failure reason for each dropped referenced
project.
- Emit one structured log line at run preparation with the requested
count, the synced count, and the failure reasons.
- Flip the workspace sync kill-switch default to on when the env value
is unset.
- Keep the primary workspace path unchanged.

## Verification

- `tsc --noEmit` passed in the server package.
- `heartbeat-referenced-projects.test.ts` and
`heartbeat-project-env.test.ts` passed.
- `workspace-runtime.test.ts` passed.
- `adapter-utils` runtime tests passed for sandbox, command, remote, and
file sync paths.

## Risks

- The new default can expose the feature to more runs if an operator
does not set the env override.
- The new surfaced warnings can change operator visible run output.
- The structured log line can add noise if a run has many referenced
project failures.

## Model Used

- OpenAI Codex, GPT-5, tool use, large context window.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-29 17:12:22 -07:00
Nicky LeachandPaperclip 15ce70dc18 feat(server): thread plural referenced-project workspaces through run prep (#10448)
## Thinking Path

> - Paperclip coordinates work for autonomous companies.
> - A run needs a workspace view before execution starts.
> - That view now needs to cover one anchor project and more referenced
projects.
> - Those extra workspaces must stay separate and must not change the
anchor path when the feature stays off.
> - This PR threads plural workspace data through run prep behind a
default-off kill switch.
> - It also keeps extra project workspaces isolated and makes the
realization contract round-trip the new shape.
> - The benefit is safer run prep for referenced projects without
changing the current default path.

## Linked Issues or Issue Description

This PR does not link a public GitHub issue.
It follows the internal run-prep task for plural referenced-project
workspaces.

Problem:
- Run prep resolves the anchor project today, but it does not yet carry
each referenced project into the run workspace view.
- That gap blocks runs that need a second repo or sibling project during
preparation.

Proposed solution:
- Thread a plural workspace result through run prep.
- Keep the anchor path unchanged when the kill switch is off.
- Resolve each referenced project into its own managed checkout
directory when the flag is on.

Alternatives considered:
- Keep one shared workspace and layer the extra repos into it. Rejected
because it would blur isolation and make failures harder to bound.
- Upload the extra workspaces immediately. Rejected because this PR only
prepares the data path.

Roadmap alignment:
- This change sits in the workspace and sandbox path.
- It matches the roadmap work on workspace strategy and cloud or sandbox
agents.

## What Changed

- Added `additionalWorkspaces[]` to the run workspace result.
- Split workspace resolution into an anchor path and an optional
referenced-project path behind `PAPERCLIP_MULTI_PROJECT_WORKSPACE_SYNC`.
- Kept per-project failure isolation so one bad clone does not stop the
run.
- Keyed managed workspace directories by `projectId` so sibling
workspaces stay separate.
- Added `additionalSources[]` to the workspace realization request and
kept read and write paths backward compatible.
- Added tests for the anchor-only path, the new workspace shape, and the
per-project directory rule.

## Verification

- `pnpm --filter @paperclipai/server run typecheck`
- `pnpm --filter @paperclipai/shared run typecheck`
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-project-env.test.ts` 21/21
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/workspace-runtime.test.ts` 98/98

## Risks

Low risk. The new path stays behind a default-off kill switch, so the
anchor flow does not change when the flag is off.
The main risk is a bad referenced project clone. That case now drops
only the affected project and keeps the run alive.
The shared type change also needs every consumer to use the new array
field where extra workspaces matter.

## Model Used

OpenAI Codex (GPT-5; exact internal model ID not exposed in this
environment; tool use enabled)

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-29 14:27:05 -07:00
Nicky LeachandPaperclip 9574cad3e8 test(server): drain heartbeat runs to quiescence before low-trust teardown (#10450)
## Thinking Path

> - Paperclip manages AI work through tasks, comments, and heartbeats
> - A heartbeat wake can register after a test body ends
> - The low-trust red-team route suite tears down data while that wake
can still run
> - Teardown can then lock `issues` and `heartbeat_runs` in opposite
order and deadlock
> - This pull request adds a drain that waits for heartbeat runs to
reach quiescence before teardown
> - The benefit is stable test teardown without removing coverage

## Linked Issues or Issue Description

The serialized low-trust red-team route suite can deadlock in
`afterEach` teardown.
A heartbeat wake can register after the test body ends.
Teardown can then delete `heartbeat_runs` while the wake still writes
issue tables.
This change waits until no run is queued or running before any delete.

## What Changed

- Added `drainHeartbeatRunsToQuiescence` for test teardown.
- Called the drain first in the low-trust red-team route suite
`afterEach` path.
- Kept the change test-teardown only.

## Verification

- The author handoff reports `tsc -p server/tsconfig.json --noEmit` as
clean.
- The author handoff reports 60 of 60 stress-loop runs with zero
deadlocks.
- `pnpm --filter @paperclipai/server typecheck` could not run here
because this workspace lacks `node_modules/typescript/bin/tsc`.

## Risks

- Low risk.
- The change only affects test teardown.
- If a wake never reaches registration, the drain can wait longer than
expected.
- The loop re-checks the run table until no run is queued or running.

## Model Used

OpenAI GPT-5, tool-use, 256k context.

## Checklist

- [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>
2026-07-29 12:05:13 -07:00
Nicky LeachandPaperclip 11273c18d6 feat(server): resolve per-project-authorized referenced-project set for run prep (#10380)
## Thinking Path

> - Paperclip is the control plane for autonomous AI companies
> - Run prep needs to know which referenced projects belong in a run
without breaking company boundaries
> - The anchor project must keep its existing authorization path, while
additional mentioned projects must be checked independently and fail
closed if access is denied or unknown
> - This pull request adds a helper that computes the deduped,
company-scoped referenced-project set for run prep and warns when a
project is rejected
> - It also introduces an off-by-default kill-switch so downstream
consumers can adopt the set safely
> - The benefit is safer multi-project run preparation without widening
access beyond what the run actor is already allowed to read

## Linked Issues or Issue Description

This PR does not rely on a public GitHub issue. The change is
self-contained and follows the feature-request style description below
so reviewers can evaluate it without leaving the PR.

**Problem / motivation**
- Run prep needs to assemble a referenced-project set that includes the
anchor project plus additional @-mentioned projects.
- Additional projects must be authorized independently and rejected
projects must not widen access.
- The result should be safe to merge inertly behind a default-off
kill-switch until downstream consumers opt in.

**Proposed solution**
- Add `resolveRunReferencedProjects(issueId, anchorProjectId, opts)` in
`server/src/services/heartbeat.ts`.
- Compute a deduped company-scoped set with the anchor project first and
additional mentions admitted only after a fail-closed `project:read`
authorization check.
- Drop missing, foreign-company, denied, or errored projects and append
run warnings when they are rejected.
- Keep the feature inert behind a default-off kill-switch until
downstream workspace resolution is wired to consume it.

**Alternatives considered**
- Reusing company membership alone was rejected because it would
over-admit projects and widen access.
- Including all mentioned projects without per-project authorization was
rejected because it would bypass the existing access model.

**Roadmap alignment**
- This is Phase 1 only: the helper is computed but not yet consumed
downstream, so the merge is inert until a later phase turns the flag on.

## What Changed

- Added `resolveRunReferencedProjects(issueId, anchorProjectId, opts)`
in `server/src/services/heartbeat.ts`.
- Enforced company scoping, deduplication, fail-closed authorization,
and warning emission for additional referenced projects.
- Added a configurable cap for the additional referenced-project set.
- Added tests covering allowed, denied, foreign-company, thrown-auth,
dedupe, and overflow cases.
- Added a default-off kill-switch for downstream consumption of the
computed set.

## Verification

- `tsc --noEmit`
- `server/src/__tests__/issues-service.test.ts` now passes 117/117
- `git log --oneline origin/master..HEAD` shows only the expected single
commit on this branch

## Risks

- The new helper is computed but not yet consumed by workspace
resolution, so behavior only changes once downstream code is wired to
it.
- The authorization path for additional referenced projects is stricter
than before, so any unexpected access gap will surface as a dropped
project plus warning.

## Model Used

OpenAI Codex, GPT-5, tool-use enabled, 128k context.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-29 09:48:54 -07:00
DottaandPaperclip 1cd09ed555 perf(heartbeat): reuse task sessions for issue-scoped timer wakes and bound control-plane write retries (#10350)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents make progress in heartbeats: the server wakes an agent
session, it does a slice of work on an issue, records a disposition, and
exits
> - Benchmarking identical coding tasks run as Paperclip-orchestrated
agent pairs vs invoking the same agent harness directly measured a
1.8–2.2× wall-clock slowdown for the Paperclip pairs, dominated by
per-heartbeat orchestration overhead rather than model time
> - Two contributors stood out: (1) since PF-4 (#4838) every
`heartbeat_timer` wake starts a brand-new task session, so continuation
work on a specific issue repays the full session-start and
re-orientation cost on every heartbeat; (2) in degraded environments
agents burn many tool calls retrying the same failing control-plane
write before giving up
> - This pull request reuses the task session for issue-scoped timer
wakes (keeping the PF-4 fresh-session rule only for unscoped exploratory
wakes, which were the original context-bloat case) and adds a
bounded-retry rule to the wake prompt and core skill: after 2
consecutive failures of the same control-plane write, stop retrying it
for the rest of the heartbeat and rely on the adapter/runtime status
channel
> - The benefit is materially less wall-clock and token overhead per
heartbeat while preserving the context-bloat protection PF-4 was added
for

## Linked Issues or Issue Description

Refs #4838 (merged PF-4 change whose reset rule this refines), Refs
#5287, Refs #1907 (related timer-heartbeat session work).

No public GitHub issue exists for the slowdown itself; bug-report
fields:

- **What happened:** Agent pairs orchestrated through Paperclip
heartbeats complete identical task sets 1.8–2.2× slower (wall-clock)
than the same harness invoked directly. Profiling attributed the gap to
per-heartbeat orchestration overhead: every timer wake discards the task
session (full session start + re-orientation), and in degraded
environments agents repeatedly retry the same failing control-plane
write.
- **Expected behavior:** Heartbeat orchestration should add minimal
wall-clock overhead on top of the underlying harness; issue-scoped
continuation work should not pay a fresh-session tax each interval.
- **Steps to reproduce:** Run a fixed benchmark task set once through
Paperclip issue heartbeats and once via direct harness invocation with
the same model/config; compare wall-clock totals.
- **Version/commit:** master @ 3d23c3b2c3, self-hosted deployment.

## What Changed

- `server/src/services/heartbeat.ts`: `shouldResetTaskSessionForWake`
now resets only for `heartbeat_timer` wakes with no derivable task key
(unscoped exploratory wakes). Issue-scoped timer wakes reuse the issue's
task session. `describeSessionResetReason` updated to stay in exact
agreement.
- `server/src/__tests__/heartbeat-timer-wake-session-reset-pf4.test.ts`:
new cases for scoped vs unscoped timer wakes, plus the scoped case added
to the reset/reason agreement invariant.
- `packages/adapter-utils/src/server-utils.ts`: wake prompt template and
execution contract gain a bounded-retry rule — after 2 consecutive
failures of the same control-plane write, stop retrying it for the rest
of the heartbeat, continue useful work, report the failure in the final
response, and use the adapter/runtime status channel as the sanctioned
fallback.
- `packages/adapter-utils/src/server-utils.test.ts`: asserts the new
prompt lines are present in both the template and the rendered wake
prompt.
- `skills/paperclip/SKILL.md`: documents the same bounded write-retry
rule in the core Paperclip skill.

## Verification

- `node_modules/.bin/vitest run
packages/adapter-utils/src/server-utils.test.ts` — 1 file, 83 tests
passed
- `cd server && node_modules/.bin/vitest run
src/__tests__/heartbeat-timer-wake-session-reset-pf4.test.ts` — 1 file,
14 tests passed
- Both run on this branch rebased onto current master (3d23c3b2c3)

## Risks

- Behavioral shift: issue-scoped timer wakes now reuse sessions, so a
long-lived issue session can grow across heartbeats. Mitigated by
keeping the PF-4 reset for unscoped wakes (the originally observed bloat
case) and by existing session compaction.
- Prompt/skill text changes alter agent guidance; the new rule is scoped
narrowly to repeated failures of the same control-plane write.
- No migrations, no API or schema changes, no dependency changes.

## Model Used

- Claude (Anthropic) — `claude-fable-5` (Fable 5), extended reasoning
with tool use, driven 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-27 22:01:29 -05:00
DottaandPaperclip c3bd0c5d50 feat(skills): add beta releases for the core Paperclip skill (#10228)
## Thinking Path

> - Paperclip is the open source control plane people use to organize
and operate AI-agent companies.
> - Agent behavior depends partly on the bundled Paperclip core skill
synchronized into each runtime.
> - The existing database and runtime plumbing already supports
immutable skill-version snapshots and per-agent version selections, but
no product workflow exposed that capability.
> - Replacing the live bundled skill globally would make champion
adoption risky and difficult to compare across agents.
> - This pull request adds an experimental, instance-level beta-skills
gate plus a repository release registry, immutable seeded releases,
enforcement, and a per-agent release picker.
> - The benefit is controlled per-agent evaluation of frozen core-skill
releases while the default-off path remains behaviorally unchanged.

## Linked Issues or Issue Description

### Subsystem affected

Cross-cutting: `server/`, `ui/`, `packages/db`, and `packages/shared`.

### Problem or motivation

Paperclip needs a safe way to evaluate improved versions of its core
operating skill without globally replacing the live default. Today the
version-snapshot and per-agent pin plumbing exists, but operators cannot
use it. A global replacement would make regressions difficult to contain
and would prevent controlled comparisons across agents.

### Proposed solution

Add a default-off instance experiment that exposes immutable, named
core-skill releases. When enabled, operators can pin each agent to a
seeded release; when disabled, every agent resolves the live default
while saved pins remain intact. Validate pinned writes at the API
boundary, gate reads at runtime, and expose the selection in the agent
Skills tab.

### Alternatives considered

- **Replace the bundled core skill globally:** rejected because it
changes every agent at once and provides no rollback/isolation boundary.
- **Ship releases as separate skills:** rejected because releases are
versions of one core capability, not independently enabled skills.
- **Store release snapshots only outside the repository:** rejected
because repository provenance and hashes make builds reproducible and
reviewable.

### Roadmap alignment

This extends the Skills Manager / Skill Studio direction in `ROADMAP.md`
by making core-skill versions operable per agent. It does not duplicate
another open implementation PR; GitHub searches found no related
`enableBetaSkills` change.

### Additional context

The feature remains experimental and default off. The V7 champion was
selected through a multi-model evaluation process, and the frozen
release contents are verified by SHA-256 below.

## What Changed

- Added the default-off instance-level `enableBetaSkills` experimental
flag.
- Added `skills-releases/paperclip/` with the ordered release registry
and frozen `v0` plus `v7-roster` snapshots.
- Added release metadata to `company_skill_versions` and idempotent
release seeding. The migration was planned as `0191`, then renumbered to
`0192` because current `master` claimed `0191` before final rebase.
- Added read-time gating and write-time validation so disabled instances
always resolve the live default and reject pinned-version writes.
- Added the per-agent Release picker in the agent Skills tab, including
responsive layout and beta-pin state.
- Kept `EDITS.md` out of the release registry and PR diff.

### V7 Adoption Evidence

- Paid roster: 6 models, 94-case suite.
- Result: 553/564 pass-within-2, mean 92.17/94, versus the P2 baseline
of 544/564.
- Reference model improved 84→91; maximin improved 84→90.
- Final report:
https://pages.paperclip.ing/skills/optimization/paperclip/pap-14624-p3-final-20260721/

### Provenance

- `v7-roster` is the Phase 1 champion plus additions-only edits
E107–E112. Per-edit rationale remains in the evals repository at
`source/v7-roster/EDITS.md` and is deliberately excluded from this PR.
- `v0` is the `skills/paperclip` tree from commit `ea66ea81`.
- Champion selection was accepted on July 21, 2026 via board card
`9c304fc2` (PAP-14624 G3).
- This delivery mechanism was accepted on July 24, 2026 via plan
revision `2367abd2` (PAP-14858).

### QA Evidence

- P4 QA matrix comment `b7f40522-4e9b-4a3a-9821-28e86fe1a987`: all 6
acceptance criteria passed.
- Automated QA matrix: 166 tests passed with 0 failures, including real
filesystem materialization and full SHA-256 assertions.
- UI QA exercised the real agent Skills tab at desktop and mobile widths
with the experimental flag both on and off.

## Verification

- `pnpm check:token-gates`
- Focused beta-release matrix: 169 tests passed across shared
validators, server services/routes/heartbeat behavior, instance settings
UI, and release picker UI.
- `pnpm -r typecheck`
- `pnpm build`
- `pnpm test:run`: server and UI partitions passed; one CLI doctor test
inherited temporary AWS credentials from the agent heartbeat and
expected no static credentials. The isolated rerun with
`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`
unset passed 8/8.
- V7 SHA-256:
- `SKILL.md`:
`53ab290489684cbf116fdd1406a95f6b6f53c9c36358b1bf8bfeae481e253575`
- `references/cases.md`:
`3b821f59064a7761091020a14819a8d787131f24029748563d6c0e1be7e6eaec`
- `references/workflows.md`:
`69747bd6e05f7e3673d1e67b07ff295df1869c05e1fd029804d5fa9177db92cd`
- Confirmed 49 changed files, no `pnpm-lock.yaml`, no workflow changes,
and no `EDITS.md`.

## Risks

- **Migration:** low-to-moderate risk. Three nullable columns and one
partial unique index are added idempotently; existing rows remain valid.
- **Behavior:** low risk while the flag is off because read-time
resolution forces the live default and saved pins are preserved but
inactive.
- **Frozen content:** release snapshots intentionally diverge from
future live skill edits; provenance and hashes make that divergence
explicit and reproducible.
- **UI:** low risk. The picker only renders for the bundled core skill
when the experimental flag is enabled and seeded releases exist.

> This extends the existing Skills Manager / Skill Studio direction
described in `ROADMAP.md`; it does not duplicate another open
implementation PR. The GitHub PR search found no related
`enableBetaSkills` change.

## Model Used

- OpenAI Codex using `gpt-5.5` with reasoning and
terminal/code-execution tools; context-window size is not exposed by
this runtime. Earlier implementation commits also record Claude Opus 4.8
assistance where applicable.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-27 19:45:59 -05:00
DottaandPaperclip 665408c6d0 fix(codex): classify mid-turn harness crashes structurally as retriable infra (#10210)
## Thinking Path

> - Paperclip is the open source control plane people use to manage AI
agents and their work
> - Heartbeat execution relies on adapters distinguishing agent failures
from failures in the harness running beneath the agent
> - Codex MCP transport crashes can kill the CLI after the JSONL
protocol has started but before it emits a protocol-terminal event
> - Those interrupted streams were left unclassified, so the control
plane terminalized the heartbeat as `heartbeat_failed` / `agent_failure`
with no continuation
> - Agent-level failure is already expressible through the JSONL
protocol via an `error` event, `turn.failed`, or `turn.completed`, so an
interrupted nonzero exit can be classified structurally without
inspecting unstable error strings
> - This pull request reports that shape as `codex_harness_crash` in the
`transient_upstream` family and routes it through Paperclip's existing
bounded retry and recovery-continuation paths
> - The benefit is that transient Codex harness failures recover safely
without misclassifying quoted agent output or depending on
transport-specific wording

## Linked Issues or Issue Description

- **What happened:** Codex MCP transport failures, including rmcp worker
death, could terminate the CLI mid-turn after protocol output began but
before any terminal JSONL event. The run then became an unclassified
terminal heartbeat failure with `continuationCount: 0`; this occurred in
3 of 44 L3 Codex-lane trials during the associated benchmark
investigation.
- **Expected behavior:** a nonzero Codex exit after the protocol starts
but before an `error`, `turn.failed`, or `turn.completed` event should
be treated as a harness/infrastructure crash and enter the existing
bounded retry policy.
- **Why structural classification:** transport error strings vary, and
stdout may quote agent output that merely discusses network failures.
The protocol boundary identifies whether the agent itself produced a
terminal result without regex matching.
- **Recovery behavior:** `codex_harness_crash` maps to `errorFamily:
transient_upstream`, using the existing `same_session` →
`safer_invocation` → `fresh_session` ladder plus the
recovery-continuation transient-infrastructure path.
- Supersedes the regex-based approach in #10150, which is closed.

## What Changed

- Added protocol-state tracking that identifies a nonzero exit after
protocol start and before any protocol-terminal event as
`codex_harness_crash`.
- Propagated the structural classification as `transient_upstream`
through the Codex adapter.
- Added parse unit coverage, including a faithful crash-shaped stream,
without matching stderr transport strings.
- Added adapter execution coverage using a fake Codex process that emits
a protocol prefix and then dies with the observed rmcp stderr line.
- Added heartbeat bounded-retry coverage, including the `errorCode`-only
fallback, and recovery-continuation classification coverage.

## Verification

- `parse.test.ts` — 16 passed.
- `codex-local-execute.test.ts` — 16 passed.
- `heartbeat-retry-scheduling.test.ts` — 30 passed.
- `service.pause-durability.test.ts` — 6 passed.
- Server and Codex adapter TypeScript checks passed.
- The branch commit is unchanged from the tested and pushed `88f5464d40`
handoff.

## Risks

- Low risk: the classification requires a nonzero exit after protocol
start and before any protocol-terminal event, so normal agent-declared
failures and completed turns keep their existing behavior.
- The change intentionally broadens recovery for structurally
interrupted Codex runs; bounded retry limits still prevent indefinite
continuation loops.
- No schema, migration, public API, UI, lockfile, or workflow 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

- OpenAI Codex coding agent. The exact runtime model ID and
context-window size were not exposed by the execution environment;
capabilities used for the implementation included repository analysis,
reasoning, code editing, and terminal-based 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
— the pre-existing, already-pushed branch name was explicitly prescribed
for this replacement PR
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-24 16:40:47 -05:00
DottaandClaude Fable 5 b996b71a38 Deduplicate wake-payload issue descriptions and compact resume deltas (#10216)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Heartbeat wake payloads and the task-context markdown are the two
channels that deliver an issue's brief into an agent's prompt
> - #10151 fixed wake-prompt-only adapter lanes waking without the issue
description by adding it to the structured wake payload
> - That left the description delivered twice per prompt on lanes that
also inject the task-context markdown, and re-delivered in full on every
resume wake, permanently bloating persistent-session context
> - This pull request makes the task markdown the single description
carrier on lanes that use it, and omits the description from
non-assignment resume deltas on all lanes while keeping it for
assignment-shaped and recovery wakes
> - The benefit is that every lane receives the brief exactly once when
it needs it, and long-lived sessions stop re-paying the full brief in
tokens on every wake

## Linked Issues or Issue Description

Refs #10151

Related prior work: #2883, #8402 (earlier description-delivery attempts
referenced by #10151). I searched the PR list for open work on
wake-payload description handling and found none besides the merged
#10151.

**Bug:** After #10151, adapters that inject the `Paperclip task context`
markdown (ACPX engine lanes, claude-local CLI, hermes server and
gateway) receive the issue description twice in a single prompt — once
in the wake prompt's `Issue description:` block and once in the task
markdown. Separately, resume deltas re-send the full description (up to
12k characters) on every wake even though the persistent session already
received it.

**Expected behavior:** The description appears exactly once per prompt
on every lane, and resume deltas only carry it when the resuming session
may not have seen the brief (assignment-shaped or recovery wakes),
leaving an explicit fetch breadcrumb otherwise.

**Reproduction:** Wake a claude-local or ACPX agent on an issue with a
description and inspect the assembled prompt: the description text
appears in both the wake-payload block and the task-context block. Wake
the same session again via a comment: the full description is present
again in the resume delta.

**Affected version:** Current `master` (with #10151 merged).

**Deployment mode:** Adapter-backed heartbeat execution, local and
sandboxed lanes.

## What Changed

- `renderPaperclipWakePrompt` accepts `suppressIssueDescription`; the
four task-markdown lanes pass it so the task markdown stays the single,
uncapped description carrier there.
- Non-assignment resume deltas omit the description and emit `- issue
description: omitted from this resume delta; fetch the issue if you need
the latest brief`. Assignment-shaped reasons (`issue_assigned`,
`issue_reopened_via_comment`, `issue_recovery_action_restored`,
`issue_tree_restored`) and recovery wakes still deliver the full brief.
- `buildPaperclipTaskMarkdown` gains `includeDescription`; the server
now also publishes `context.paperclipTaskMarkdownCompact` (description
stripped, directives and wake comment kept), and the new
`selectPaperclipTaskMarkdown` helper picks the right variant under the
same resume rules, falling back to the full markdown when no compact
variant exists (version skew safety).
- The wake prompt's description block now carries the same user-authored
trust framing the task markdown already had.

## Verification

- `npx vitest run packages/adapter-utils/src/server-utils.test.ts
packages/adapters/claude-local/src/server/acp.test.ts
packages/adapters/codex-local/src/server/acp.test.ts
server/src/__tests__/heartbeat-context-summary.test.ts` — 137 tests
passed, including new coverage for suppression, resume omission plus
breadcrumb, assignment-shaped resume inclusion, compact-variant
building, variant selection, and an end-to-end ACPX prompt-assembly test
asserting the description appears exactly once on fresh wakes and not at
all on comment resumes.
- `npx vitest run` in `packages/adapters/hermes` — 59 tests passed,
including a gateway execute-level test asserting the brief is sent
exactly once on fresh runs and not re-sent on stable-session resumes.
- `tsc --noEmit` in `packages/adapter-utils`,
`packages/adapters/claude-local`, `packages/adapters/hermes` — clean;
`server` matches the `master` baseline exactly (pre-existing plugin-sdk
resolution errors only, none in touched files).
- Pre-existing failures confirmed identical on clean `master`:
claude-local `execute.remote.test.ts` / `test.probe.test.ts`,
adapter-utils `mcp-isolation.integration.test.ts` (requires a newer
local Claude CLI).

## Risks

- Behavioral shift, prompt-only: a resumed session woken by a comment on
an issue it never handled (rare — assignment wakes normally precede
comment wakes) would not get the inline description; the breadcrumb plus
the standard issue-fetch path covers it.
- Additive context key (`paperclipTaskMarkdownCompact`); older adapters
ignore it and newer adapters fall back to the full markdown when it is
absent, so mixed-version deployments degrade to current behavior.
- No schema, migration, or API changes; the structured wake-payload JSON
shape is unchanged.
- Known follow-up deliberately out of scope: openclaw embeds the raw
wake-payload JSON (which still contains the description) in prompt text
for machine parsing. The hermes-gateway lane is handled: it detects
stable-session resumes (issue/agent session-key strategy plus a stored
prior session id), compacts the task markdown, and omits the description
from its prompt-embedded JSON copy.

> This is a focused correctness/efficiency fix to existing wake plumbing
and does not overlap with planned roadmap feature work.

## Model Used

- Anthropic Claude Fable 5 (`claude-fable-5`), extended thinking
enabled, with repository tool use, shell execution, and local test
execution via Claude Code.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
(execution-workspace branch, same convention as merged #10202)
- [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
(code-level docs; 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 16:35:07 -05:00
DottaandPaperclip d3c004d1b8 Fix issue descriptions in structured wake payloads (#10151)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Heartbeat wake payloads provide the scoped task context an agent
needs before it can act safely
> - Assignment wakes already loaded the issue description for task
markdown, but the structured wake-payload builder dropped it
> - Agents reading `PAPERCLIP_WAKE_PAYLOAD_JSON` could therefore see a
missing brief while also being told no fallback fetch was needed
> - Long descriptions also need a bounded representation so wake
environments and prompts remain safe
> - This pull request carries the description through the server and
adapter contract, and marks truncated descriptions as requiring fallback
fetch
> - The benefit is that agents receive the actual brief instead of
inventing requirements from the title

## Linked Issues or Issue Description

Fixes: #5844
Fixes: #2882

Related prior attempts: #2883 and #8402. This change adds focused
regression coverage and enforces the missing long-description fallback
invariant.

**Bug:** Issue-assignment wake payloads omitted the issue description
from the structured payload even when the issue had a populated
description.

**Expected behavior:** The structured wake payload includes the issue
description. If the description must be truncated for payload size,
`fallbackFetchNeeded` is `true`.

**Reproduction:** Assign an issue with a description to an agent and
inspect `PAPERCLIP_WAKE_PAYLOAD_JSON`; before this change,
`issue.description` was absent while `fallbackFetchNeeded` could remain
`false`.

**Affected version:** Reproduced on current `master` before this patch.

**Deployment mode:** Adapter-backed heartbeat execution, including local
Codex agents.

## What Changed

- Include `issues.description` in the server wake-payload query and
supplied issue summaries.
- Bound inline descriptions at 12,000 characters and force fallback
fetch when truncation occurs.
- Preserve and render description metadata through shared adapter
normalization and prompt rendering.
- Add focused tests for long-description fallback and exact brief-string
rendering.

## Verification

- `pnpm exec vitest run
server/src/__tests__/heartbeat-agent-session-message.test.ts
packages/adapter-utils/src/server-utils.test.ts` — 81 tests passed.
- `pnpm --filter @paperclipai/adapter-utils typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `git diff --check` — passed.

## Risks

- Low risk: the payload shape is additive.
- Very long descriptions are truncated at 12,000 characters; the payload
explicitly requests a fallback fetch for the full brief.
- Prompt size increases by the issue-description length for scoped
wakes, bounded by the same limit.

> This is a focused correctness fix and does not overlap with planned
roadmap feature work.

## Model Used

- OpenAI GPT-5.4 via Codex CLI, with reasoning, repository tool use,
shell execution, and test execution. 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>
2026-07-24 15:40:39 -05:00
7014e46e5b fix(recovery): wake ambiguous successful runs on normal model (#10184)
## Thinking Path

> - Paperclip is the open source control plane people use to coordinate
AI agents and their work.
> - The heartbeat recovery subsystem detects successful runs that leave
assigned issues `in_progress` without a durable disposition or
continuation path.
> - The existing corrective wake used a cheap, status-only model
profile, so the assignee could not perform missing verification or
deliverable work before choosing the issue disposition.
> - The existing wake prompt also omitted the original issue context and
the agent's own final report, making an honest finish/blocked/continue
decision harder.
> - This pull request keeps the structural handoff guards and
one-attempt loop bound, but wakes the assignee on its normal model lane
with context-rich instructions.
> - The benefit is that Paperclip asks the responsible agent to inspect
its own evidence, perform the smallest missing verification when needed,
and then record a real disposition without server-side prose
classification.

## Linked Issues or Issue Description

Related prior approach: #10154 (closed; this PR intentionally does not
reuse its regex classifier or route-level gate).

**Problem**

A succeeded agent run can leave its issue `in_progress` with no valid
disposition. Paperclip already detects this structurally and queues a
corrective handoff, but that wake currently runs as cheap/status-only
recovery and receives little context. The assignee may be unable to
create deliverables or verify the work, and the prompt does not quote
the report that caused the ambiguity.

**Expected behavior**

The corrective wake should use the assignee's normal model and adapter
settings, include the issue identifier/title/description, quote the
agent's own final report, include any recorded next action, preserve the
four disposition options, and explicitly require concrete verification
before marking the issue done.

**Scope**

This change does not classify run prose, add a route-level disposition
gate, alter run-liveness classification, or change the one-attempt
handoff loop bound.

## What Changed

- Switched successful-run corrective handoff payloads and context
snapshots from `status_only` to `normal_model`, removing cheap-model and
status-only guard hints.
- Added issue description, final-report, next-action, and
detected-progress fallback context to the handoff decision and
instruction builder.
- Reworked the instruction into clear "supposed to do / what happened /
options / what to do" sections with bounded description/report excerpts
and verbatim blockquotes.
- Added unit and heartbeat integration coverage for normal-lane
payloads, context plumbing, evidence quoting, fallback behavior, and
truncation while preserving structural skip tests.

## Verification

- `cd server && pnpm exec vitest run
src/services/recovery/successful-run-handoff.test.ts` — 24 tests passed.
- `cd server && pnpm exec vitest run
src/__tests__/heartbeat-process-recovery.test.ts -t "queues one
finish-handoff wake when a successful run leaves in-progress work
without a next action"` — 1 passed, 90 skipped.
- `pnpm --dir server typecheck` — passed.
- `git diff --check` — passed.

## Risks

- Low-to-moderate behavioral risk: an ambiguous successful run now
consumes the assignee's normal model rather than a cheap profile and may
perform verification or finish work before disposition.
- Prompt excerpts are bounded to approximately 1,200 description
characters and 2,000 report characters; very long context is
intentionally ellipsized.
- The existing structural skip guards, idempotency key, and single
corrective attempt remain unchanged to prevent loops.

> 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 using `gpt-5.6-sol`, high reasoning effort, with
repository/tool execution. Context-window size was not exposed by the
runtime configuration.

## 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>
2026-07-24 14:32:26 -05:00
DottaandPaperclip 7f766526a6 feat(sandbox): add task-scoped egress grants (#10155)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Confinement providers protect agent runs with default-deny network
policies
> - Kubernetes environments currently apply only provider-level,
namespace-wide egress allowances
> - Tasks that legitimately need GitHub or package registries therefore
cannot request narrow access, while network failures do not explain the
governing policy or how to request a grant
> - This pull request adds issue-scoped egress grants that become
workload-owned, run-label-selected policies and carries the effective
grant through lease audit metadata
> - The benefit is that internet-dependent work can run without enabling
broad egress for every concurrent task, and denied requests point
operators to the exact grant path

## Linked Issues or Issue Description

No public issue exists. Related but distinct: Refs #9944, which adds a
provider-wide open-internet posture; this PR keeps provider defaults
narrow and adds per-task grants.

**Problem / motivation**
Kubernetes sandbox egress is configured at the provider/tenant level. A
task that needs to clone from GitHub or install from PyPI cannot request
those destinations without changing the policy for every run in the
tenant namespace. DNS/connectivity failures also surface as generic tool
errors with no policy name or remediation path.

**Proposed solution**
Accept `executionWorkspaceSettings.networkEgress.allowFqdns` and
`allowCidrs`, forward the setting through heartbeat environment
acquisition, and create a workload-owned NetworkPolicy or
CiliumNetworkPolicy selected by `paperclip.io/run-id`. Record the
effective grant in lease activity/metadata, expose policy context
through `PAPERCLIP_NETWORK_EGRESS_*`, and append the grant path to
likely policy-related stderr failures.

**Alternatives considered**
A provider-wide open-internet switch is broader than required and is
already covered by #9944. Mutating the existing namespace policy would
leak each task's destinations to other concurrent runs. Standard
Kubernetes NetworkPolicy cannot enforce FQDNs exactly, so standard mode
uses the existing hardened public-IPv4 TCP 80/443 fallback only for the
selected run; Cilium mode remains exact.

**Roadmap alignment**
This extends the existing cloud/sandbox agent roadmap capability with
task-level control-plane policy and does not duplicate a planned roadmap
item.

## What Changed

- Added validated `networkEgress` grants to issue execution workspace
settings and forwarded them through environment lease acquisition.
- Added workload-owned, run-label-scoped
NetworkPolicy/CiliumNetworkPolicy resources for task FQDN/CIDR grants.
- Added lease audit metadata, sandbox policy environment variables, and
actionable network-denial stderr guidance.
- Added focused parser, manifest, policy creation, and denial-message
tests plus Kubernetes provider documentation.

## Verification

- `pnpm -C packages/shared exec vitest run src/validators/issue.test.ts`
— 27 passed.
- `pnpm -C packages/plugins/sandbox-providers/kubernetes test -- --run
test/unit/network-policy.test.ts test/unit/cilium-network-policy.test.ts
test/unit/scoped-network-egress.test.ts` — 21 passed.
- `pnpm -C server exec vitest run
src/__tests__/execution-workspace-policy.test.ts` — 15 passed.
- `pnpm exec vitest run
server/src/__tests__/heartbeat-plugin-environment.test.ts
server/src/__tests__/environment-runtime.test.ts` — 26 passed.
- `pnpm --dir packages/db build && pnpm --dir packages/shared build &&
pnpm --dir packages/plugins/sdk build` — passed, including migration
safety checks.
- `pnpm --dir packages/plugins/sandbox-providers/kubernetes typecheck &&
pnpm --dir server typecheck` — passed after refreshing the worktree's
frozen offline dependencies.
- End-to-end cluster validation of the `build-cython-ext` benchmark
remains for CI/maintainer Kubernetes infrastructure; the focused tests
assert `github.com` and `pypi.org` produce a policy selected only by the
granted run.

## Risks

- Standard NetworkPolicy cannot express FQDNs, so an FQDN grant allows
hardened public IPv4 TCP 80/443 for that run; use Cilium mode for exact
hostname enforcement.
- The new field is additive and absent by default, so existing runs keep
the current provider-level policy.
- Workload owner references garbage-collect scoped policies with the
Job/Sandbox; a cluster/controller that ignores owner references could
temporarily strand a policy that still selects no future run ID.

> 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, exact model ID `gpt-5.6-sol`, high reasoning mode, tool
use and code execution. 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>
2026-07-24 09:58:58 -05:00
Michael NguyenandPaperclip caae2778f0 fix: deliver plugin agent session turns and replies (#10137)
## Thinking Path

> - Paperclip manages agent execution through heartbeat runs and
adapter-specific sessions
> - Plugins can open an agent session and send a conversational message
through the host service
> - The host previously stored that message only in opaque wake payload
metadata, so local adapters never saw it in their CLI prompt
> - The host also forwarded run log chunks but did not expose the
persisted final assistant text as the session reply
> - This pull request defines both sides of the session contract in the
shared wake renderer and terminal run event
> - The benefit is that local adapters receive the actual conversational
turn and plugins receive one canonical final reply

## Linked Issues or Issue Description

Related context: Refs #629 and Refs #2880 describe adjacent
`claude_local` final-text visibility failures. They concern issue
comments rather than plugin agent sessions, but exercise the same need
for a canonical persisted run summary.

Companion consumer change: paperclipai/paperclip-gateway#3.

Bug description:

- **Observed:** calling the plugin host's
`agents.sessions.sendMessage()` with `prompt: "hello"` woke a
`claude_local` agent, but the generated CLI prompt omitted `hello`. On
completion, the session emitted log chunks and a generic `Run completed`
done event, so callers could not reliably recover the assistant reply.
- **Expected:** the prompt becomes the user-supplied conversational turn
for that agent session, and the successful terminal event carries the
run's canonical final user-facing assistant text.
- **Reproduction:** create a plugin agent session for a local adapter,
call `sendMessage()` with a non-empty prompt, inspect the adapter prompt
and terminal session event.
- **Affected baseline:** `b517b887a` on `master`, local trusted
deployment with plugin host services and `claude_local`; `codex_local`
shared the wake-rendering gap because both use the common Paperclip wake
prompt renderer.

## What Changed

- Added a typed `agentMessage` wake payload rendered by the shared
adapter prompt path used by `claude_local`, `codex_local`, and other
local adapters.
- Labeled session content as user-supplied and explicitly
non-authoritative: it cannot expand authorization, permissions, task
scope, or company boundaries.
- Preserved ordinary heartbeat behavior by omitting the section when no
agent-session message exists.
- Added canonical `finalText` to terminal heartbeat status events from
the already-persisted run summary/result/message.
- Defined successful `AgentSessionEvent.message` as the canonical final
user-facing reply (or `null`) and forwarded it on the terminal `done`
event.
- Added host, wake-renderer, normal-heartbeat, and terminal-reply
regression coverage.

## Verification

- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/heartbeat-agent-session-message.test.ts
server/src/__tests__/heartbeat-run-status-payload.test.ts
server/src/__tests__/plugin-agent-sessions.test.ts
server/src/__tests__/heartbeat-run-summary.test.ts` — 87 passed.
- `pnpm -r typecheck` — passed across all 31 workspaces.
- `pnpm build` — passed.
- `pnpm test:run` — 2,860 passed, 1 skipped, 3 unrelated failures: two
existing macOS temp-path alias assertions (`/tmp` vs `/private/tmp`) in
workspace branch-containment tests and one reproducible auto-port
runtime-service adoption failure. The same three failures reproduce when
the two files run alone; none touch this change.
- Live Slack verification intentionally remains operator-gated because
it requires rebuilding/restarting the host.

## Risks

- User-controlled chat text now reaches the model prompt, which is an
intentional prompt-injection surface. The renderer labels it as
untrusted conversational content, while the existing plugin/session
company checks and caller authorization remain unchanged.
- `finalText` is added to company-scoped heartbeat status events. It is
derived from the same persisted summary/result/message already used for
run comments; no raw stdout or secrets are added.
- Consumers that ignore the new field remain compatible, and successful
runs without usable final text still emit `message: null`.

> 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), agentic reasoning with repository/tool use and
code execution; context-window size is not surfaced in this environment.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-23 20:52:42 -07:00
8af70b9fae fix(test): drain in-flight heartbeat runs before liveness teardown (#10040)
## Thinking Path

> - Paperclip runs AI agent heartbeats to manage work; each heartbeat
dispatches `executeRun` fire-and-forget, which is intentional for
concurrency
> - The server escalation test suite
(`heartbeat-issue-liveness-escalation.test.ts`) exercises
`reconcileIssueGraphLiveness`, which heals a resolved-dependency wake by
enqueuing an on-demand heartbeat run
> - `enqueueWakeup` → `startNextQueuedRunForAgent` dispatches the run
fire-and-forget (`void executeRun(...)`), so the background run outlives
the awaited reconcile call
> - The test's `afterEach` polled `heartbeat_runs.status` to wait for
idle, but that flips to `completed` while `executeRun`'s finally block
is still flushing events — the escaping `heartbeat_run_events` insert
could land between the events delete and the runs delete, tripping the
FK constraint
> - This PR fixes the race deterministically by tracking in-flight
`executeRun` promises and exposing
`heartbeatService.drainActiveRunExecutions()`, which the suite awaits
before clearing tables
> - The benefit is a permanently reliable escalation test suite with no
sleeps, no retry bumps, and no production behavior change

## Linked Issues or Issue Description

**What happened?**

The `heartbeat-issue-liveness-escalation.test.ts` suite intermittently
failed in CI with:
```
delete on table "heartbeat_runs" violates foreign key constraint
"heartbeat_run_events_run_id_heartbeat_runs_id_fk"
```

**Expected behavior**

`afterEach` cleanup should complete without FK violations.

**Steps to reproduce**

The race is timing-dependent but surfaces reliably when the teardown
window is artificially widened. `reconcileIssueGraphLiveness()` heals
resolved-dependency wakes by dispatching a heartbeat run fire-and-forget
(`void executeRun(...)`). The old `afterEach` polled
`heartbeat_runs.status` — but that flips to `completed` while
`executeRun`'s finally block still has pending `heartbeat_run_events`
row writes. The escaping insert can land between the events delete and
the runs delete.

**Paperclip version or commit**

Reproducible on current `master` (commit
`b57aa9950c707a024156c34b79326a82b2dcca31`)

## What Changed

- **`server/src/services/heartbeat.ts`** — tracks all in-flight
`executeRun` promises in a module-level `Set`; exposes
`heartbeatService(db).drainActiveRunExecutions()`, which loops until the
set drains (a completing run can enqueue the next queued run in its
finally, so a single `await` is not enough)
-
**`server/src/server-suites/heartbeat-issue-liveness-escalation.test.ts`**
— replaces the poll-on-`heartbeat_runs.status` teardown with `await
heartbeatService(db).drainActiveRunExecutions()` before clearing tables;
removes the now-unnecessary `waitForHeartbeatRunToComplete` helper

## Verification

```bash
# Full file (22 tests)
npx vitest run server/src/server-suites/heartbeat-issue-liveness-escalation.test.ts

# 12x stress loop (264 test-runs, 0 failures)
for i in $(seq 1 12); do
  npx vitest run server/src/server-suites/heartbeat-issue-liveness-escalation.test.ts || break
done

# Type check the changed files
npx tsc --noEmit
```

- 22/22 tests green locally
- 12/12 full-file loop iterations: 264 test-runs / 264 afterEach cycles,
0 failures
- Widened-teardown stress variant (failed deterministically before the
fix) now passes with the drain

## Risks

Low risk. The drain mechanism is additive — it only affects test
teardown and could also be wired into graceful shutdown. The
fire-and-forget dispatch in production is unchanged. The `Set`-based
tracking adds negligible overhead per run dispatch (insert on dispatch,
delete on completion).

## Model Used

- **Provider:** Anthropic
- **Model:** Claude Sonnet 4.6 (`claude-sonnet-4-6`)
- **Context window:** 200K tokens
- **Mode:** Tool use, code execution, extended reasoning

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` /
`Closes: #` / `Refs: #` OR (b) described the issue in-PR following the
relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] 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: Harold Kim <harold@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-22 12:37:09 -07:00
e1e35881af feat(cost-events): propagate issue.billing_code at heartbeat record time (#6821)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agent runs report progress through the heartbeat service, which
writes the cost ledger (`cost_events`) as usage accrues
> - `cost_events` already has a `billing_code` column, but nothing
populates it — the heartbeat writes `issueId`/`projectId` and leaves
`billing_code` NULL
> - Issues carry a `billing_code`, so the attribution data sits one join
away but never reaches the ledger rows
> - Reporting therefore has to reconstruct attribution by joining back
to `issues` at query time, which reflects the issue's *current* billing
code rather than the one in effect when the cost was incurred
> - This pull request threads `billingCode` through
`resolveLedgerScopeForRun` so the heartbeat stamps it onto each
`cost_events` row at record time
> - The benefit is that attribution is captured at write time and stays
correct if an issue's billing code later changes

## Linked Issues or Issue Description

No existing public GitHub issue. Describing the problem in-PR:

**Problem.** `cost_events` has a `billing_code` column that is never
written. The heartbeat's cost-ledger insert records `issueId` and
`projectId` but not the billing code of the issue the run belongs to, so
every row lands with `billing_code` NULL.

**Impact.** Cost-per-billing-code reporting has to derive attribution by
joining `cost_events` back to `issues` at query time. That join returns
the issue's billing code *as of the query*, not as of when the cost was
incurred, so historical cost reports shift retroactively whenever an
issue is re-coded.

**Desired behaviour.** The billing code in effect at record time is
stored on the `cost_events` row itself.

**Related PRs.** #6820 — same change to the same file by the same
author, opened separately. These are duplicates; only one should land.

## What Changed

- `resolveLedgerScopeForRun` now selects `issues.billingCode` alongside
`id` and `projectId`.
- The scope object it returns gained a `billingCode` field, populated
with `issue?.billingCode ?? null`.
- The early-return path for runs with no issue in context returns
`billingCode: null`.
- The `costs.createEvent` call in `heartbeatService` passes
`billingCode: ledgerScope.billingCode` alongside `issueId`/`projectId`.

No schema migration: `cost_events.billing_code` already exists.

## Verification

**No automated test accompanies this change.** There is currently no
test asserting that a `cost_events` row carries the issue's billing code
when an issue is in scope, or `null` when there is not. A reviewer
should treat the checks below as manual verification only.

Manual verification against a running instance:

```sql
-- Non-NULL billing_code for recent runs on billed issues
SELECT billing_code, COUNT(*)
FROM cost_events
WHERE created_at > NOW() - INTERVAL '1 hour'
GROUP BY billing_code;

-- Cost attribution query this change is intended to enable
SELECT billing_code, SUM(cost_cents)
FROM cost_events
GROUP BY billing_code;
```

Expected: rows for runs attached to an issue with a billing code now
carry that code; runs with no issue in context remain NULL.

## Risks

Low risk in blast radius, with two things worth a reviewer's attention:

- **Behavioural shift for consumers.** `cost_events.billing_code` was
uniformly NULL and now starts arriving populated. Anything downstream
that groups, filters, or dedupes on that column will see new values and
new cardinality. Existing rows are not backfilled, so the column is
mixed NULL/non-NULL across the historical boundary.
- **No test coverage.** The null-fallback behaviour on both paths is
asserted only by reading the code, not by a test.
- **Migration safety:** not applicable — no schema change; the column
already exists.
- **Failure mode:** if `billingCode` were absent from the `issues`
selection the value would silently be `undefined` rather than erroring,
so the field is worth confirming in review.

## Model Used

**TODO (author):** this section is required and cannot be completed on
your behalf. Please state the provider and model name, the exact model
ID/version, and the reasoning/thinking mode used — or "None —
human-authored" if no AI model was involved. Per the template, the
"Generated with Claude Code" footer is not a substitute for this
section.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [ ] I have specified the model used (with version and capability
details) — **pending author input, see above**
- [ ] 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 — #6820 is a duplicate of this PR
- [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
- [ ] I have added or updated tests where applicable — **no test added
for the new field**
- [x] I have updated relevant documentation to reflect my changes — not
applicable, no user-facing or documented behaviour changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green — **`e2e` did not complete on
`5ca5fde` (Playwright install timed out at 30m and the run was
cancelled); all other checks pass**
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
**currently 4/5, sole finding being this description**
- [ ] I will address all Greptile and reviewer comments before
requesting merge

---

<sub>This description was reformatted to
`.github/PULL_REQUEST_TEMPLATE.md` by the Paperclip PR triage bot. The
code was not modified. Checklist boxes reflect the PR's verifiable state
at commit `5ca5fde`; unchecked items are genuinely outstanding, not
oversights. The **Model Used** section requires input from the author.
The previous description's `LEG-` reference was removed as an internal,
instance-local identifier that the template prohibits.</sub>

---------

Co-authored-by: Lead Backend Engineer Agent <backend1@legacykeeper.io>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
2026-07-22 13:05:28 -05:00
5ed0b74b34 fix(runtime): scope PAPERCLIP_ env-binding strip to reserved keys (#9974)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agent runs get their environment from user/adapter/project/routine
env bindings resolved by the server heartbeat, plus `PAPERCLIP_*`
runtime vars (identity, wake, workspace, API access) injected by the
harness
> - The heartbeat stripped **every** `PAPERCLIP_`-prefixed binding
before resolution, so legitimately user-named keys (e.g. cloud provider
token bindings like `PAPERCLIP_CLOUD_PROD_PROVIDER_RAILWAY_*`) were
silently dropped and never reached the run env
> - At the same time, several adapters honored an explicitly configured
`PAPERCLIP_API_KEY` over the harness-minted run token, which is exactly
the one key config must never control
> - This pull request replaces the blanket prefix strip with a precise
three-rule policy: never accept `PAPERCLIP_API_KEY` from config, always
let harness-assigned runtime vars win, and let every other
`PAPERCLIP_*`-named user binding flow through
> - The benefit is that user secrets with a `PAPERCLIP_`-style name work
like any other binding, while runtime identity and API credentials stay
fully harness-controlled

## Linked Issues or Issue Description

**Bug description** (no public issue exists):

- **What happened:** Env bindings whose key starts with `PAPERCLIP_`
(e.g. a cloud provider token a user deliberately named
`PAPERCLIP_CLOUD_PROD_PROVIDER_RAILWAY_TOKEN`) were silently stripped by
the server before secret resolution, so the spawned agent never received
them. No error, no access event — the variable just never appeared.
- **Expected behavior:** A user-named `PAPERCLIP_*` binding should reach
the run env unless the harness itself uses that key. Only
`PAPERCLIP_API_KEY` should be categorically rejected, and
harness-assigned runtime vars (`PAPERCLIP_RUN_ID`, `PAPERCLIP_AGENT_ID`,
wake/workspace vars, …) should always win over config.
- **Steps to reproduce:** Configure an agent/project env binding named
`PAPERCLIP_<ANYTHING>` (plain or secret_ref), run a heartbeat, and
inspect the spawned process env — the key is absent.
- **Deployment mode:** local server, any local adapter.

Related prior PRs (different, save-time/API-layer blanket-ban approach;
this PR supersedes that direction with a runtime allow-except-reserved
policy): Refs #8239, Refs #8439.

## What Changed

- `server/src/services/heartbeat.ts`: the pre-resolution strip now
removes only `PAPERCLIP_API_KEY` (hard denylist) instead of every
`PAPERCLIP_`-prefixed binding; other `PAPERCLIP_*` keys flow into
binding resolution. Low-trust inline-sensitive-env checks now also cover
those keys.
- `packages/adapter-utils/src/server-utils.ts`: new
`isForbiddenConfigEnvKey()` helper; the shared
`refreshPaperclipWorkspaceEnvForExecution` merge drops
`PAPERCLIP_API_KEY` from config and keeps harness-assigned `PAPERCLIP_*`
keys authoritative.
- `packages/adapter-utils/src/acpx-engine/execute.ts`: removed the
explicit-`PAPERCLIP_API_KEY`-from-config allowance; the run token
(`authToken`) is now always applied; config `PAPERCLIP_API_KEY` is
ignored.
- All local adapters (`claude-local`, `codex-local`, `cursor-local`,
`gemini-local`, `grok-local`, `opencode-local`, `pi-local`) plus
`cursor-cloud`, `hermes`, and the server `process` adapter: removed
`hasExplicitApiKey`-style allowances so the harness token always wins,
and guarded the remaining unguarded env-merge loops (claude-local inline
loop, process adapter) with the same policy.
- Tests updated/added: heartbeat binding-strip test now asserts the
three-rule policy; adapter-utils merge tests assert the
`PAPERCLIP_API_KEY` ban and `PAPERCLIP_*` pass-through; acpx engine
tests moved credential fixtures to `authToken` and assert config
`PAPERCLIP_API_KEY` is ignored while other `PAPERCLIP_*` config keys
forward and still bust the session fingerprint on rotation.

## Verification

- `pnpm vitest run packages/adapter-utils/src/server-utils.test.ts
packages/adapter-utils/src/acpx-engine/execute.test.ts` — 127 passed
- `pnpm vitest run server/src/__tests__/heartbeat-project-env.test.ts
server/src/__tests__/heartbeat-local-environment.test.ts
server/src/__tests__/claude-local-execute.test.ts
server/src/__tests__/codex-local-execute.test.ts
server/src/__tests__/cursor-local-execute.test.ts
server/src/__tests__/gemini-local-execute.test.ts` — 68 passed
- Adapter package execute suites and the server tests touching API-key
fixtures (`heartbeat-run-log`, `redaction`,
`effective-run-config-fingerprints`, `agent-permissions-routes`) —
green. Three pre-existing sandbox/SSH fixture failures reproduce
identically on clean `master` on this host and are unrelated.
- `pnpm --filter <pkg> typecheck` for server, adapter-utils, and all
nine touched adapter packages — all pass.

## Risks

- Behavioral change: a deployment that relied on configuring a static
`PAPERCLIP_API_KEY` in adapter config env loses that override — by
design; the harness-minted run token is now the only source. When no run
token exists, no API key is injected at all.
- `PAPERCLIP_*`-named user bindings now reach binding resolution and run
envs; a key that collides with a harness runtime var is still discarded
at merge time, so runtime identity/wake/workspace vars cannot be
spoofed.
- Low risk otherwise: no migrations, no API surface changes.

## Model Used

- Claude Fable 5 (`claude-fable-5`, Anthropic Claude 5 family,
Mythos-class tier), extended thinking enabled, agentic tool use (file
edits, shell, test runner) via 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
- [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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 07:01:57 -05:00
DottaandPaperclip 7e00f67138 feat(connections): add v3 schema core (#9958)
## Thinking Path

> - Paperclip is the open source control plane people use to manage
AI-agent companies and their governed access to external systems.
> - Connected Apps build on the existing Apps and MCP gateway substrate
so companies can configure reusable, auditable integrations.
> - The current connection record does not yet have a stable public
address, explicit ownership/auth method fields, or subject-specific
credential grants.
> - Without that schema core, later OAuth, per-user authorization, token
brokering, triggers, and connector-service phases cannot enforce tenant
and subject boundaries consistently.
> - This pull request adds the forward-compatible Connections v3 schema
core while preserving the existing connection lifecycle and directly
migrating the remote MCP transport name.
> - The benefit is a company-scoped, least-privilege foundation for
one-click integrations without bypassing Paperclip secrets, profiles,
rules, or audit controls.

## Linked Issues or Issue Description

No matching public issue was found.

**Problem**

Paperclip's current app connections need a durable identity and
authorization substrate before Connected Apps can safely support
multiple setup methods, per-user credentials, provider tenants, and
managed connector services. The existing schema only models a single
connection-level credential set and uses legacy transport terminology.

**Proposed solution**

Add a stable company-scoped connection UID, explicit
ownership/auth/transport fields, a subject-aware `connection_grants`
table, and multi-key credential annotations. Backfill existing
connections and workspace grants in a reversible migration, then update
shared/server/UI contracts to the new `mcp_remote` transport name.

**Related work**

- Related foundation: #9534
- Roadmap: Connected Apps (one-click integrations)

## What Changed

- Added company-scoped connection `uid`, `ownership`, `authKind`, and
canonical transport fields across database, shared contracts,
validators, services, and UI fixtures.
- Added `connection_grants` with workspace/user subject rules, provider
tenant metadata, credential secret refs, revocation state, company
scoping, and uniqueness constraints.
- Added migration `0182_connections_v3_schema_core` to backfill stable
UIDs, rename `remote_http` to `mcp_remote`, infer auth kinds, create
default workspace grants, and support rollback coverage.
- Added multi-key credential annotations and updated gateway/access
services without changing the existing lifecycle behavior.
- Updated the connection glossary, connector playbook, and security
threat model for the new identity, grant, and relay boundaries.
- Added explicit test UIDs to direct database fixtures so the new
non-null invariant is exercised across affected server suites.

## Verification

- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm exec vitest run server/src/__tests__/tool-access-service.test.ts
server/src/__tests__/tool-gateway-service.test.ts
server/src/__tests__/tool-gateway.test.ts
server/src/__tests__/heartbeat-runtime-skills.test.ts
server/src/__tests__/tool-oauth-legacy-backfill.test.ts
server/src/__tests__/tool-access-policy-service.test.ts
server/src/__tests__/heartbeat-runtime-mcp-servers.test.ts
packages/db/src/connections-v3-schema-core-migration.test.ts
packages/shared/src/validators/tool-access.test.ts --config
vitest.config.ts` — 9 files, 218 tests passed.
- Latest-head GitHub Actions: build, typecheck, general/serialized
suites, backup/worktree restore coverage, both e2e shards, canary,
policy, and security scans pass.
- Greptile: 5/5 with zero unresolved threads.
- `pnpm check:token-gates` remains red only on five pre-existing `#9627`
color literals outside this change.

## Risks

- **Migration risk:** UID backfill and default-grant creation touch
every existing connection. The migration uses company-scoped uniqueness,
deterministic legacy UIDs with ID suffixes, and seeded up/rollback
coverage.
- **Authorization risk:** Grant rows carry credential references.
Constraints enforce workspace-vs-user subject shape, company/connection
lookup indexes, one default grant per connection, and one user grant per
connection/subject. Security review is requested specifically for this
design.
- **Compatibility risk:** `remote_http` is renamed directly to
`mcp_remote`; all repository call sites and fixtures are updated in the
same change.
- **Future-phase risk:** Subject-bound token issuance, triggers, and
connector-service relay verification remain fail-closed requirements
documented for later phases; this PR does not expose those capabilities.

> 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 coding agent. The runtime did not expose an exact
underlying model ID or context-window size; capabilities used include
repository inspection, code editing, shell execution, test execution,
Git/GitHub CLI operations, and structured reasoning.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-21 15:16:26 -05:00
1f1f545238 feat: add built-in summarizer and summary slots (#9713)
## Thinking Path

> - Paperclip is the open source control plane people use to organize,
govern, and understand AI-agent work
> - Operators need concise, current status views across projects and
execution workspaces without manually reading every issue and run
> - Paperclip already has auditable issues, documents, built-in agents,
routines, and live run events, but no first-class summary-slot workflow
connecting those systems
> - A built-in Summarizer can generate status prose through ordinary
governed tasks while summary slots provide stable, revisioned
destinations for that output
> - The UI needs to show current summaries, generation progress,
failures, revisions, and streaming draft status in the places operators
already work
> - This pull request adds the end-to-end summary-slot data, API, agent,
orchestration, and UI surfaces behind an experimental setting
> - The benefit is decision-oriented status context that remains
company-scoped, auditable, retryable, and inexpensive by default

## Linked Issues or Issue Description

No public GitHub issue exists for this feature.

**Problem**

Operators currently have to reconstruct project and workspace status by
reading many issues, runs, and comments. This makes it hard to identify
decisions, review queues, recent work, and the next event worth
watching.

**Proposed capability**

Add an experimental summary system with revisioned summary slots for
projects and workspaces, a paused-by-default built-in Summarizer agent,
governed generation tasks, live draft status, and reusable UI cards.

**Expected behavior**

- Summary data remains company-scoped and revisions remain auditable.
- Generation runs through normal issue/agent orchestration and
deduplicates active requests.
- Only the linked built-in Summarizer generation task can author a slot
revision.
- Operators can generate, retry, inspect revisions, and follow draft
progress from project and workspace views.
- The feature remains opt-in and background generation remains paused by
default.

## What Changed

- Added summary-slot schema, idempotent migrations, shared contracts,
validators, API paths, and service tests.
- Added company-scoped summary-slot routes for reading revisions,
requesting generation, and guarded Summarizer writes with activity
logging.
- Added terminal generation finalization, failure reasons, assignment
wakeups, and orchestration integration.
- Added the paused-by-default built-in Summarizer bundle, low-cost
runtime defaults, status-summarization skill, and stale-summary routine.
- Added summary cards, revision selection, retry/configuration states,
live draft streaming, transcript chunk handling, and project/workspace
integrations.
- Updated Claude local parsing for streamed status output and expanded
server, adapter, shared, database, catalog, and UI coverage.

## Verification

- `pnpm -r typecheck`
- `pnpm exec vitest run packages/db/src/summary-slots-schema.test.ts
packages/shared/src/summary-slot.test.ts
server/src/__tests__/summary-slot-routes.test.ts
server/src/__tests__/summary-slots.test.ts
server/src/__tests__/built-in-agents.test.ts
ui/src/components/SummarySlotCard.test.tsx
ui/src/components/SummarySlotCard.status.test.tsx
ui/src/components/useSummaryDraftStream.test.tsx
ui/src/lib/summary-draft-stream.test.ts
ui/src/lib/run-log-chunks.test.ts
ui/src/context/LiveUpdatesProvider.hook.test.tsx` — 113 tests passed
- `pnpm test:run` — server and UI suites passed; one CLI AWS doctor test
was affected by inherited `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`,
and passed when those host credentials were removed
- `pnpm exec vitest run cli/src/__tests__/secrets.test.ts` with
inherited AWS credential variables removed — 8 tests passed
- `pnpm build`
- `pnpm check:token-gates` currently reports nine `#9627` comment
references introduced by current `master`; none are in this PR diff

## Risks

- Database risk is limited by incrementally ordered, idempotent
migrations and migration safety checks.
- Summary generation creates normal issues/runs, so misconfiguration can
produce failed slots; the UI exposes retryable failure reasons and agent
configuration entry points.
- Streaming draft parsing depends on the documented `STATUS:` protocol;
final persisted revisions remain the source of truth.
- The feature is experimental, opt-in, and its built-in routine is
paused with no background token spend by default.

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

## Model Used

- OpenAI GPT-5.3 Codex with reasoning, repository tool use, code
execution, GitHub CLI, and Paperclip control-plane integration. Earlier
branch commits also record Claude model co-authorship where applicable.

## 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 <noreply@anthropic.com>
2026-07-17 11:03:07 -05:00
DottaandPaperclip b07b2994cc feat: stamp responsible users on activity logs (#9731)
## Thinking Path

> - Paperclip is the control plane people use to manage AI-agent
companies and their work
> - The activity log is the generic audit spine for mutations across the
control plane
> - Activity rows identify agents and runs, but they do not persist the
responsible human upstream
> - Relying only on run joins loses attribution after run pruning and
misses agent API-key actions outside a run
> - This pull request resolves responsible-user attribution when each
activity row is written and stores it directly
> - The benefit is durable, queryable agent audit feeds without
rewriting historical provenance

## Linked Issues or Issue Description

### Problem or motivation

Agent activity records do not persist the responsible user, so
attribution can disappear when runs are pruned and no-run API-key
mutations cannot be attributed correctly.

### Proposed solution

Resolve attribution for each new activity row from the run, related
issue, active agent API key, or company default, in that order, and
persist the result directly.

### Alternatives considered

Read-time joins alone were rejected because pruned runs lose durable
attribution and out-of-run agent-key actions have no run to join.
Historical backfill was rejected because it would invent provenance.

### Roadmap alignment

This strengthens the durable audit-trail direction described in
`ROADMAP.md` without adding a new product surface.

## What Changed

- Added nullable `activity_log.responsible_user_id` plus
company/agent/time and company/responsible-user/time indexes.
- Added an idempotent forward-only migration with no historical
backfill.
- Added centralized write-time resolution: heartbeat run → issue
attribution → active agent API key → company default.
- Propagated authenticated API-key IDs through existing request-backed
`logActivity()` calls.
- Added unit coverage for every fallback and an embedded-Postgres
assertion for the no-run API-key stamping path.

## Verification

- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/activity-log-responsible-user.test.ts
src/__tests__/authz-company-access.test.ts`
- Initial focused verification: 25 tests passed; migration safety
passed.
- Follow-up regression verification: 54 focused
attribution/company-skill/environment/issue-tree/tool-gateway tests
passed; server typecheck passed.
- GitHub: full build, typecheck, server shards, serialized suites, e2e,
security, and policy checks passed.

## Risks

- Adding two indexes to an existing large table can hold a write lock
while the transactional migration runs. The migration safety
suppressions document why `CONCURRENTLY` is unavailable under the
current Drizzle migration runner.
- Historical rows remain nullable by design; this avoids inventing
provenance and keeps the migration forward-only.
- API-key attribution requires request-backed activity call sites to
pass the authenticated key ID; this PR mechanically updates the existing
actor-based activity calls and covers the no-run path with integration
testing.

> 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.4`; context-window size was not exposed by the
runtime. Medium reasoning with repository editing, terminal execution,
and test execution capabilities was used.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-16 20:54:55 -05:00
DottaandPaperclip 6ec059ab4e fix(server): suppress stale handoff alarms during live continuation (#9695)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The control plane records a successful-run handoff when productive
work ends without a durable next-step disposition
> - That handoff state was derived only from the latest activity event,
without checking whether a corrective run or wake was currently alive
> - As a result, actively progressing issues could still show a
high-severity missing-disposition alarm and blocked-inbox row
> - The same stale required event could also remain indefinitely when a
later successful run correctly skipped recovery because another valid
continuation path already existed
> - This pull request makes the derived state liveness-aware, suppresses
attention only while the live path exists, and resolves stale required
events on valid-path skips
> - The benefit is that productive work stays calm while genuine stalls
still resurface automatically when liveness disappears

## Linked Issues or Issue Description

- **Bug:** An issue whose latest successful-run handoff event is
`required` continues to report a missing disposition even while a
heartbeat run, scheduled retry, or queued/deferred/claimed wake is
actively targeting that issue.
- **Expected behavior:** The API should expose current continuation
liveness, the blocked inbox should suppress the alarm only while that
path remains live, and a later successful run that skips recovery
because a valid path exists should durably resolve the stale event.
- **Related but distinct:** #9370 changes disposition freshness at
detection time; #8748 adds an explicit policy opt-out. This PR preserves
detection/escalation policy and fixes read-time/current-liveness state.

## What Changed

- Extended `SuccessfulRunHandoffState` with `hasLiveContinuation` and
optional `liveRunId` evidence.
- Added bounded liveness hydration for required handoff states using
active heartbeat-run and wake-request signals.
- Suppressed `missing_disposition` blocked-inbox rows only while a run,
scheduled retry, or live wake targets the issue.
- Added durable `issue.successful_run_handoff_resolved` logging when
handoff detection skips because another valid continuation path owns the
next action.
- Added focused regressions for live/absent derived state, self-healing
attention suppression, valid-path skip classification, and
resolved-event logging.
- Updated UI normalization and fixtures for the shared contract without
changing rendering behavior.

## Verification

- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm vitest run
server/src/services/recovery/successful-run-handoff.test.ts
server/src/__tests__/issue-list-assignee-filter-routes.test.ts
server/src/__tests__/issue-blocker-attention.test.ts` — 56 passed
- `pnpm vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "queues one
finish-handoff wake when a successful run leaves in-progress work
without a next action"` — 1 passed
- `git diff --check`

## Risks

- Low risk: no schema or migration changes, and detection, bounded
correction attempts, and escalation behavior are unchanged.
- Liveness lookups are limited to issues whose latest handoff state is
`required`; blocked-inbox suppression reuses rows already loaded by that
query path.
- Suppression is read-time and self-healing: when the run or wake stops,
the alarm returns on the next fetch.

> 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 using `gpt-5.4`, tool-enabled software-engineering
workflow with repository, shell, test, Git, GitHub, and Paperclip
control-plane access. Context-window size is not exposed by this
runtime.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-16 10:49:47 -05:00
DottaandPaperclip c65ab09d9f fix(recovery): wait for provider quota resets (#9635)
## Thinking Path

> - Paperclip is the open source control plane people use to manage
AI-agent companies and keep assigned work moving safely.
> - The recovery subsystem decides whether a failed agent run should
retry, wait, block for configuration, or escalate to another owner.
> - Provider usage-limit failures currently arrive as generic
`adapter_failed` results, so stranded-work reconciliation can create
takeover recovery even when the provider states that capacity will reset
later.
> - Credential and model lookup failures are also configuration
problems, not evidence that another agent should take over the task.
> - This pull request classifies those failure families at recovery time
and persists the classification on the run.
> - Quota failures now schedule a monitor for the original assignee at
the parsed reset time, or after a bounded default backoff when no reset
time is available.
> - The benefit is that transient provider capacity waits no longer wake
recovery owners, while configuration failures stop with an actionable
classification.

## Linked Issues or Issue Description

No public GitHub issue exists for this exact change.

**What happened?** When an assigned issue's latest run failed with a
provider usage-limit message such as "try again at 12:00 AM (UTC),"
recovery treated the run as generic `adapter_failed` work and could
create a takeover action. Missing credentials and `model_not_found`
failures followed the same generic path.

**Expected behavior:** Provider quota failures should keep the original
assignee and schedule a monitor for the reset time, without creating
recovery work or immediately waking another owner. Missing credentials
and model lookup failures should be classified as
`configuration_incomplete` and blocked with the configuration fix
recorded.

**Steps to reproduce:**
1. Assign and start an issue for an agent.
2. Record a failed heartbeat run with `errorCode: adapter_failed` and a
provider quota/reset message.
3. Run stranded assigned-issue reconciliation.
4. Observe that the old behavior routes the issue through generic
recovery instead of waiting for provider capacity.

Reproduced on `master` at `9af96461d`. This is a core recovery bug, not
adapter-specific, and applies to built-from-source deployments with
either embedded PGlite or Postgres.

Related work checked: #9288 adds adapter-side Claude provider-limit
classification; #5392 suppresses some recovery creation for quota-class
errors; #9634 is a broader recovery-routing change with overlapping
provider-quota behavior. This PR is the narrow recovery-service fix with
focused parsed-reset, fallback-backoff, zero-takeover, and
configuration-failure coverage.

## What Changed

- Added conservative recovery-time classification for provider quota,
missing-credential, and model-not-found adapter failures.
- Parsed provider reset timestamps with a default one-hour backoff when
no usable reset time is present.
- Persisted `provider_quota` or `configuration_incomplete` metadata on
the failed heartbeat run.
- Scheduled quota monitors for the active issue owner, including the
current review participant, without creating recovery actions or
enqueueing takeover wakes.
- Routed configuration failures to blocked recovery with actionable
evidence instead of a takeover.
- Added unit and embedded-database regression coverage for
parsed/fallback quota timing, zero CTO/recovery wake behavior, and
configuration classification.

## Verification

- `pnpm exec vitest run
server/src/services/recovery/provider-failure-classification.test.ts
server/src/__tests__/issue-recovery-actions.test.ts
server/src/__tests__/issue-monitor-scheduler.test.ts
server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts` — 4
files passed, 66 tests passed.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.

## Risks

- Recovery behavior changes for text-matched adapter failures; matching
is intentionally conservative, and unmatched failures retain the
existing generic recovery path.
- Provider reset strings do not always include a date or timezone;
parsing chooses the next future matching time and falls back to a
one-hour wait when the timestamp is unusable.
- This overlaps the provider-quota portion of broader recovery-routing
PR #9634, so only one implementation should land if both remain open.
- No schema, migration, API contract, or UI changes are included. No
documentation update is needed because this corrects internal recovery
behavior without changing operator commands or configuration.

> 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 `gpt-5.4`, medium reasoning, tool use, and
code execution. The runtime does not expose its configured
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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-16 09:24:38 -05:00
DottaandPaperclip 85404b46c5 fix(server): throttle serial recovery repeats (#9651)
## Thinking Path

> - Paperclip is the open source control plane people use to coordinate
AI-agent companies.
> - Its recovery services create productivity reviews and liveness
escalations when work stops making progress.
> - Existing uniqueness guards prevent concurrent duplicates, but
terminal recovery tasks can still be recreated serially without enough
time for conditions to change.
> - That creates noisy review churn for persistently stalled issues and
immediate liveness re-escalation after a recovery task closes.
> - This pull request adds bounded, configurable cooldown and no-action
suppression behavior to those two recovery paths.
> - The benefit is quieter recovery automation that still resumes
automatically after source activity or cooldown expiry.

## Linked Issues or Issue Description

### Pre-submission checklist

- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I can reproduce this behavior on `master`.
- [x] I have confirmed the behavior originates in Paperclip core
recovery orchestration, not an adapter, provider, or local
configuration.

### What happened?

Recovery reconciliation can serially recreate equivalent system-origin
tasks after previous tasks become terminal. Productivity reviews allowed
multiple creations for the same source issue within a rolling day, and a
closed liveness escalation could be recreated immediately for the same
incident or recovery leaf.

### Expected behavior

Productivity review creation should be limited to once per rolling 24
hours, repeated completed reviews that produced no source action should
eventually suppress further creation until activity resumes, and
recently terminal liveness escalations should receive a short cooldown
before recreation.

### Steps to reproduce

1. Create a stalled assigned issue that meets productivity-review
eligibility.
2. Complete repeated productivity-review tasks without adding
source-issue activity, then reconcile again within 24 hours.
3. Create and close a liveness escalation for a blocked issue graph,
then immediately reconcile the same graph.
4. Observe that equivalent system tasks can be recreated serially
without a meaningful state change.

### Paperclip version or commit

`5588ddf68175eea448f9d19677b97d7393c38c3d` (`master` when reproduced)

### Deployment mode

Local dev (`pnpm dev`)

### Installation method

Built from source (`pnpm dev` / `pnpm build`)

### Agent adapter(s) involved

- [x] Not adapter-specific (core bug)

### Database mode

Embedded PGlite (default — `DATABASE_URL` unset)

### Access context

Unclear / not applicable

### Node.js version

Current repository-supported Node.js runtime.

### Operating system

Linux development environment.

### Relevant logs or output

No error is emitted; the bug is repeated task creation visible in
persisted issue history.

### Relevant config (if applicable)

No special configuration is required.

### Additional context

The concurrent/open-task uniqueness guards work as designed; this change
targets serial repeats after matching tasks become terminal.

### Privacy checklist

- [x] I have reviewed all pasted output for PII and redacted where
necessary.

## What Changed

- Tightened the productivity-review creation cap to one review per
source issue in a rolling 24-hour window.
- Added configurable suppression after three consecutive completed
reviews with no source-issue activity, with automatic reset when source
activity occurs.
- Added a configurable one-hour default cooldown for matching terminal
liveness escalations.
- Exposed the liveness reconciliation clock/cooldown inputs for
deterministic orchestration tests.
- Added focused tests for daily enforcement, no-action suppression and
reset, and cooldown expiry.

## Verification

- `pnpm exec vitest run
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts` — 36
tests passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- Confirm the focused tests demonstrate creation after source activity
and after the liveness cooldown expires.

## Risks

- Low-to-moderate behavioral risk: recovery tasks intentionally appear
less often, so overly aggressive thresholds could delay intervention for
a persistently stalled issue.
- Thresholds are configurable through reconciliation inputs, and source
activity resets productivity-review suppression.
- No database migration, public API change, telemetry contract change,
or UI behavior change.

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

## Model Used

- OpenAI Codex using GPT-5.3-Codex, with reasoning, repository/terminal
tool use, code execution, and test execution. 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>
2026-07-16 09:22:50 -05:00
DottaandPaperclip 992389480a fix(server): restore hot-restart run adoption (#9647)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The local heartbeat/runtime subsystem starts long-running local
agent processes and records their run state.
> - Operators sometimes need to rebuild and restart the Paperclip server
while local agent processes are still alive.
> - A normal restart should remain conservative, but a guarded
production hot restart needs an explicit marker, startup reconciliation,
and an inspectable report.
> - The broader hot-restart PR is currently merge-conflicted, so this
pull request lands the minimal server-side recovery path on current
`master`.
> - The benefit is that deploy operators can restart from a current
branch without reverting production changes and without marking adopted
live runs as `process_lost`.

## Linked Issues or Issue Description

No public GitHub issue exists for this deploy-safety fix.

Bug fix:

- What happened: the current deployable `master` branch did not include
the hot-restart marker CLI, startup adoption report path, or health
version proof needed by guarded service restarts.
- Expected behavior: a deploy operator can write a one-shot marker
before restarting, the old server snapshots eligible running child
processes, the new server reports adopted/finalized/lost runs, and
adopted live runs are not reaped as `process_lost`.
- Steps to reproduce: restart a server with running local child-process
heartbeat runs without the marker/adoption path; startup orphan reaping
has no adoption metadata and treats live detached children as lost.
- Paperclip version/commit: fixed on top of `master` at `b606869a6`.
- Deployment mode: production/local-service style deployments that
rebuild and restart the primary `paperclip.service`.
- Related PR: Refs #9628. This PR intentionally lands a smaller
deploy-safe subset because #9628 is currently merge-conflicted.
- Duplicate search: searched public PRs/issues for `hot restart` and
`process_lost adoption`; #9628 is the directly related prior
implementation.

## What Changed

- Added `scripts/request-hot-restart.ts` to write a one-shot hot-restart
intent marker under `PAPERCLIP_HOME`.
- Added `server/src/services/hot-restart.ts` for intent/report path
resolution, parsing, atomic writes, shutdown snapshots, and marker
cleanup.
- Wired server shutdown/startup so explicit hot restarts snapshot active
runs, skip the normal heartbeat drain, reconcile live child processes on
boot, and write `hot-restart-report.json`.
- Preserved adopted run metadata so normal orphan reaping does not
regress adopted live runs to `process_lost`.
- Added `serverVersion` health proof alongside existing `version`, plus
docs and regression coverage.

## Verification

- `pnpm vitest run server/src/__tests__/health.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts` — 2 files
passed, 100 tests passed.
- `pnpm --filter @paperclipai/server typecheck`
- `env PAPERCLIP_HOME="$PAPERCLIP_RUN_SCRATCH_DIR/hot-restart-cli-smoke"
pnpm --filter @paperclipai/server exec tsx
../scripts/request-hot-restart.ts --server-pid 12345`
- Branch ancestry checked after `git fetch origin master`:
`origin/master` was `b606869a6`, and `HEAD..origin/master` was empty.

## Risks

- Medium risk: process adoption depends on PID/PGID metadata and the
service manager leaving child processes alive for the guarded restart.
- Normal restarts remain conservative, but an incorrect marker PID
intentionally falls back to graceful drain instead of adoption.
- The PR is server-only and does not include the broader
UI/experimental-setting work from #9628.

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

## Model Used

- OpenAI GPT-5 via Codex coding agent in a Paperclip execution
workspace; tool use and shell/code execution enabled; context window not
surfaced by this runtime.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-16 02:46:09 -05:00
DottaandPaperclip 4f9894df44 fix(server): bound accepted-interaction continuation recovery (#9656)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Heartbeat recovery keeps assigned issues moving when a run or
continuation path disappears
> - Accepted issue-thread interactions can create a continuation wake
after an agent previously parked for review
> - The recovery sweep could requeue that accepted-interaction wake
while the queued-run gate cancelled it using the older pre-acceptance
park summary
> - That cancellation path had no bound, so recovery could repeat the
same wake and cancellation indefinitely
> - This pull request makes accepted-interaction evidence supersede the
older park and caps repeated recovery cancellations at three attempts
> - The benefit is that accepted work resumes normally, while genuine
repeated failures become a visible dependency wait or escalation instead
of a cancel loop

## Linked Issues or Issue Description

Refs #9331

The accepted-interaction continuation recovery added by #9331 can
encounter a stale continuation summary written before approval. The
sweep requeues a continuation carrying the accepted interaction
timestamp, but queued-run invalidation cancels it because the older
summary says to wait for review. Recovery then sees the accepted
interaction without a successful run and requeues again. This PR
prevents that stale-summary cancellation and adds a bounded fallback if
three equivalent cancellations have already occurred.

## What Changed

- Let queued continuation wakes with a parseable `interactionResolvedAt`
bypass a pre-acceptance waiting-for-review park summary.
- Count consecutive unsuccessful continuation runs for the same issue
and agent since interaction acceptance; after three review-park
cancellations, convert a real dependency wait or use the existing
visible escalation path.
- Add focused regression coverage for the park bypass, unchanged
non-interaction park behavior, below-cap requeue, cap escalation, and
successful-run skip.
- Document the accepted-interaction precedence and bounded requeue
contract in execution semantics §9.2.

## Verification

- `pnpm vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "accepted
interaction continuation recovery|accepted interaction recovery after
its continuation succeeds|requeues accepted interaction continuations
stranded"`
- `pnpm vitest run
server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts -t
"pre-acceptance review park|continuation summary parks executor work"`
- `pnpm --filter @paperclipai/server typecheck`

## Risks

- Low risk: the park bypass only applies when the queued context
contains a parseable interaction resolution timestamp.
- The retry bound is scoped to unsuccessful `issue_continuation_needed`
runs for the same company, issue, agent, error code, and post-acceptance
time window.
- No schema, migration, API, or UI 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

- OpenAI Codex, exact model ID `gpt-5.5`, high-reasoning coding mode
with repository tool use and command execution; context-window size was
not exposed by the runtime.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-16 02:34:20 -05:00
DottaandPaperclip d32ed88443 fix(recovery): route recovery by failure cause (#9634)
## Thinking Path

> - Paperclip is the open source control plane people use to coordinate
AI-agent companies and their work.
> - Its recovery subsystem detects stranded issue execution and decides
whether to retry, escalate, or request operator intervention.
> - The existing recovery path used a mostly generic owner ladder and
generic execution contract, so transient failures could wake a manager
who then performed the deliverable instead of repairing and returning
the task.
> - Provider quota failures also entered the same takeover path even
when the correct action was to wait for capacity and retry the original
assignee.
> - Recovery actions already retain the source owner and evidence needed
to choose a cause-specific route, render a scoped contract, and measure
whether work was handed back.
> - This pull request adds a cause-keyed recovery playbook, propagates
its contract through every built-in adapter, and makes resolved recovery
actions return work to the original owner by default.
> - The benefit is bounded self-recovery that preserves task ownership,
avoids needless management takeover, and makes recovery outcomes
observable.

## Linked Issues or Issue Description

No matching public GitHub issue was found.

Related recovery work was reviewed but is not duplicated here: #9630
restores bounded recovery continuations, #8807 changes one
assignee-ranking case, and #9404 records runtime-failure transition
evidence. This change instead introduces cause-specific routing and
recovery contracts across the recovery lifecycle.

### What happened?

When an issue became stranded, recovery generally selected an owner
through the same fallback ladder and rendered the normal execution
contract. That made the recovery wake look like ordinary deliverable
work, even when the correct action was to retry the original agent,
repair its runtime, or wait for a provider quota reset.

### Expected behavior

Recovery should select a response by failure cause, tell the recipient
to recover rather than complete the deliverable, suppress takeover wakes
for provider quota waits, and return repaired work to its original
assignee unless the recovery owner explicitly completes it.

### Actual behavior

Recovery could escalate transient failures to management, omit the
cause-specific next action from the wake, and leave the recovery owner
assigned after the runtime problem was resolved.

### Impact

The generic path creates avoidable management work, ownership churn, and
budget consumption while obscuring whether recovery successfully
returned work to the responsible agent.

## What Changed

- Added cause-keyed routing for process loss, missing disposition,
provider quota limits, Codex output inactivity, workspace validation
failures, and fallback recovery causes.
- Added recovery-scoped wake rendering that replaces the generic
execution contract with the failure summary, original assignee, attempt
count, next action, and cause-specific playbook instruction.
- Propagated the structured recovery contract through all built-in
adapter execution paths, including Hermes local and gateway adapters.
- Added provider-quota wait monitoring so capacity failures schedule the
original assignee instead of enqueueing a takeover wake.
- Added hand-back behavior and `handed_back` / `owner_completed` outcome
accounting when recovery actions are resolved.
- Added focused routing, renderer, quota-monitor, and hand-back
regression coverage plus implementation-spec documentation.

## Verification

- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-workspace-branch-containment.test.ts
server/src/__tests__/issue-recovery-actions.test.ts`
  - 4 test files passed; 194 tests passed.
- Targeted `pnpm --filter ... typecheck` across
`@paperclipai/adapter-utils`, `@paperclipai/shared`,
`@paperclipai/server`, `@paperclipai/ui`, and all nine changed adapter
packages.
  - 13 affected workspace packages passed typecheck.
- `pnpm check:token-gates`
  - All UI token gates passed.

## Risks

- Recovery routing behavior changes for stranded work, so an incorrectly
classified cause could select a different recipient than before;
fallback causes retain the existing management ladder.
- Provider quota detection depends on structured failure evidence and
conservative text matching; unmatched failures continue through fallback
recovery.
- Adapter prompt plumbing changes across built-ins, covered by shared
renderer tests and compile-time call signatures.

> 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 exact model ID `gpt-5.6-sol`, using reasoning, tool
use, and code execution. The runtime does not expose its configured
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>
2026-07-15 20:04:42 -05:00