Commit Graph
6704 Commits
Author SHA1 Message Date
Dotta a0c5ad9805 test(evals): scope vault AWS credentials to selected server 2026-10-27 01:31:00 -05:00
Dotta 717974e7f7 test(evals): add finite private public-origin sign-in operation 2026-10-27 01:19:25 -05:00
Dotta 7813e24256 Permit one recorded revalidation after qualifying identity invalidation 2026-10-27 01:15:48 -05:00
Dotta 589c90df67 Bind computer tests to saved Morgan runtime and preserve configuration 2026-10-27 01:15:21 -05:00
Dotta 522c5f1bec test(evals): author actual Slack file transfer and size phases 2026-10-27 01:15:20 -05:00
Dotta 9aa09b51c7 Author actual operator computer qualification journeys 2026-10-27 01:09:33 -05:00
Dotta 19510abe5e test(evals): retain missing Grok HTTPS harness binding gap 2026-10-27 01:04:45 -05:00
Dotta 28abc602be test(evals): require genuine existing Bot authorization and challenge 2026-10-27 01:01:36 -05:00
Dotta 1bb90f12cf test(evals): add finite protected Grok routine form entry 2026-10-27 01:01:18 -05:00
Dotta 0af670575d test(evals): calibrate review negation and format new cases 2026-10-27 01:01:18 -05:00
Dotta eeef850419 test(evals): close scoped tool review outcome branches 2026-10-27 00:55:31 -05:00
Dotta 7c77384469 test(evals): require real owned cloud choices before hire 2026-10-27 00:51:51 -05:00
Dotta 325af67e3d Qualify exact Docker Desktop private bind translation 2026-10-27 00:43:54 -05:00
Dotta c1d36080ac test(evals): require genuine saved target authentication failure 2026-10-27 00:43:18 -05:00
Dotta 5c40b00e04 test(evals): author least-scope native secret delivery 2026-10-27 00:42:43 -05:00
Dotta 2f2ecc8269 test(evals): author real human Claude runner conversion 2026-10-27 00:37:21 -05:00
Dotta 772c29dad9 Add explicit owned Docker PostgreSQL E2E lane 2026-10-27 00:35:32 -05:00
Dotta a7373c02dd test(evals): author own personal credential delivery 2026-10-27 00:32:20 -05:00
Dotta 7244c70f11 test(evals): reconcile real account setup requests before resuming 2026-10-27 00:28:33 -05:00
Dotta 3e75b69e05 test(evals): return verified gateway prerequisite receipt 2026-10-27 00:25:11 -05:00
Dotta 41ebbb1272 test(evals): author scoped gateway error diagnosis 2026-10-27 00:24:29 -05:00
Dotta b81b95b36e test(evals): author missing Claude account and real hire resumption 2026-10-27 00:20:51 -05:00
Dotta 7986c118ef test(evals): independently verify secret proposal denial and approval 2026-10-27 00:15:49 -05:00
Dotta a097298ed8 test(evals): bind Claude target to actual Environment contract 2026-10-27 00:10:30 -05:00
Dotta 0d0a7defbe Bind partial archive replies to confirmed outcome counts 2026-10-27 00:04:02 -05:00
Dotta 270035738e test(evals): author real Claude hire and private qualification 2026-10-27 00:03:22 -05:00
Dotta 84e807f546 test(evals): preserve delegated work across personal grant revocation 2026-10-27 00:02:01 -05:00
Dotta 6d9865068a Reconcile archive transport and 210 authored catalog cases 2026-10-26 23:58:56 -05:00
Dotta 4043cf8984 Add owned native archive fault transport lifecycle 2026-10-26 23:54:28 -05:00
Dotta caff11daf6 test(evals): release post-stop gate only after cleanup proof 2026-10-26 23:50:53 -05:00
Dotta 22064ecbd5 test(evals): enforce actual addressed connection resolver identity 2026-10-26 23:50:08 -05:00
Dotta 83d06d6f6b test(evals): author same-source post-stop native continuation 2026-10-26 23:47:29 -05:00
Dotta b117aabd21 test(evals): independently render retrieved Slack launch artifacts 2026-10-26 23:45:59 -05:00
Dotta 8c1cbee398 test(evals): author real Slack thread handoff and isolation case 2026-10-26 23:41:13 -05:00
Dotta eb10c18bee test(evals): guard real native Stop canonical boundary 2026-10-26 23:40:30 -05:00
Dotta 819c1622d1 Reconcile campaign metadata and preserve startup diagnosis 2026-10-26 23:35:32 -05:00
Dotta 05b29a381c test(evals): bind one genuine Slack distractor setup exchange 2026-10-26 23:35:15 -05:00
Devin FoleyandPaperclip 0cb3fc7db5 fix(runner): retain ACPX transport failure details (#17837)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip Runner reports provider failures to the execution and
recovery services.
> - Those services read the terminal event's structured `error` field.
> - The ACPX transport-failure path wrote its code and message only at
the top level.
> - The session facade therefore replaced that cause with `error: null`
and the generic message "Provider turn failed".
> - This pull request uses the existing structured error contract while
retaining the old fields.
> - Recovery can retain the actual redacted cause without reopening the
failed provider or assuming a retry is safe.

## Linked Issues or Issue Description

**What happened?**

A native ACPX run can lose its failure details between runnerd and the
session facade. The transport-failure producer emits a failed turn with
top-level `code` and `message`. The facade reads `error`. The server
receives a generic provider failure and cannot use the original cause to
explain or classify recovery.

**Expected behavior**

Retain the transport code and redacted cause in the structured terminal
error. Keep failed sessions closed, provider termination unproven, and
automatic replay disabled.

**Steps to reproduce**

1. Open a native ACPX session and start a turn.
2. Send malformed activity from the sidecar after it acknowledges the
turn.
3. Poll the native provider events.
4. Observe that `turn.failed.error` is absent even though the producer
has a transport code and cause.

Refs: #16366, #17543. Those changes cover legacy process-session
diagnostics and session-ensure errors. This change repairs the native
ACPX polling terminal contract.

## What Changed

- Add the existing transport code and redacted message to the terminal
event's structured `error` field.
- Mark that error as non-recoverable. Keep the previous top-level fields
for existing readers.
- Add a real sidecar fault fixture and a native-provider regression. It
checks one failed terminal, retained error details, a closed session,
and no provider reopening.
- Document the failure contract and verification scope in execution
semantics and the recovery map.

## Verification

- Red: the new fault test failed because the structured transport error
code was null on the original code.
- Green: all 26 native-provider integration tests pass with the fix.
- Three session-facade terminal tests and three native-runtime
provider-failure classification tests pass.
- `pnpm -r typecheck` and `pnpm build` pass.
- Hosted test partitions, native Runner checks, typecheck, build,
browser shards and canary passed. Two final aggregate jobs received no
runner for several minutes. The first CI attempt was cancelled after
saving its status, and only those two cancelled jobs were resubmitted.
Both aggregate jobs passed on attempt 2. The current head has 55
successful checks, four expected skips and successful Snyk status.
- `git diff --check`, `node scripts/check-module-boundaries.mjs`, and
`node scripts/check-maps.mjs` pass.
- The full local Vitest suite is not run for this patch. The local
filesystem fell below 500 MiB free during verification. The preceding
full-suite attempt crashed embedded PostgreSQL; focused checks and CI
are used without claiming that attempt passed.
- Live incident: verified the exact predecessor sandbox was destroyed
and workspace recovery completed. An audited user retry through the
normal task-retry service started one new native run. That attempt
preserved the original question but failed before provider startup with
a missing workspace-sync descriptor binding. The startup race is fixed
separately in #17838. A later audited retry completed successfully,
saved its useful answer and restored its workspace. The agent API
handoff failed; an audited operator repair through the normal issue
service completed the required user assignment in Review without
repeating the comment or provider work. This verifies recovery admission
on the existing deployment; the new Runner change is not deployed yet.
- Evidence limit: the historical terminal had `error: null` and its raw
sidecar frame was not retained. Fault injection proves this producer
defect and the same loss of diagnostics. It does not establish which
transport fault caused the historical turn to end.

## Risks

- Recovery now receives a specific transport failure code. The error
remains non-recoverable and `providerTerminalObserved` remains false.
Stop proof, effect reconciliation, workspace preservation, retry limits,
and normal execution authority still govern any successor.
- This change preserves diagnostics. It does not make a malformed or
lost provider connection healthy.
- No schema migration, credentials, provider selection, or model
settings change.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, source inspection, tool use,
and code execution. The runtime does not expose the exact model ID or
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-10-26 21:34:29 -07:00
Devin FoleyandPaperclip 94819b6ded fix(recovery): preserve live heartbeat preparation ownership (#17838)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Heartbeat prepares each run before it starts the native provider.
> - Startup recovery can run while that preparation is still active.
> - The native input and coordinator exist before workspace
synchronization saves its descriptor.
> - Recovery checked the later native executor registry but missed the
earlier heartbeat execution registry.
> - It could claim the live run and dispatch a second executor against
incomplete workspace state.
> - This change gives the recovery claim the existing live heartbeat
registry and checks it before changing ownership.

## Linked Issues or Issue Description

**What happened?**

Startup recovery can claim a native run that the same server is still
preparing. The second execution reads the admitted native input before
workspace synchronization has saved its descriptor. It fails with
`native_workspace_sync_descriptor_binding_missing`. The original
preparation then finds a failed run and cannot start the provider.

**Expected behavior**

Leave live preparation with its current executor. Do not change its
native coordinator or dispatch another executor. An inactive run must
still pass the existing recovery checks.

**Steps to reproduce**

1. Start heartbeat runtime selection for a remote sandbox.
2. Hold workspace synchronization after native input and coordinator
persistence.
3. Run native startup recovery with the heartbeat execution still
active.
4. Observe an unintended `bootstrap_incomplete` claim and a second
execution.

Refs #17439 for broader remote controller recovery. This change repairs
the current heartbeat startup race. It does not add checkpoint storage
or remote adoption. A separate diagnostics correction is in #17837.

## What Changed

- Pass the existing heartbeat live execution registry to native recovery
claims.
- Check the current registry after locking the run and before any
takeover or coordinator change.
- Return the existing waiting disposition for live preparation. Keep
workspace descriptor validation unchanged.
- Add a PostgreSQL regression at the real runtime-selection boundary and
an assembled heartbeat recovery regression with the real claim service.
- Document the preparation interval and its recovery behavior.

## Verification

- Red: the new runtime-selection test observed an unintended bootstrap
claim on the original code. The heartbeat regression also failed because
recovery dispatched the live run.
- Green on current head `88bec51f10d61eb851eec849e82a80bb198555c7`: all
144 runtime-selection, heartbeat recovery, workspace-preparation and
restart-policy tests pass.
- These cases check an unchanged coordinator, no duplicate recovery
dispatch, a running source run, and normal recovery after the execution
leaves the registry.
- The full native restart integration file passes all 22 available
cases. Nine real-runner process cases are skipped because local Runner
test binaries were removed to recover disk space. The passing cases
include remote startup, old preparation fencing, ownership conflicts,
Stop, and concurrent recovery.
- Direct full server TypeScript check passes: `pnpm exec tsc --noEmit -p
server/tsconfig.json`. Hosted CI passes on this head, including full
test partitions, typecheck, build, native Runner checks, serialized
suites, browser shards, canary and both aggregates. Apex is 5/5 with no
actionable findings. The current head has 54 successful checks, two
expected skips and successful Snyk status.
- `git diff --check`, module boundary checks, and map checks pass.
- A wider local safety run stopped with PostgreSQL error 53100 and
ENOSPC after 64 tests passed. Recursive typecheck also stopped with
ENOSPC. These attempts did not pass. Generated files in this worktree
were removed to free space. The full local test suite and build have not
been run for this change; hosted CI must supply those results.
- Live evidence: a production startup recovery claimed a new run during
outward workspace staging. The second preparation failed before provider
startup. Its exact sandbox was later destroyed with successful cleanup.
The patch is not deployed. A later audited retry on the existing
deployment completed successfully and saved the requested answer once.
An audited operator repair through the normal issue service completed
the final user assignment in Review after the agent API handoff failed.
This acceptance verifies recovered work, not deployment of the new
guard.

## Risks

- A live heartbeat remains responsible for its own completion and
cleanup. Recovery must not compete with it. The registry is
process-local; existing durable ownership checks still govern other
controllers.
- An executor that is still active remains protected until its cleanup
removes its registry entry. This adds no timer, polling loop, schema or
credential change.
- Missing or corrupt workspace descriptors still fail closed. This
change does not authorize replay of unknown provider actions.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, source inspection, tool use
and code execution. The runtime does not expose the exact serving model
ID or 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-10-26 21:34:15 -07:00
Dotta 9d850991d9 test(evals): prepare owned real Slack endpoint fixtures 2026-10-26 23:32:28 -05:00
Dotta e4b62f48aa test(evals): author remaining internal catalog journeys 2026-10-26 23:30:48 -05:00
Dotta e670ad77ab test(evals): bind scoped Slack identity and audited AgentMail recipient 2026-10-26 23:20:40 -05:00
3edba11576 fix(github): stop counting suspended installations as repository access (#17343)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents get GitHub access through a managed GitHub App connection.
**Refresh access** reads the installations and repositories that the
user token can reach.
> - GitHub keeps listing a suspended App installation and all of its
repositories for the user token. But the token then loses write access
and cannot read private repositories.
> - `loadGitHubGrantMetadata` did not read `suspended_at`. It counted a
suspended installation as live access ("all repos") and set the
connection to healthy.
> - So the board showed "connected, all repositories" while agents got a
403 on `git push` and a 404 on private repositories. The suspend webhook
marks the connection unhealthy, but the next refresh or reconnect
reverted that.
> - This pull request skips suspended installations. When all
installations are suspended, Refresh access fails with a clear error and
a link to the installation settings page, where an owner can click
**Unsuspend**.
> - The benefit is that the board shows the real access state, and
operators get a direct path to fix it.

## Linked Issues or Issue Description

**What happened?**
A GitHub App installation on an organization was suspended. The
Paperclip GitHub connection still showed "connected" with access to all
repositories after **Refresh access**. Agents that used the managed
token got `Permission to <org>/<repo>.git denied` (403) on push,
`Resource not accessible by integration` on branch creation, and 404 on
private repositories.

**Expected behavior**
Refresh access must not count a suspended installation as repository
access. When every installation is suspended, Refresh access must fail
with a message that says the installation is suspended, and the page
must link to the installation settings page.

**Steps to reproduce**
1. Connect GitHub to Paperclip through the GitHub App, with an
installation on an organization.
2. On GitHub, suspend that installation (Organization settings → GitHub
Apps → Configure → Suspend).
3. In Paperclip, click **Refresh access** on the GitHub connection.
4. See that the connection stays healthy and reports all repositories,
while agent pushes fail with 403.

**Paperclip version or commit**
`master` at a708a1333.

**Additional context**
`GET /user/installations` returns the suspended installation with
`suspended_at` set and `repository_selection: all`, and `GET
/user/installations/{id}/repositories` still lists its repositories.
Unsuspending the installation restored push access and private reads for
the same token.

## What Changed

- `server/src/services/tool-access/github-access-discovery.ts`:
`loadGitHubGrantMetadata` skips installations that have `suspended_at`
set. It does not add their IDs, owners, repository selection, or
repositories. When there is no usable installation and at least one is
suspended, it throws `github_installation_required` with a "suspended"
message. `managementUrl` points at the suspended installation's settings
page (or the generic installations page when more than one is
suspended).
- `server/src/services/tool-access/catalog/health.ts`:
`checkConnectionHealth` (the Refresh access path) keeps
`installationUrl` and `managementUrl` when it rebuilds a
`github_installation_required` error. Before, the links were dropped.
- `server/src/services/tool-access/catalog/failures.ts`:
`github_installation_required` keeps its own code and returns 422.
Before, it was folded into a generic `paperclip_error` with HTTP 502.
- `ui/src/pages/apps/app-detail/AccessRetryBanner.tsx` (new): the
attention banner with the saved health message and **Retry access**.
After a failed Refresh access with `github_installation_required`, it
shows an **Open installation settings on GitHub** link (https
`github.com` URLs only).
- `ui/src/pages/apps/AppDetail.tsx`: uses `AccessRetryBanner`, refetches
the connection when a refresh fails so the banner shows the saved
message, and keys the page on `connectionId` so a recovery link never
carries over to another connection. The file stays under 1,000 lines.
- Tests:
- `server/src/__tests__/github-grant-metadata.test.ts`: a mix of
suspended and active installations, and an account where all
installations are suspended.
- `server/src/__tests__/github-health-recovery.test.ts`: drives
`checkConnectionHealth` with a suspended-installation GitHub response
and checks the 422, code, and recovery links.
- `ui/src/pages/apps/AppDetail.test.tsx`: a failed Retry access renders
the installation settings link, and navigating to another cached
connection does not show it.

## Verification

- Red/green: with the source changes reverted and the tests kept, the
new tests fail (suspended installation counted as `all`; recovery link
missing from the health-check error; link not rendered; link leaking to
another connection). With the changes, they pass.
- `server`: `github-grant-metadata`, `github-health-recovery`,
`tool-access-service`, `github-operation-credentials`,
`tool-access-oauth-protocol.integration`, `tool-connection-removal`,
`tool-connection-visibility`, and the other `github-*` / `tool-access-*`
suites pass locally (630 tests).
- `ui`: `AppDetail.test.tsx` passes (78 tests). `ui` `tsc --noEmit`
shows no errors in changed files.
- Not run locally: the server `tsc`. The sandbox has a 4 GB memory
limit, and the server typecheck is killed there even on clean `master`.
CI ran the full typecheck and build, and both pass.
- CI: all gates green. One unrelated `workspace-recovery.test.ts` poll
timeout in a server shard passed on re-run.
- Live check: after the suspended installation was unsuspended, a
managed-token `git push --dry-run` succeeded and a private repository
returned 200.

## Risks

- Low risk. The change only affects installations that GitHub marks as
suspended. Active installations behave as before.
- A connection that has only suspended installations now fails Refresh
access with `github_installation_required` (422) and does not report
fake access. This is the intended behavior.
- The recovery link only renders for https `github.com` URLs.
- Keying the app page on `connectionId` remounts it when moving between
connections, which resets per-page state (for example an open rename
field). That reset is intended.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through the
Claude Code agent SDK with tool use (shell, file edit, 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

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

---------

Co-authored-by: devinfoley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:16:40 -07:00
Dotta 85f49cb351 test(evals): add signed disposable Slack relay for genuine replay proof 2026-10-26 23:11:36 -05:00
Dotta 83ed9d1477 test(evals): require actual unsupported Slack file phase after transfer 2026-10-26 23:10:33 -05:00
TonioandClaude Fable 5.1 3e7c3ee9a9 fix(ui): fall back to the icon face when an Artifacts V1 preview fails to load (#17260)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents produce artifacts: documents, files, pictures, video and
links. The Artifacts V1 presentation (`enableArtifactsV1`, #17228) shows
them as prints, sheets and rows on the Artifacts page, in the panel's
Artifacts tab and in the task thread
> - A picture is printed from its attachment's content path. When that
content cannot be loaded (on staging, image work products whose
attachment returns 404), the browser draws its broken-picture glyph
inside the bordered print and inside the 40px row thumbnail
> - The presentation already has a face for a file with nothing to show:
the icon sheet (the faint glyph and the type label) and the row's
document icon. A picture that cannot be loaded should take that face,
not a browser glyph
> - This pull request listens for the element's own `error` event in the
V1 primitives and falls back to the icon face on all three surfaces: the
stack front and the single tile on the Artifacts page, and the panel and
thread rows
> - The benefit is that a missing or unreadable file reads as a file of
its type everywhere, with no fetch to find out, and nothing changes for
a picture that loads

## Linked Issues or Issue Description

No public issue exists. This section follows the bug report template.

**What happened?**

With Artifacts V1 on, an image work product whose attachment content
returns 404 ("Attachment not found") shows the browser's broken-image
glyph inside the bordered print on the Artifacts page (the stack front
and the single tile) and inside the 40px thumbnail in the panel and
thread rows.

**What did you expect to happen?**

The artifact shows the icon face a file without a preview gets: the icon
sheet with the faint glyph and the type label on the Artifacts page, and
the document icon with the type label in the rows.

**Steps to reproduce**

1. Turn on Settings > Experimental > Artifacts V1.
2. Register an image work product whose attachment content returns 404,
or delete the attachment behind one.
3. Open the Artifacts page, the task's Artifacts tab and the task
thread.

**Relevant subsystem**

The web UI (`ui/src/components/artifacts/v1/`).

## What Changed

- `ArtifactPrint`: a new `usePreviewFailure(source)` hook beside
`useImageAspect`, and an `onMediaError` prop wired to the `error` event
of the print's `<img>` and `<video>`. The hook remembers the source that
failed rather than a flag, so a front that moves on to another picture
(a stack's newest artifact) gets a fresh chance.
- `ArtifactFront`: owns the failure state for its subject and renders
the icon sheet (`IconSheet`, the `icon` treatment) once the print
reports an error; it passes the error on through its own `onMediaError`.
- `ArtifactStack`: on an error it stops fitting a print and stands the
icon sheet where a sheet stands in the media stack (the portrait anchor,
where audio stands), keeping the media backing.
- `ArtifactRow`: the thumbnail listens for `error` on its `<img>` /
`<video>` and gives way to `ArtifactRowIcon`, the document icon with the
type label.
- Until the picture loads the print stays the 4:3 placeholder, and a
picture that loads behaves as before. No fetch is made to detect a
failure.
- A failed video takes the plain icon sheet with no play disc, since the
stream it would promise cannot play; a video with no URL at all keeps
the existing media-fallback treatment with the disc.
- `index.ts` exports `usePreviewFailure`; the plan doc
`doc/plans/2026-10-22-artifacts-mvp-v1.md` records the fallback.
- Tests: `ArtifactTile.test.tsx` covers the single tile (4:3
placeholder, then the icon sheet after `error`), the picture stack (the
sheet stands where the audio stack's sheet stands, on the media backing)
and a video whose stream fails (icon sheet, no play disc).
`ArtifactRow.test.tsx` covers the thumbnail and the video thumbnail
giving way to the document icon, and a new source getting its own
chance.

## Verification

- `cd ui && npx vitest run src/components/artifacts/v1` — 6 files, 33
tests pass (5 new).
- `pnpm run check:token-gates` — exits 0: all four token gates clean,
palette tokens and the ClipLab character in sync. (The bare `pnpm
check:token-gates` form reports "Command not found" under pnpm 9.15.4 in
this worktree; `pnpm run` works.)
- `cd ui && pnpm typecheck` — exits 0 with no errors.
- Browser proof on a throwaway backend-free vite page (not committed)
rendering the primitives with a 404 picture, a 404 video, a picture that
loads and audio: on the Artifacts page the 404 picture's stack front is
the PNG icon sheet at the same position as the audio stack's sheet (left
37.89px, top 13.34px) on the media backing; the single tiles show the
PNG and WEBM icon sheets with no play disc while the loading picture
keeps its fitted 240×160 print; the rows show the PNG and WEBM document
icons with no `<img>`/`<video>` left, while the loading picture's row
keeps its thumbnail. Not run: the browser suites (`pnpm test:e2e`, `pnpm
test:release-smoke`) and a live staging check.

States enumerated: loading (4:3 placeholder print, unchanged), loaded
(fitted print, unchanged), failed picture (icon sheet / document icon),
failed poster-less video (icon sheet without play disc / document icon),
source changed after a failure (print again).

## Risks

- Low risk. The change is behind `enableArtifactsV1` and only acts on
the element's `error` event, which fires for a 404, a network failure,
or content that is not decodable as a picture; nothing is fetched to
find out.
- `ArtifactFront` and `ArtifactStack` each keep the failure state for
their subject (as both already keep the loaded aspect), set from the
same event in one render; a consumer that renders `ArtifactFront` with a
print size but ignores `onMediaError` still gets the icon face, only at
the print's position.
- A picture that fails once and later becomes readable at the same URL
keeps the icon face until the surface remounts or the subject moves on
to another source; the data does not change under a mounted tile in
practice.

> 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

- Claude (Anthropic), model ID `claude-fable-5-1`, extended thinking,
running in Claude Code (desktop app) with tool use: file editing, shell,
Vitest, and the built-in browser for the proof page.

## Checklist

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

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
canary/v2026.1027.0-canary.4 nightly/v2026.1027.0-nightly.0
2026-10-26 21:08:49 -07:00
4da876ab0b fix(native): always advertise run-scoped readers to provider threads (#16995)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native runs use a Paperclip tool authority to declare tools to the
provider thread. Codex runs through runnerd.
> - An external-chat wake with a truncated comment batch requires the
agent to call `read_current_wake_comments`. A terminal fence fails the
run if the agent did not read every bound comment.
> - Codex `thread/resume` has no `dynamicTools` parameter. A resumed
thread keeps the tool catalog from its first `thread/start`.
> - The tool authority declared `read_current_wake_comments` and
`read_chat_attachment` only when the current run had a binding. The
tool-contract fingerprint already said that both tools are "always
advertised", so the code and the contract did not agree.
> - When a truncated chat wake resumed a thread that started on an
ordinary turn, the agent could not see the reader. The agent blocked,
and the fence failed the run with `native_current_wake_comments_unread`.
The task then stayed blocked for the board.
> - This pull request declares both readers on every turn and rotates
threads that started with the old catalog.
> - The benefit: truncated GitHub, Slack, and other chat wakes can read
their full comment batch on resumed threads. They no longer fail at the
fence.

## Linked Issues or Issue Description

No public issue exists. This is the bug description, in the fields of
`bug_report.yml`.

**What happened?**

A native Codex run got a GitHub chat wake with a truncated comment
batch. The prompt told the agent to call `read_current_wake_comments`.
The agent searched for the tool and did not find it. It reported a block
(`required_wake_reader_unavailable`). The server sent the reader in the
run's authorized tool list, but the provider thread had resumed. The
fence then failed the run with `native_current_wake_comments_unread`,
and the task became blocked. This happened 6 times on staging for the
same task.

**Expected behavior**

A resumed thread can call `read_current_wake_comments` and
`read_chat_attachment` on any turn that has a binding. The run then
completes normally.

**Steps to reproduce**

1. Start a native Codex session on a task with an ordinary wake. Do not
use a truncated chat wake.
2. Send an external-chat message that makes the wake comment batch
truncated (`fallbackFetchNeeded: true`).
3. The run resumes the same Codex thread. The model cannot see
`read_current_wake_comments`. The run fails with
`native_current_wake_comments_unread`.

**Paperclip version or commit**

Hosted staging release `fc0d390d`. The code is the same on `master` at
`3bbc210`.

**Deployment mode**

Hosted (staging). The native runner uses the `codex_app_server` backend
through runnerd in an external sandbox.

**Agent adapter(s) involved**

Codex (native `paperclip_runner`).

Related history: the existing retained-fingerprint table already lists a
"conditional current-wake reader" rotation. This change fixes a
regression of that contract. I searched open and closed PRs for
`current_wake_comments` and "wake comments" and found no duplicate fix.

## What Changed

- `PaperclipRunnerToolAuthority.definitions()` now always declares
`read_current_wake_comments` and `read_chat_attachment`. Execution stays
fail-closed: without the server-verified binding or scope, a call still
fails with `paperclip_runner_tool_not_advertised` or
`paperclip_runner_chat_attachment_read_scope_unavailable`.
- The native tool-contract fingerprint goes from v17 to v18, with the
two policy values set to `.v2`. Checkpoints from deployed v17 threads,
which may lack the readers, start a fresh provider thread one time with
full task context.
- New regression test: the catalog for an ordinary turn is equal to the
catalog for a truncated chat turn, and it contains both readers.
- New resume tests: the deployed v17 local and remote fingerprints do
not rebind. The existing test that asserted conditional advertisement
now asserts the stable catalog and the fail-closed execution.

## Verification

- Red before the fix: `npx vitest run
src/services/native-runtime/paperclip-runner-tool-authority.test.ts -t
"advertises only real bindings|keeps run-scoped reader declarations
stable"` had 2 failures. The unbound catalog had 48 tools and did not
include either reader.
- Green after the fix: `paperclip-runner-tool-authority.test.ts`,
`native-session-resume.test.ts`, `current-wake-comments.test.ts`,
`chat-attachment-read.test.ts`, `native-execution-input.test.ts`, and
`file-delivery-bridges.test.ts` all pass.
- `native-session-executor.test.ts`: 764 pass and 1 fails. The failure
(`compares export process compatibility … (changed: false)`,
`db.transaction is not a function`) also happens on unchanged `main`, so
this change did not cause it.
- Consequential assumption: Codex does not refresh dynamic tools on
resume. Evidence: `codex app-server generate-json-schema` for codex-cli
0.160.0 shows `ThreadStartParams.dynamicTools`, but `ThreadResumeParams`
has no `dynamicTools` field. The failed staging run also showed it: the
server listed `read_current_wake_comments` as authorized, but the
model's tool search returned no match after `thread/resume`.
- Not run locally: the server `tsc --noEmit` step (the sandbox has a 4
GB memory limit and the process was killed) and `dot-runner.test.ts`
(`cargo` is not installed). CI runs both.
- Not run: a live end-to-end chat wake against a real Codex provider.
That needs a staging deployment.

## Risks

- The fingerprint bump makes every native session that resumes from a
v17 checkpoint start a fresh provider thread one time. Paperclip
rebuilds the full task context, but the provider-side conversation
history is not carried over. This is the same tradeoff as earlier
fingerprint bumps.
- Two more tool declarations use a small part of the Codex dynamic-tool
budget. The assigned-MCP fit check already includes these definitions.
- Follow-up, not in this PR: the native Codex driver reports
`toolRefreshOnResume: true`, and the runnerd fake app server accepts
`dynamicTools` on `thread/resume`. Real codex-cli 0.160.0 has no such
field. Connection-tool continuations that depend on resume refresh need
a separate check.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) from Anthropic, running as a
Paperclip agent with tool use (shell, file edit, and test execution).

## Checklist

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

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

Co-authored-by: devinfoley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:08:00 -07:00
Dotta 9c1e30e2c9 test(evals): bind canonical Slack ingress to actual native source 2026-10-26 23:06:56 -05:00
Dotta c2b8cb697a test(evals): qualify finite Slack companion provider and source contracts 2026-10-26 23:04:03 -05:00
Dotta 7d357062f5 test(evals): probe unsupported effective skill-use prerequisites 2026-10-26 23:01:42 -05:00