Commit Graph
5324 Commits
Author SHA1 Message Date
Devin FoleyandPaperclip 73659c6b27 fix(server): let Git expiration discard Paperclip-managed checkouts (#16586)
Reconstructable Git expiration rejected nearly every terminal worktree with unique_ignored_files, because Paperclip writes two ignored paths into every worktree itself: .claude/settings.local.json and the managed project checkouts under .paperclip-repositories/. Expiration now accepts the settings file, and accepts a managed checkout only through a prover the expiration worker supplies. The prover maps the checkout back to its project workspace row and proves it reproducible against that row's repository URL: worktree and stock-storage checks, no linked worktrees, no unreachable objects, and every ref, reflog and HEAD commit reachable from upstream refs fetched fresh into a proof store. Every inspection Git command now runs with core.fsmonitor disabled.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-16 14:31:06 -07:00
DottaandPaperclip 3db33c131e perf(slack): overlap endpoint preparation and narrow credential reads (#16593)
Prepare Slack endpoints in pairs with a per-call concurrency limit of two, and resolve only the current bot token for live tool authority. Preserve candidate-order errors, active-sibling settlement, credential rotation and authorization revocation.

Verified with real PostgreSQL regression controls, full repository CI/E2E, security checks and current-head Greptile Plus5/5 with no unresolved findings.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-16 16:05:46 -05:00
Nicky LeachandPaperclip d1463fb79e Preserve saved delivery deadlines across clock ticks (#16589)
## Thinking Path

> - Paperclip manages AI agent work and message delivery.
> - Delivery workers restore saved retry and maintenance deadlines.
> - The coordinator converts each future deadline to a delay and back.
> - A clock tick between those reads can move the deadline.
> - This pull request keeps the saved future timestamp.
> - A deterministic test covers the clock tick.

## Linked Issues or Issue Description

Refs #16503. This separate fix addresses an unrelated CI failure found
during its review.

**What happened?**

The unchanged delayed GitHub publication restart test intermittently
reports a wake deadline one millisecond after the saved timestamp. The
coordinator subtracts one clock reading from the deadline, then adds the
delay to a later reading.

**Expected behavior**

Keep a future saved deadline exactly. Preserve the current retry
behavior for expired deadlines during idle reconciliation.

**Steps to reproduce**

Register a task with a future `nextRunAt` timestamp. Advance
`Date.now()` by one millisecond on each read. The old coordinator
reports `nextWakeAt()` one millisecond after the saved timestamp. The
new unit test forces this sequence.

## What Changed

- Let the internal scheduler accept the existing absolute future
deadline.
- Keep relative retry and immediate wake scheduling unchanged.
- Add a deterministic advancing-clock regression. Keep the chat
integration assertion unchanged.

## Verification

- Based on `origin/master` at `c91f7a6e5f`.
- Coordinator suite and the unchanged delayed GitHub publication restart
case: 16 passed, 1346 unrelated cases skipped.
- The new regression fails against the original coordinator with the
exact one-millisecond offset.
- Server typecheck passed.
- On head `d2db3e15d6dfafd390bab59529474abebb19d09c`, all 55 hosted
checks passed, with 2 expected skips. This includes all server/chat
shards, Runner checks, build, canary dry run, security checks, and
end-to-end tests.
- Standard Greptile and Apex are both 5/5 on that exact head, with no
review threads. Apex completed after green CI; final CI verification
remains fully green and the branch is mergeable.
- `git diff --check` and the pre-push secret/PII scan passed.

## Risks

Low risk. The change keeps the saved future timestamp. Existing tests
cover retry, idle drain, preemption, and shutdown behavior. No schema or
API change is required.

## Model Used

OpenAI Codex, GPT-6, with reasoning, repository inspection, code
execution, and testing. The exact deployment model ID and context window
are not exposed in this session.

## 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 references)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [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 (no
public contract changed)
- [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>
canary/v2026.1016.0-canary.16
2026-10-16 12:09:38 -07:00
DottaandPaperclip 93ab7bad11 Add immutable Muse clients and OAuth grant contracts (#16447)
Add immutable Muse client assets and the grant-linked private contract.

Keep the grant migration safe for replay and partial schema recovery, preserving exact preview receipts and existing data. Keep public wake signals separate from private provider authority.

Refs #16447.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-16 14:09:26 -05:00
DottaandPaperclip 2cc7992369 Share the external Runner lifecycle and add strict Muse contracts (#16444)
Add the external Runner foundation with distinct Dot/Grok V6, canonical home-mode V7 and Muse V8 contracts.

Keep generated schemas and profiles synchronized. Bound default question-answer command IDs in a disjoint namespace while preserving existing IDs and durable replay. Preserve canonical migration history and add normally generated, replay-safe Muse schema milestones with literal preview-history fixtures.

Refs #16444.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-16 13:46:50 -05:00
Devin FoleyandPaperclip 4de6335c3b perf(server): trim saved run JSON from agent authentication (#16577)
## Thinking Path

> - Paperclip coordinates AI agents that work for companies.
> - Agent JWT authentication checks each request against its saved run.
> - Saved runs can contain large prompts and result payloads.
> - The first run lookup needs only identity fields, cancellation state,
and the conversation flag.
> - Reading all saved JSON transfers data that this lookup does not use.
> - This PR selects the required JSON fields in PostgreSQL and preserves
the existing authorization decisions.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The initial saved-run lookup in agent JWT authentication.

**Subsystem affected**

Server authentication and database queries.

**Current behavior**

The lookup transfers the full context and result JSON on each request.
It uses only the cancellation state and conversation flag from those
values.

**Proposed behavior**

Select the required JSON fields in PostgreSQL. Keep the run, company,
and agent predicates. Read current state on every request.

**Reason and benefit**

A synthetic lookup with a 96 KiB prompt and 4 KiB result returns 102,520
JSON bytes before this change and 134 bytes after it. This reduces data
transferred by this query.

**Breaking changes**

None intended. Identity fields, write revocation, read access, and
cancellation response codes retain their current behavior.

Related: #9280 addresses authentication round-trip counts. This PR only
reduces the data returned by the existing initial run lookup.

## What Changed

- Extract the initial run lookup and select only the JSON fields that
authentication consumes.
- Add real PostgreSQL tests for legacy JSON shapes, fresh cancellation
state, payload size, and run/company/agent scope.
- Add a reproducible benchmark that loads the original query from an
explicit baseline commit.
- Document the lookup and benchmark.

## Verification

- Baseline: `06689c1b3410000bcd65ff40cb469e600209c4db`.
- `pnpm -r typecheck`: passed.
- `pnpm build`: passed.
- Final server typecheck: passed.
- Final-head GitHub checks: all green at
`2be1c38e6bf51616b69993cf84cee1eef9657517`. Apex: 5/5, zero unresolved
threads, conflict-free.
- `pnpm check:module-boundaries`: passed. The standalone synthetic
benchmark now uses the existing root benchmark directory and resolves
Drizzle through the server workspace.
- Focused HTTP middleware and database tests: 29 passed on real Homebrew
PostgreSQL 16.14. The SQL test compares 270 combinations of status,
cancellation JSON, and conversation JSON. It also checks fresh state and
company/agent/run scope.
- Broad native verification: 873 passed across the HTTP middleware, SQL
projection, run diagnostics, and native executor suites.
- The default local full command completed its general server lane with
12,090 passed and 8,885 skipped tests. It could not finish the root
workflow: the host could not start the bundled PostgreSQL; a diagnostics
assertion matched a directory name; and Git fixtures retained the host
default branch. These failures remain in the verification record. All 68
failed files now pass on replay with disposable Homebrew PostgreSQL, an
isolated checkout, and command-scoped Git defaults. The 68-file replay
passed 2,588 tests and left two email skill-resolution failures. Moving
the checkout outside an unrelated ancestor skills directory resolved
those last failures: all 44 tests in that file passed. Product code and
test assertions were unchanged for these host adjustments.

Benchmark command:

```sh
pnpm --filter @paperclipai/server exec tsx ../scripts/benchmark-agent-authentication.ts --baseline-ref=06689c1b3410000bcd65ff40cb469e600209c4db
```

The final benchmark ran at treatment commit
`2be1c38e6bf51616b69993cf84cee1eef9657517` with a clean worktree. The
local run used PostgreSQL 16.14 and the maintained test constructor seam
to start disposable Homebrew clusters. The bundled PostgreSQL binary
could not start on this host. Both versions use the same schema and
predicates. Each case has two warmup rounds and ten measured rounds with
alternating order. Random content prevents unrealistic compression.
Complete authentication decisions match.

| Context / result | Before JSON bytes | After JSON bytes | Before
median | After median |
| --- | ---: | ---: | ---: | ---: |
| 1 KiB / 4 KiB | 5,240 | 134 | 0.157 ms | 0.157 ms |
| 96 KiB / 4 KiB | 102,520 | 134 | 0.331 ms | 0.140 ms |

These are local query round trips, including result transfer and
decoding. The small control has no material latency gain. The large case
improves by about 2.4x. Both plans read the same single row. The primary
result is reduced response size. This does not measure HTTP latency,
server CPU savings, or fleet-wide cost savings. Full samples and plans
are retained with the verification evidence.

## Risks

- JSON types must retain their meaning. The real database tests compare
the consumed authorization contract with the original projection,
including SQL null, JSON null, scalars, arrays, objects, string
booleans, and cancellation states.
- The change covers the first JWT run lookup. Later run identity capture
keeps its existing full-run reads and transaction locks. Those reads
remain a separate cost.
- The change adds no schema migration or authorization cache.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, code editing, and shell
tools. This session did not expose a more specific serving model ID or
its 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-16 11:44:14 -07:00
DottaandPaperclip c91f7a6e5f perf(runner): overlap verified sandbox launch assets (#16585)
Native sandbox launch preparation stages fresh harness files and immutable runtime context in sequence. Overlap them only under the verified concurrent-sync capability, settle every started transfer before launch or cleanup, and preserve serial SSH behavior and error priority.

A matched real-Daytona comparison reduces staging from 1,552 ms to 1,017 ms. The larger active-run difference includes model variation and is not attributed to this change. Add an explicit single-attempt diagnostic and synchronize stale Pi evidence metadata with its shipped profile.

Validation: 764 native runner/tracing cases, 2,764 support cases, repository typecheck/build, fresh exact-head full CI/E2E/security and Apex 5/5 pass. Observability remains a silent no-op when no endpoint is configured.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
canary/v2026.1016.0-canary.15
2026-10-16 13:35:26 -05:00
DottaandPaperclip f5534717b0 fix(runtime): preserve unexported native work during lease cleanup (#16583)
Preserve the exact native workspace source until its export is finalized. Apply the same guard to cleanup sweeps and direct environment teardown. Keep stop-only recovery and healthy warm retention available.

Verified with 317 targeted tests, server typecheck, module boundary check, all current-head CI gates, and Greptile 5/5.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-16 12:56:09 -05:00
Nicky LeachandPaperclip 6f8ad3d77b Add verified session-local stdio MCP transport to Runner (#16526)
## Thinking Path

> - Paperclip manages AI agent runs and their tool authority.
> - Runner supplies provider sessions with assigned MCP connections.
> - The current native connection binding supports an HTTP gateway only.
> - Execution hosts also have private local tools with stdio MCP
interfaces.
> - Those tools need session-local registration and safe concurrency.
> - This pull request adds verified local stdio assignments beside the
existing gateway.
> - Providers receive the existing tool schemas and image content.

## Linked Issues or Issue Description

**Problem or motivation**

Runner can supply a native provider session with an HTTP MCP gateway,
but cannot also assign a verified local stdio MCP server without
persistent provider configuration. Execution hosts need a session-local
transport for local MCP tools, coordination for calls sharing a
resource, and native schema/image delivery. The pinned native Codex
client drops MCP images when `structuredContent` is also present.

**Proposed solution**

Add general stdio MCP transport support using host-assigned executable
digests, arguments, environment and a private Unix socket identifying
the shared resource. Tool implementations supply their own schemas and
responses; Runner forwards their native MCP schemas and content.
Register the transport for the current provider session. Serialize calls
across processes, keep unknown outcomes fenced, and authorize assigned
native Codex tools without a new prompt while routing explicit tool
confirmations through existing PRP permission requests. Retain the HTTP
gateway and applicable permission policy.

**Alternatives considered**

Reimplementing tools or translating their actions into provider-specific
APIs would tie the transport to individual tool implementations. Global
provider config would leak session assignments. Shell commands alone do
not deliver native MCP images or preserve MCP approval requests.

**Roadmap alignment**

This completes the existing Runner transport boundary. Related: #10690
(operator config for the legacy ACPX engine), #11605 (gateway delivery
of installed stdio connections), #16486 (preconfigured provider homes).
This change assigns local tools on the execution host and leaves those
administration paths intact.

## What Changed

- Add reserved, verified stdio bindings for native Codex and through the
shared ACPX runtime. ACPX clients must support stdio MCP assignments.
Read-only sessions omit the binding. No home/project config is
rewritten.
- Run the backend with only its assigned environment through the
verified executable launch path.
- Serialize all calls sharing a private Unix socket across sessions.
Metadata remains responsive. Queued cancellation prevents execution;
active calls remain fenced until acknowledgement. Unknown outcomes
require a resource restart.
- Authorize host-assigned native Codex tools without a new per-call
prompt, matching existing execution-host shell utilities. Preserve
explicit tool deny/prompt rules. Route requested MCP tool confirmations
through one-time PRP approvals, bound to the active thread and turn.
Keep shell escalation disabled and existing tool deny rules effective.
ACPX adapters retain their applicable ACPX permission policy.
- Preserve schemas and image blocks. For native Codex 0.160/0.162,
retain structured metadata as MCP text on image-bearing results to avoid
the client's image-loss bug. ACPX adapters receive the original
response.
- Document transport authority, cancellation and recovery limits.

## Verification

- `pnpm -r typecheck` and `pnpm build` passed with pnpm 9.15.4 and Rust
1.97.1 on macOS arm64.
- Before Apex, full `paperclip-runner-core` Rust tests passed. After the
fixes, all 379 library tests and eight subprocess tests using a generic
stdio MCP fixture pass. Those subprocess tests cover schema/image
passthrough, assigned-only environment, executable drift, call
serialization, cancellation and unknown outcomes. The native Codex suite
passed 91 tests with two intentionally ignored;
`receipt_limit_rejects_the_call_and_keeps_polling_when_interrupt_fails`
timed out waiting for `turn.interrupted`, including an isolated retry.
The unchanged pre-fix commit `80917b301a2309f7966a466cf0c198a2112be2d5`
reproduces the same timeout in isolation; this local native-suite run is
not claimed as fully passing.
- All 252 native controller tests pass against the final narrowed
approval branch. Focused TypeScript transport/permission suites passed
(298 tests); final binding, read-only host, policy and adapter checks
passed (105 tests).
- **Provider-client smoke coverage:** pinned Codex 0.160.0 through
native Runner and Claude SDK 0.3.286 used local stdio MCP servers and
loopback model API fixtures without live model credentials. These
clients define the smoke-test coverage; this is not an exhaustive
adapter compatibility test. Image responses reached the next model
request as typed image blocks. Native Codex checks also verified
unattended execution without approval requests, explicit per-tool prompt
denial without backend execution, disabled-tool rules, and stale-turn
approval rejection.
- Two Greptile concurrency findings were reproduced with failing
subprocess tests and fixed: pending cancellation under output
backpressure, and a ctime-based fence reset after chmod. Initial checks
exposed Codex CLI option-scope and key-quoting errors, which were
corrected. The existing ordinary elicitation regression also exposed an
overly broad request branch; the final branch handles explicit tool
approvals only.
- `pnpm test:run` was attempted. The broad local server suite hit
persistent embedded PostgreSQL startup failures and unrelated local Git
fixture failures; it was stopped with the failure log retained. Those
local results are not claimed as passing. Hosted Linux CI gates passed
on the reviewed runtime commit, as recorded below.
- First Apex review found three additional defects, now fixed with
regressions: keep the resource-lock socket snapshot instead of sampling
the socket twice; project native permission requests with durable
turn/item IDs while retaining provider IDs in details; validate provider
response conversion before marking a request as settling. The retry
regression failed before the fix. Focused Codex request/adapter checks
pass (23 tests), server permission projection passes (16 tests), and all
379 Runner library tests pass. The broader database-backed bridge suite
was attempted but its local PostgreSQL setup timed out; 29 tests did not
execute. This local failure is retained, not claimed as passing.
- Runtime acceptance before the documentation clarification, on
`bd4fccae001157beb5bc76ff17c1920a5db3105a`: standard Greptile 5/5, then
Apex 5/5 with no unresolved threads; all 60 checks complete successfully
or intentionally skip. Ten CI jobs were interrupted by the same runner
shutdown, including one stale job that required cancellation before
retry; the same-commit retry passed. Hosted Rust tests also pass,
including the native suite with the local baseline timeout noted above.
- `git diff --check`, map validation, and pre-push secret/PII scan
passed.

## Risks

- No live model account or deployment isolation qualification was run.
The loopback fixture proves the actual vendor-client request format, not
model interpretation.
- Stdio MCP support uses the current host-assignment contract: each
binding requires a verified executable and a private Unix socket
identifying the serialized resource. Other platforms retain the HTTP
path. No provider version was upgraded.
- A lost acknowledgement blocks subsequent calls until the host restarts
the assigned resource. Direct shell calls are outside this MCP fence and
retain the existing shell policy.
- Preconfigured Claude remains unattended and denies interactive
approval requests. Host assignment authorizes native Codex local tools;
it must be supplied only for sessions already authorized to use the
resource.

## Model Used

OpenAI Codex, GPT-6, with reasoning and repository/tool execution. The
session does not expose a more specific model identifier 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
- [ ] 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-10-16 10:39:38 -07:00
DottaandPaperclip ce0eff7d1f perf: reduce fresh workspace clone and sandbox staging waits (#16487)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Each run needs a private workspace and safe persistence.
> - Fresh isolated repository sources download upstream Git history
again.
> - Sandbox staging also verifies an optional seed cache before it
uploads the workspace.
> - That verification holds a company lock during a full extraction and
hash scan.
> - This pull request reuses pristine Git objects through an
authenticated remote clone.
> - It also overlaps seed verification with upload and moves
verification outside the publication lock.
> - The benefit is less repeated transfer and less serial preparation
work, with the same task isolation and recovery contract.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Fresh task-private repository preparation and inbound sandbox workspace
staging.

**Current behavior**

Each isolated remote source clones the full upstream history. Optional
seed verification then extracts and hashes another full tree before
upload. One workspace's verification can hold the company cache lock
while other workspaces wait.

**Proposed behavior**

Use a company and credential scoped cache of pristine upstream objects
for GitHub HTTPS sources. Each task still clones from the current
remote. It disconnects from the cache before publication. Run seed
verification alongside upload. Hold the company lock only for final
capacity checks and publication.

**Reason and benefit**

These operations lie on the workspace preparation path. Removing
repeated Git pack transfer and serial verification can reduce startup
time. Local measurements below cover these boundaries. They do not
establish hosted agent latency.

Related: #16425, #16454, #16398, #16461, #16468, #16473. The cache
inspection and durable folder proposal in #13376 covers separate product
work.

## What Changed

- Add a pristine upstream Git object cache for fresh isolated GitHub
HTTPS sources. Key it by company, repository and current credential
fingerprint. Do not store credentials.
- Keep online clone authorization and current remote refs. Use
`--reference-if-able --dissociate` so task objects remain independent. A
reference failure retries once online without the cache and resolves
credentials again.
- Cap cache storage at 64 entries and 4 GiB per company, with 2 GiB per
entry. Skip contended publication. Bound priming by a cooperative
five-second budget and a 4,096-entry scan allowance. Keep the existing
free-space reserve.
- Overlap optional seed verification and required workspace upload.
Settle both before returning or deleting temporary files. Recheck quotas
and company directory identity under the publication lock.
- Keep the independent fresh upstream reproducibility proof, task
commits, dirty source copies, ignored files, nested repositories and
durable recovery behavior.
- Suppress optional Git server-info generation during dissociation so
generated storage passes the existing safe-expiration checks. This uses
[the supported Git
setting](https://git-scm.com/docs/git-config/2.43.0#Documentation/git-config.txt-repackupdateServerInfo).
- Add bounded timing phases, regression coverage and two repeatable
local benchmarks. No first-party Telemetry or dependency changes.

## Verification

- `pnpm -r typecheck`: passed.
- `pnpm build`: passed.
- Affected adapter suites: 240 tests passed. Native workspace
persistence/history and tracing: 33 tests passed. Server
checkout/isolation/tracing batch: 53 tests passed. Final object-cache
authority, path and capacity suite: 12 tests passed. These batches
overlap.
- Cache and expiration/restoration lifecycle batch: 34 tests passed. A
direct generated-storage check covers cached clones.
- Real HTTP Git fixture: a matching cache does not bypass revoked remote
access. Other fixtures cover current refs/tags, hidden cached commits,
independent objects after cache removal, corruption fallback,
credential/company scope, quota limits and publication contention.
- Initial restricted runs hit SQLite write and Unix socket permission
failures. The adapter batch passed with disposable writable instance
state and socket access. The first typecheck rejected the test use of
`Promise.withResolvers`; typed deferred gates now use the supported
library contract.
- Repository benchmark on final head
`9047368bf3b7fa2061e00d698b8b51cf4ae1da32`: five alternating samples,
real local `file://` Git transport, synthetic 32 MiB history, no
injected latency. Median 1143.8 ms to 240.5 ms; ranges 1085.7–1486.6 ms
and 188.0–254.8 ms. Received Git pack bytes fell from 33,569,788 to
zero. Initial cache priming took 1208.4 ms and is reported separately.
- Sandbox benchmark, five alternating samples per mode on 10,000 files
with real packing, extraction and verification: with a controlled
3-second transfer delay, median 9.450 s to 7.620 s (ranges 9.280–12.115
s and 7.296–7.983 s). With no injected delay, median 6.808 s to 6.450 s
(ranges 6.255–7.029 s and 5.709–6.902 s). The no-delay ranges overlap;
local copying competes with verification for disk. Both modes verify
final tree contents, ignored-file exclusion and Git HEAD. These are
synthetic local fixtures, not hosted agent runs.
- Reproduce with `pnpm --filter @paperclipai/server exec tsx
../scripts/benchmark-repository-object-cache.ts` and `pnpm --filter
@paperclipai/server exec tsx ../scripts/benchmark-workspace-staging.ts
--baseline-ref=307eb16c0c --transfer-delay-ms=3000`.
- Prior head `af766bc820` received Greptile 5/5 with no findings. Its
E2E shard 7 failed in `plan-task-fixture` cleanup: company deletion
raced an `activity_log` foreign key. That fixture has no provider run or
Git workspace. The failure is retained here; final-head CI must pass.
The corrected corruption fixture now forces a pack and verifies a real
damaged archive. Current head `9047368bf3b7fa2061e00d698b8b51cf4ae1da32`
has Greptile 5/5 with no review threads or findings. Final head has 55
passing checks and two optional Storybook skips. [The first
workspace-shard
attempt](https://github.com/paperclipai/paperclip/actions/runs/38802441857/job/116655485350)
passed all 1,778 cases, then reported a late `EPIPE` from unchanged
`server-utils.ts` while the ACP reuse test was active. That case mocks
workspace preparation and passed locally in isolation. [Its single retry
on the same
commit](https://github.com/paperclipai/paperclip/actions/runs/38802441857/job/116659728034)
passed, as did the dependent verification gate. The earlier E2E cleanup
fixture passed on this final head. Both initial failures remain recorded
here. A full local suite was not duplicated; the affected boundaries
were run locally and the complete suite runs in CI.

## Risks

- First cache population adds local copying. Cold clones, credential
rotation, capacity exhaustion and a corrupt cache retain the online
clone path. No eviction is added. Cached data is bounded, but many
companies can each consume their allowance.
- Individual filesystem calls and cleanup can exceed the cooperative
priming budget. Failed reference clones make one additional online
attempt.
- Parallel verification and upload compete for disk and CPU. The local
no-delay benchmark shows a smaller gain than a slow-transfer case.
Verification still checks contents before cache publication, and
required upload failures remain failures.
- Local fixtures do not prove hosted throughput or the 15–30-second
end-to-end target. The fresh checkout still writes its working tree, and
model startup, copyback and other preparation costs remain.

## Model Used

OpenAI Codex, GPT-6 family. The exact serving model ID and context
window are not exposed in this session. Used reasoning, source editing,
shell tools 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 and contains no internal task
identifiers
- [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>
canary/v2026.1016.0-canary.14
2026-10-16 12:21:47 -05:00
Devin FoleyandPaperclip 647116dd4b fix(adapter-utils): allow sandbox agents to read run diagnostics (#16575)
## Thinking Path

> - Paperclip manages agent work and its execution history.
> - Sandbox agents use a callback bridge to reach the control plane.
> - The API already provides authorized run diagnostics.
> - The bridge denied those read routes before the API could check
access.
> - Health scans could see task activity but could not inspect failed
runs.
> - This pull request admits exact GET routes on both bridge transports.
> - Linked task results also apply the caller's task-read filter.
> - Agents can inspect failures through their existing run identity.

## Linked Issues or Issue Description

**What happened?**

A sandbox health scan received `403 Route not allowed` for run history,
run details, events, and task recovery records. The same agent could
read task activity and connected provider state. The bridge stopped
these diagnostic requests before they reached the API.

**Expected behavior**

The bridge should admit the existing diagnostic GET routes. The API must
still enforce company and task access and redact run content. Execution
and recovery writes must stay denied by these rules.

**Steps to reproduce**

1. Start an agent in a sandbox with the normal callback bridge.
2. Use its injected API credential to request company run history.
3. Request a run's details or events.
4. Observe the bridge's route denial before the API handles the request.

**Paperclip version or commit**

Reproduced with the callback route list on master at `9f4a956b906`.

**Deployment mode**

Authenticated server with a managed sandbox. Both queue and HTTP/2
transports use this route list.

Related work: Refs #15854 adds separate cost-read routes to this list.
It does not add run diagnostics. Searches found no duplicate
run-diagnostic change.

## What Changed

- Admit nine existing diagnostic GET paths: company history and live
runs; run details, events, logs, workspace operations and task links;
task runs and recovery records.
- Reject other methods, extra path segments, execution control, recovery
resolution, and provider-trace administration.
- Filter linked tasks by the caller's task-read predicate in both
activity and original-task queries. Keep those results in the run's
company. A readable run must not expose private task titles.
- Test both route lists and the HTTP/2 request path. Preserve query
parameters and controller denial responses. Add a database regression
with public and private tasks linked to a readable company run.
- Document bounded diagnostic reads, private-run stubs, access checks,
and credential handling in the agent API reference. Update both affected
run access maps with the sandbox entry points and verification limits.

## Verification

- Passed both bridge suites: 139 tests.
- Passed the server run-route, activity-route, and secret-redaction
suites: 117 tests, including the new task-link filter test. The cold
route-import test needed a 30-second timeout on this host.
- Passed `pnpm -r typecheck`, `pnpm build`, the adapter-utils typecheck,
both generated capability checks, and `node scripts/check-maps.mjs`.
- Attempted `pnpm test:run`. The bundled PostgreSQL binary cannot start
on this host. The native session suite reported 15 database startup
failures; the privacy suite skipped its 15 cases. Stopped that full
local attempt after those failures. No full local-suite pass is claimed.
The final 16-case privacy suite also skipped locally for the same host
limitation; Linux CI ran all 16 cases successfully.
- One unchanged queue-response test hit a file-read race during the
combined local run. A separate rerun of both complete bridge suites
passed all 139 cases. The transient race was not independently isolated;
the original failure is retained in this record.
- All CI gates pass on head `97c65cec459d3fc2787083d05ae6ec51e3fd7b70`.
Linux CI passed all 16 database-backed run privacy cases, including the
new public/private task-link regression. These also cover private-run
denial, authorized owner/reviewer reads, redacted list stubs, credential
diagnostics, and deleted-task handling: [serialized shard
1](https://github.com/paperclipai/paperclip/actions/runs/38925932547/job/117057907621).
- Apex reviewed the final head at 5/5. Both findings are addressed and
their review threads are resolved.
- After deployment, run a new sandbox audit. Confirm it can list and
inspect authorized failed runs without a board credential. This has not
been deployed or live-tested by this PR.

## Risks

- Sandbox agents can reach more diagnostic GET routes. The existing
server access checks and redaction still apply.
- Linked task results now omit tasks that the caller cannot read.
Authorized task links remain visible.
- This change does not grant execution control or recover a failed
workspace.
- Existing bridge sessions may need a new run to load the updated route
list.
- No schema, credential, UI, workflow, or lockfile change.

## Model Used

OpenAI Codex, based on GPT-6, assisted with implementation and
verification. The exact serving model ID and context-window limit are
not exposed in this session. Capabilities used: code editing, shell
execution, and test execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-16 09:04:07 -07:00
DottaandPaperclip 06689c1b34 perf(skills): coalesce inventory before run cache locking (#16576)
Share each active company skill-inventory refresh before taking the version-cache lock. Read actor visibility under the lock and keep snapshot publication protected through durable run pinning. Preserve empty-selection behavior and the list API.

Validation: 176 affected cases, full repository typecheck/build, fresh full CI/E2E, security and exact-head Apex 5/5 pass. The local six-reader latency experiment improves from 50.4s to 18.3s; live response latency is a separate acceptance check.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-16 10:39:02 -05:00
Nicky LeachandPaperclip 5fbece9dcc Synchronize assigned skills through agent runtime hooks (#16501)
## Thinking Path

> - Paperclip manages shared skills and agent assignments.
> - Runtime plugins need installed skills before a task starts.
> - Skill content can change without changing the agent row.
> - Source access and version pins also affect delivered files.
> - This pull request feeds those changes into the durable runtime
stream.
> - The runtime reader resolves the same immutable skill sources used
for execution.

## Linked Issues or Issue Description

Builds on merged PR #16499. This pull request now targets `master` and
contains only the five skill synchronization commits.

**Problem**

Plugins receive no durable notification when an assigned skill changes.
A saved assignment can also become inaccessible after private-source
access changes.

**Proposed solution**

Invalidate referencing agents at installed skill mutations. Invalidate
inherited access when source bindings, project privacy, or policy
change. Return authorized skill sources and version IDs through the
owning runtime reader.

**Alternatives considered**

Activity-only subscriptions miss service writers. Per-task
synchronization adds preparation work. Copying current files would
ignore version pins.

**Roadmap alignment**

This extends existing skills, source synchronization, and plugin runtime
support.

## What Changed

- Defer multi-skill source runtime hooks until commit, after publication
holds all skill rows. Keep file-save journaling immediate to preserve
filesystem rollback.

- Add transactional invalidations for skill content, companion files,
versions, rename, deletion, policy, source access, and pin-setting
changes.
- Recognize canonical keys and supported legacy aliases.
- Reuse the execution skill resolver, current agent access, and instance
version-pin rules.
- Resolve and materialize sources outside the agent row lock. Recheck
ownership and runtime revision afterward.
- Keep task-only mentions and running-task snapshots in the execution
path.
- Document conservative invalidations for pinned assignments and
inherited access.

## Verification

- Rebased onto origin/master `9f4a956b90`, which contains merged PR
#16499. All five commits are unchanged according to `git range-diff`; no
conflict resolutions or source changes were needed.
- On rebased head `58b065941`, all 237 focused tests passed: runtime
skill hooks/source publication (41), plus content service, assignment
routes, policy routes, and project source access (196).
- Server typecheck passed after rebuilding Runner with the installed
Rust toolchain. Migration numbering/safety and map checks passed;
snapshot pruning made no changes. Migration CLI checks required local
IPC permission.
- Real PostgreSQL coverage includes concurrent source
publication/version saves, policy-only tightening/restoration,
referencing-agent fanout, company isolation, no-op counters, supporting
files, rename/deletion, rollback, private-source access, pinned/latest
bytes, and unassignment.
- Earlier stack validation passed workspace typecheck/build. Fresh
hosted CI and review must verify the rebased head before handoff.
- The broad local suite was previously limited by an unchanged
diagnostic test that rejects "private" in an error stack under this
/private/tmp worktree; that unchanged suite passed all 108 tests in the
original checkout. Hosted full-suite checks provide exact-head
verification.
- Diff whitespace and full PR secret/PII scan passed.
- All hosted CI/security checks are green on `58b065941`; fresh standard
Greptile and Apex both returned 5/5 with no findings or unresolved
threads. The PR targets master and is mergeable without conflicts.

## Risks

- Apply migration 0348 after the runtime-stream migration in #16499.
- Content invalidations include pinned assignments. The resolved version
stays pinned; consumers can avoid writes when its version/hash is
unchanged.
- Project access and source changes invalidate only agents assigned
skills from the affected sources. Company policy still invalidates the
company. Source polling bookkeeping and popularity counters do not
create work.
- The reader rebuilds installed caches without upstream requests.
Concurrent changes cause a bounded retry and then a conflict.
- Private transport integration and live remote computer qualification
remain separate.

## Model Used

OpenAI Codex, GPT-6, with reasoning, repository inspection, code
execution, and testing. The exact deployment model ID and context window
are not exposed in this session.

## 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 references)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [x] I have run tests locally and they pass (focused suites; broad
local limitation documented above)
- [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>
canary/v2026.1016.0-canary.12
2026-10-16 08:28:47 -07:00
Nicky LeachandPaperclip 9f4a956b90 Add durable agent runtime synchronization hooks (#16499)
## Thinking Path

> - Paperclip manages agents and their runtime configuration.
> - Plugins can maintain external agent computers.
> - Configuration and file saves currently miss durable plugin delivery.
> - A transient activity notification cannot recover after interruption.
> - This pull request adds an opt-in agent runtime resource stream.
> - The existing dispatcher delivers and acknowledges its committed
work.
> - Plugins can prepare computers when configuration changes.

## Linked Issues or Issue Description

Related: #15631 and #16203 establish agent lifecycle and native durable
delivery.

**Problem**

Plugins cannot reliably react to agent configuration and instruction
changes without polling or waiting for execution.

**Proposed solution**

Add an opt-in `agent_runtime` stream to the existing resource journal.
Record changes at the database mutation and managed file audit
boundaries. Wake the dispatcher with content-free commit notifications.
Provide scoped runtime inputs to the selected lifecycle plugin.

**Alternatives considered**

Route-only activity events miss service and background writers.
Scheduled scans delay changes and repeatedly query unchanged state.

**Roadmap alignment**

This extends the existing plugin and agent configuration work.

## What Changed

- Add durable update/delete events for agent runtime state, including an
existing-agent baseline.
- Capture configuration changes, instruction edits, deletion, restore,
and managed run writeback.
- Add commit notifications and a reconnect scan. Register one listener
for configured, enabled runtime subscribers. Release it when none
remain.
- Keep HTTP startup independent of queued plugin callbacks.
- Add `ctx.agents.getRuntime`. Require lifecycle capability, invocation
company, enabled plugin, and owning lifecycle selection.
- Keep legacy agent/project subscriptions isolated. Manual inbox reads
retain both legacy streams.
- Resolve recovered instruction paths without reading or hashing bundle
contents. Use the company reporting graph for runtime health.
- Repair missed file events on identical save/deletion retries after a
journal failure; preserve content tokens and conditional-write checks.
- Correlate file audits with runtime revisions to deduplicate recorded
retries without a new table. Bound the lookup with the existing
company/timestamp index.
- Document the contract, migration, access boundaries, and
session-connection requirement.

## Verification

- Rebased the full stack onto origin/master after #16507.
- Final replayed stack: `pnpm -r typecheck` and `pnpm build` passed.
- All 132 affected runtime, skill, connector, publication, instruction,
managed-agent, and delivery tests passed after the latest-master rebase.
Workspace typecheck and build passed.
- Journal failure injection verifies recovery on identical file writes,
deletions, and instruction retries from an acknowledged inbox.
- Deduplication follow-up: 80 runtime/instruction/delivery tests and
server typecheck passed. Repeated retries create no audit/events; old
hashes returning after an intervening commit still repair delivery.
Built-in/native instruction writer regressions passed all 56 tests.
- PR 1 follow-up tests cover valid/cyclic reporting chains, listener
registration failure, multiple subscribers, global/company disable,
re-enable, unload, and startup with blocked callbacks.
- Database coverage includes rollback, journal failure, no-op
suppression, file saves/deletion, ordering/replay, owner/company denial,
independent-client commits, and real listener loss/reconnect.
- SDK/shared suites passed 1,059 tests during implementation.
CLI/delivery regressions also passed.
- Migration safety, map checks, diff whitespace, and local secrets/PII
scan passed.
- Hosted CI and security checks are fully green at `b296405e2`. Standard
Greptile and Apex both returned 5/5 with no unresolved findings.
- The broad local suite encountered an unchanged diagnostic test that
rejects the word "private" from an error stack. This worktree lives
under /private/tmp. The same unchanged diagnostic suite passed all 108
tests in the original checkout. Hosted full-suite checks provide
exact-head verification.

## Risks

- Apply migration 0347 before enabling a runtime subscriber. Runtime
notifications require session-level PostgreSQL LISTEN. Transaction-mode
poolers are unsupported for this opt-in feature.
- Database triggers share the mutation transaction. Journal failure
prevents configuration commits. Trigger work adds a journal insert to
relevant changes.
- Filesystem publication and SQL commit are separate boundaries. Direct
filesystem edits do not emit hooks. External clients must fence stale
revisions and retain conditional-write checks.
- The reader returns only that plugin's own instance secrets. Keep them
out of UI data, logs, plugin state, and agent files.
- No private transport integration or live remote computer qualification
is included.

## Model Used

OpenAI Codex, GPT-6, with reasoning, repository inspection, code
execution, and testing. The exact deployment model ID and context window
are not exposed in this session.

## 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 references)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [x] I have run tests locally and they pass (focused suites; broad
local limitation documented above)
- [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-16 07:53:04 -07:00
DottaandPaperclip b294abb9eb perf(server): append ordinary run events in one statement (#16569)
Ordinary run events now validate their binding, allocate a sequence and insert the decoded receipt in one atomic PostgreSQL statement. Native replay and retry-exhaustion transactions keep their existing behavior.

Verification: exact-head full CI/E2E, Superagent, legacy security and Apex 5/5 passed. PostgreSQL concurrency, rollback and payload controls pass. Controlled network-delay comparison reduced median event persistence from about192ms to48ms; no staging response-time claim is made before deployment.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
canary/v2026.1016.0-canary.11
2026-10-16 08:51:01 -05:00
DottaandPaperclip d75b3fc33f perf(daytona): observe command exit before log-follow closure (#16561)
Return completed Daytona session commands after recorded exit and a final log snapshot. Keep bounded early status reads, command deadlines, late callback fences and stream recovery.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
canary/v2026.1016.0-canary.10
2026-10-16 07:09:30 -05:00
DottaandPaperclip 43ffe8ce95 perf: skip conditions for unselected tool profile entries (#16556)
Check exact tool profile selectors before evaluating conditions. Matched entries retain fresh policy condition checks and existing approval, deny, binding, and decision-phase behavior.

Validation: 70 affected cases pass with zero skips. Full repository typecheck and build pass. A balanced same-database comparison over 47,931 entries preserves all decision JSON and reduces 200 decisions from about 1.05 seconds to 0.40 seconds. Fresh exact-head full CI, E2E, Superagent, legacy security, and Apex 5/5 passed with no unresolved findings.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-16 05:59:23 -05:00
DottaandPaperclip 8556539364 perf(adapter-utils): batch remote GitHub launcher staging (#16552)
Stage the nine managed GitHub launcher files through one bounded control command. Preserve per-file locks, content verification, atomic replacement and control-channel dispatch.

Real Daytona staging decreased from about one second to 0.24–0.34 seconds. Full CI, E2E, Superagent and Apex 5/5 passed on the reviewed head.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-16 04:59:21 -05:00
8f1cb11a03 fix(workspaces): name the owner action when a shared source is restricted (#16545)
A reused task workspace in a shared project checkout keeps its source
denial when another task's restricted worktree shares that checkout
(#16095 deliberately does not clone over existing workspaces). The run
then failed as an opaque `setup_failed` 403 and the task was blocked
with a generic "no live execution path" notice.

Convert that exact denial into a `configuration_incomplete` failure
with reason `workspace_source_restricted`, give the task notice and the
recovery action the owner step, and keep this pre-dispatch owner action
out of the unexpected Sentry run-failure stream. The reason and
predicate live in a dependency-free module. Access checks are unchanged.

Co-Authored-By: Paperclip <Paperclip-Paperclip@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
canary/v2026.1016.0-canary.8
2026-10-16 01:33:53 -07:00
DottaandPaperclip f1d4bc1617 perf(slack): resolve only signing secret during webhook checks (#16548)
Resolve only the current Slack signing secret during workspace checks and authenticated setup evidence. Preserve full runtime credential resolution, company bounds, rotation, and auditing.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
nightly/v2026.1016.0-nightly.0 canary/v2026.1016.0-canary.7
2026-10-16 03:05:05 -05:00
Devin FoleyandPaperclip 78b833c495 Dispatch routine and monitor work at saved deadlines (#16514)
## Thinking Path

> - Paperclip manages work that agents run for a company.
> - Routines and issue monitors store their next run time in the
database.
> - The app currently waits for the heartbeat interval to find that
work.
> - This can delay work after a workspace wakes before its saved
deadline.
> - This pull request uses the existing work coordinator to schedule
each known deadline.
> - It also parses each cron expression once, so sparse schedules do
less work on the event loop.
> - Agents can start due work sooner while the existing execution checks
stay in place.

## Linked Issues or Issue Description

**What happened?**

Routine and issue-monitor dispatch depends on the heartbeat scan
interval. A workspace can wake early and still wait for the next scan. A
sparse cron search also parses the same expression for every minute it
checks. In a local annual-schedule test, the deadline callback ran on
time but schedule calculation delayed the routine record by 13.6
seconds.

**Expected behavior**

Use a saved future deadline to request the existing dispatch scan.
Restore deadlines at startup and update them after a producer
transaction settles. Do not let future timers keep an idle workspace
active. Parse a cron expression once for each search.

**Steps to reproduce**

1. Set an active routine and an assigned issue monitor to the same
future deadline.
2. Choose a deadline between heartbeat scans. Use an annual routine
expression to exercise a sparse search.
3. Compare the saved deadline with the routine firing and agent start
times.

**Paperclip version or commit**

Source checkout based on c7ea5c0216.

Related public changes: #16215 reports routine deadlines for external
wake. #8034 caches time-zone formatters in the cron search. This change
reuses those contracts.

## What Changed

- Register separate routine and issue-monitor workers with the existing
delivery coordinator after startup housekeeping.
- Restore the earliest eligible future deadline from the database after
each scan.
- Notify the matching worker when routine or monitor writes settle,
including the native `set_task_monitor` tool. Keep the heartbeat
interval as a recovery scan for blocked or externally written work.
- Retain the scan-start cutoff so a deadline crossed during a slow scan
receives an immediate follow-up scan.
- Retain the coordinator gates for standby, idle drain, and shutdown.
Future timers do not count as active work. Bound expired deadline
retries during idle reconciliation so a paused worker cannot spin.
- Parse a cron expression once during a next-run search. Skip impossible
UTC dates before time-zone formatting. Preserve the existing calendar
and time-zone matching.
- Add clock, transaction, failure, SQL eligibility, and producer
notification tests. Document the scheduler behavior.

## Verification

- `pnpm -r typecheck`: passed.
- `pnpm build`: passed.
- Full CI, browser tests, and security scan: passed on final head
9fc857dcec. Greptile Apex is 5/5 with no
unresolved comments.
- Final focused startup, deadline, routine, SQL eligibility, native
monitor, cron, and coordinator tests: 159 passed across seven suites.
The crossed-deadline and native-notification regressions failed before
their fixes.
- CI caught the existing startup suppression assertion. It failed
locally before restoring the outer timer-dispatch guard; all 38 startup
tests now pass.
- A canceled-drain regression also failed before the fix. Future timers
keep their saved time; only expired deadlines use the bounded idle
retry. Server typecheck and build passed again.
- The idle-drain regression reproduced 22 checks in 20 ms before its
fix. After the fix, 43 coordinator and related worker tests passed.
Server typecheck and build passed again after the review fixes.
- The broad local `pnpm test:run` attempt reported existing
host-sensitive failures. Two Git fixture tests fail identically on
unchanged c7ea5c0 because the host default `main` leaves an extra branch
in an intended master-only remote. Both pass with a command-scoped
`init.defaultBranch=master` setting. No Git settings were changed on
disk.
- A diagnostics test also fails on both unchanged master and this branch
because its broad `private` substring assertion matches the canonical
macOS temporary source path in an error stack. A local Anthropic hire
assertion failed only in the broad run; all 70 tests in that file pass
on both source revisions when run separately. Its broad-run cause is not
determined. These failures remain recorded. The full exact-head CI
matrix passed; the broad local development attempt was then stopped.
- An earlier full run was interrupted after PostgreSQL reported host
shared-memory exhaustion. One retry was stopped when timing required the
UTC optimization. Acceptance then used an owned database on the existing
local PostgreSQL service. No shared services were stopped or reset.
- Local dispatch: routine record at +0.102 s, routine agent start at
+0.171 s, monitor agent start at +0.159 s. Both scripted agents
succeeded using real run-scoped API authentication. No model provider
was called. No duplicate runs occurred during the 70-second follow-up.
Platform: macOS, Node 25.6.1, isolated database on local PostgreSQL 16,
source checkout. The final run used a fresh database and a clean
checkout of 9fc857dcec.
- The first timing run exposed the repeated cron parsing described
above. A diagnostic run showed the timer fired 1 ms after the deadline
while the annual search blocked the event loop. Parsing once reduced
local dispatch to 5.9 seconds, which still missed the five-second test
target. The next change skips impossible UTC dates before formatting. A
second regression failed before that fix. The final timing result and
duplicate check passed.
- A separate acceptance setup failed because the direct local server had
no agent signing key. That owned fixture was removed. The retry uses an
ephemeral key in its isolated process environment.
- Owned fixtures were deleted, the local runtime exited normally, and
the disposable databases were dropped. Shared services remain running.
- No production deployment or npm publication is part of this PR.

## Risks

- Known deadlines now trigger a scan between heartbeat ticks. Existing
atomic claims, suppression checks, budgets, and execution rules still
decide whether work runs.
- Direct database writers rely on the heartbeat recovery scan. Overdue
blocked work also retains that recovery interval to avoid a tight retry
loop.
- This is not a hard real-time guarantee. Provider startup, queue load,
and other synchronous work can still delay agent execution.
- No database migration or API schema change. Revert this commit to
restore interval-only dispatch.

## Model Used

OpenAI GPT-6 (Codex), with reasoning, repository tools, terminal
execution, and automated tests. The session does not expose a more
specific model version 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 and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass for the changed areas;
full-suite CI and local exceptions are documented above
- [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>
canary/v2026.1016.0-canary.6
2026-10-15 22:44:17 -07:00
DottaandPaperclip 82a12f1a8b perf(skills): batch project source authorization reads (#16536)
Batch project-source read authorization through the existing decideReads path. Keep company scope, source union rules and fresh permission decisions.

The local80-project fixture reduces select builders from322 to4 and complete legacy preparation from689ms to128ms. All88 affected cases and both fresh-database cases pass. Full current-head CI/E2E, security and Apex5/5 pass with no unresolved findings. This is component proof; hosted latency still needs verification.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
canary/v2026.1016.0-canary.5
2026-10-16 00:42:37 -05:00
DottaandPaperclip 185483e090 perf(slack): keep authorization revision rows inside PostgreSQL (#16527)
Compute Slack authorization revisions in one PostgreSQL statement while retaining all existing scope and invalidation inputs. Return one ordered digest instead of materializing retained profile rows in Node.

Validated with 19 affected cases, full typecheck/build, final-head CI/E2E/Superagent, and Apex 5/5.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
canary/v2026.1016.0-canary.4
2026-10-15 22:31:46 -05:00
github-actions[bot]andlockfile-bot 560d895306 chore(lockfile): refresh pnpm-lock.yaml (#16516)
Auto-generated lockfile refresh after dependencies changed on master.
This PR only updates pnpm-lock.yaml.

Co-authored-by: lockfile-bot <lockfile-bot@users.noreply.github.com>
2026-10-16 02:57:47 +00:00
DottaandPaperclip 90924d61b3 fix(tools): batch profile selector validation (#16521)
Batch company-scoped profile selector validation while preserving both existing write boundaries and exact selectors.

Verified with 81 affected local cases, full repository typecheck/build, current-head CI/E2E, Superagent and Apex 5/5; no unresolved findings.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
canary/v2026.1016.0-canary.3
2026-10-15 21:12:10 -05:00
DottaandPaperclip 0e58f29eb7 fix(observability): trace pre-dispatch controller waits (#16515)
Measure usage startup, MCP handoff, dispatch admission, and budget reservation with the existing endpoint-gated tracer. Preserve outer attempt context across asynchronous provider dispatch.

Validated with 135 focused tests, full typecheck/build, fresh full CI/E2E, Superagent, and Apex 5/5 on 9b28865bb3.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
canary/v2026.1016.0-canary.2
2026-10-15 20:30:05 -05:00
Nicky LeachandPaperclip fa9f3f8a08 feat(runner): support preconfigured user homes for Codex and ACPX (#16486)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Runner launches the selected provider and keeps durable session
state.
> - Runner currently prepares private provider homes for each session.
> - Operators can already have working login, skills, and MCP
configuration on the execution machine.
> - This PR adds an explicit mode that uses that existing setup.
> - Runner keeps its task bridge and session ownership while leaving the
user's configuration in place.

## Linked Issues or Issue Description

**Problem or motivation:** Rebuilding a provider home is unnecessary
when the execution account is already configured.

**Proposed solution:** Add `homeMode: "preconfigured"` to Runner adapter
config and runnerd transport options. Keep `"isolated"` as the default.
Initially support native Codex and ACPX Claude.

**Alternatives considered:** Continue copying user configuration into a
private home, or add an approval workflow. Both add work outside this
feature's scope.

**Roadmap alignment:** This extends the existing cross-provider Runner.
It does not add another runtime.

Related work: #14056 covers managed Codex credentials. #14474 covers
command environment delivery. This mode uses user-owned credentials and
retains the existing launch boundaries.

## What Changed

- Add a closed v7 launch contract for the selected home mode. Existing
contracts retain their behavior.
- Keep homeMode under operator control. Reject task overrides and
agent-authenticated changes, including removal through replacements,
adapter changes, or rollback. Ignore stored task values and exclude it
from automatic hire inheritance.
- Skip managed AI login, assigned-skill materialization, and external
MCP setup in preconfigured mode. Preserve the Runner task/completion
bridge.
- Keep protocol state and provider lifetime fences scoped to the Runner
session. Different sessions can share a home.
- Fail preconfigured ACPX planning/read-only launches before provider
admission until a read-only user-home launch is qualified. Use isolated
mode or native Codex for planning.
- Reject ACPX approval requests. Keep native Codex on `never`. Local
permission rules and hooks remain effective. Initially require the
default Runner ACPX `approve-all` setting; other Runner policies are
unqualified in this mode and fail before launch.
- Keep the internal ACPX bridge in a stable session namespace,
preserving an existing user MCP server named `paperclip`.
- Reject preconfigured ACPX Codex before launch until a session
permission profile can enforce Runner-state denial. Remove the
unprotected full-access mode switch; use native Codex or isolated mode
for that model family.
- Bump Pi to profile 23 because its attestation covers shared Runner
source changed here. Preserve profile 22 for historical recovery, and
update Docker/package verification inputs.
- Fence recovery on mode and home bindings. Fail missing
provider-history recovery without starting a replacement session.
Exclude the external Codex home from backups and credential cleanup.
- Discover remote HOME on the execution machine. Do not project
controller HOME or XDG paths onto it.
- Separate task instructions from Codex ACP isolation and update the
generated provider digest.
- Keep remote configured-environment manifests consistent when dropping
controller paths. Preserve exact Claude model IDs through per-launch
environment settings, without editing the user home or patching the
provider.

## Verification

- Targeted local checks cover native contracts and launch allowlists,
user-config preservation, provider admission, permissions, session
recovery, remote environment projection, heartbeat preparation, and
operator-only configuration. Runner, shared, and server TypeScript
checks and the Rust binary build pass; generated ACPX profiles are
current.
- Following the latest master integration, all 757 server
configuration/executor tests and 115 Runner admission/recovery tests
passed. Additional review-fix checks passed: 103 planning contract/host
tests, 78 provider-backend tests, 95 route/preparation/inheritance
tests, 43 task-validator tests, and 43 workspace-policy tests. Counts
describe separate runs and can overlap.
- The real pinned Claude ACP/SDK regression reproduces exact-model
aliasing without the fix (`claude-sonnet-5-5` becomes `sonnet`) and
passes with the fix. All 89 adapter tests pass. User settings remain
byte-for-byte unchanged.
- Localhost-only Claude SDK probes with synthetic credentials verify the
private bridge runs, local `ask` reaches the existing reject callback,
local `deny` blocks without a callback, and a user MCP server named
`paperclip` remains available. No user model allowlist is required.
- Review regressions cover fail-closed provider admission, saved-mode
recovery before routing, controller-home path removal without
configured-environment manifest drift, pre-launch rejection of
unqualified read-only ACPX launches, and denial of task/agent changes to
the operator-owned home mode.
- Before rebase, `d4d4ee2aebe4cb88c4f4d9ef8e22fed4b36b87be` received
standard Greptile 5/5 with no unresolved threads. Prior Apex findings
are fixed and covered by regressions. No manual Greptile retrigger is
requested for this rebase, per reviewer direction.
- The prior PR head and master hit the same GitHub runner encoding crash
in workspace-b on three PR attempts, including diagnostic logging
([prior PR
CI](https://github.com/paperclipai/paperclip/actions/runs/38818474563/job/116714538405),
[prior master
CI](https://github.com/paperclipai/paperclip/actions/runs/38819528748/job/116712423193)).
Rebased onto `origin/master` at `c3ffa3f0dd` to pull in its bridge-test
index correction. The rebased head
`edd153db410bea063b645e0000aa68649f287b00` passes all CI checks ([CI
run](https://github.com/paperclipai/paperclip/actions/runs/38824676914)),
including workspace-b. Greptile's automatic standard review is 5/5 on
this exact head, with no unresolved threads; no manual review was
requested.
- Local workspace-b investigation passed 911 shared tests and 3,868
tests across the remaining 15 workspace projects. The catalog run
separately hit an existing ignored
`.agents/skills/design-guide/SKILL.md` description-budget violation in
this checkout; that file is untracked, outside the PR, and left
untouched. Repeating shared tests with CI reporting enabled also passed.
- Rebase verification: the final Git tree exactly matches the previously
reviewed branch combined with current master. All 192 Runner
admission/contract tests, 61 server preparation/provider tests, and 68
bridge tests pass, as do Runner/server TypeScript and generated-profile
checks. The real Claude adapter fixture hit an `ENOTEMPTY` cleanup race
on its initial run; all 89 adapter tests passed on retry.
- Apex on rebased head `edd153db` returned 2/5: ACPX Codex selected full
access without enforcing Runner-state denial, and agent configuration
replacement could remove the operator mode. Both are fixed in
`26e14dbbf`. Preconfigured ACPX Codex is temporarily unqualified;
re-enabling it requires session permission enforcement on thread/turn
operations and a real filesystem-denial probe. The config guard covers
effective replacements, adapter changes, and rollback while allowing
ordinary agent merges and operator removal. All 190 focused Runner
tests, 103 server tests, both typechecks, and generated-profile checks
pass. Standard review and CI passed before the next Apex request.
- CI on `26e14dbbf` passed 1,967 Runner-shard tests but hit the
previously observed `ENOTEMPTY` fixture cleanup race after successful
Claude model admission. `eb83284fb` adds bounded filesystem retries to
that fixture's teardown; all 87 adapter tests pass. Assertions and
production cleanup behavior are unchanged. Head
`eb83284fb42ee19a15d9a93b106c1bbb07e1695e` passes all 59 CI checks ([CI
run](https://github.com/paperclipai/paperclip/actions/runs/38829500857))
and standard Greptile at 5/5 with no unresolved threads. Apex also
completed at **5/5** on that exact head ([Apex
result](https://github.com/paperclipai/paperclip/pull/16486#issuecomment-6201559527)).
All review threads are resolved, the branch has no merge conflicts, and
the PR remains unmerged.
- No live paid-provider or remote-machine smoke was performed. The
lockfile remains unchanged under the repository's refresh-bot policy;
local dependency installation resolved the changed patch hash.

## Risks

This explicit opt-in grants Codex tools access to the execution home
(read-only in planning), including credentials. Native Codex separately
denies Runner-owned protocol state. ACPX Claude retains its existing
provider trust boundary, and the isolated default is unchanged.
Providers may refresh credentials and update their own metadata.

User configuration can block tools or the completion reporter. Existing
blocked results and failure diagnostics remain the recovery path. Edit
the local settings and retry; no new approval workflow is added.

External provider history remains on the execution machine and is not
backed up by Runner. Remote HOME discovery requires `getent` and fails
explicitly when unavailable. Other harnesses need separate
qualification.

The Codex ACP bundle digest changes. Existing sessions tied to the old
bundle may need a fresh session. No database migration is required.

## Model Used

OpenAI Codex, GPT-6, with reasoning and code execution. The exact
runtime model ID was not exposed.

## 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 described the issue in-PR following the feature issue
template
- [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
- [x] I have run targeted 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 risks above

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
canary/v2026.1016.0-canary.1
2026-10-15 17:57:58 -07:00
Devin FoleyandPaperclip c7ea5c0216 perf(server): skip settled committed finalizations in unscoped passes (#16485)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - On restart, `/api/health` reports `starting` until startup recovery
finishes, and deploys wait for it
> - Native recovery and the orphan reap each run an unscoped native
finalization reconciliation pass, and readiness waits for both
> - The pass selects every committed finalization with a result,
including runs that ended long ago, and spends dozens of sequential
queries on each one
> - On a busy instance this is about one minute per pass, so about two
minutes of every restart, and the periodic recovery repeats it on every
tick
> - This pull request skips settled committed finalizations in unscoped
passes, and keeps a small rotating upkeep page so their upkeep still
runs
> - The benefit is faster restarts and less steady database load, with
no change for unsettled work

## Linked Issues or Issue Description

Related: #16330 and #16329 (startup and reconciliation timing).

**What existing behavior does this improve?**
Startup recovery time and steady-state recovery load.

**Current behavior**
- The unscoped candidate scan in `reconcileNativeFinalizations` returns
every native finalization with a result and phase other than
`terminal_failure`. Nothing excludes rows that are already settled.
- Each committed row then takes the full per-row path: pending-effect,
assessment, evidence and decision reads, presentation repair (twice),
the workspace barrier read, a `finalizeNativeRun` re-read of the
committed coordinator, and workspace settlement (lease release check and
agent status update). That's about 40–60 sequential round trips per row.
- Startup runs the unscoped pass twice (native recovery, then the orphan
reap), and readiness waits for both. The periodic orphan reap runs it
again on every tick.
- On one instance, all 268 candidates were committed finalizations whose
runs had ended. Native recovery took 50–80 s and the orphan reap 47–80 s
on every restart, with database round trips of about 3.4 ms.

**Proposed behavior**
An unscoped pass skips a committed finalization when:
- its run is `succeeded`, `failed`, `cancelled` or `timed_out`, and
- the run finished more than an hour ago, and
- its decision has no `pending` or `failed` status-decision effect.

Each unscoped pass also includes up to 10 such settled rows in a
rotating order, so the presentation and evidence upkeep still reaches
every row over time. Scoped passes (explicit run IDs) are unchanged.

**Reason and benefit**
These settled rows have nothing left to finish. Recent rows, unfinished
runs and rows with undelivered effects stay in every pass, so
crash-window repairs and effect delivery behave as before. Startup and
periodic passes no longer scale with the full history of committed runs.

**Breaking changes**
None. A late repair on a settled row can be delayed until its turn in
the upkeep rotation instead of the next pass.

## What Changed

-
`server/src/services/native-runtime/native-finalization-reconciler.ts`:
- `settledCommittedFinalization(now)`: the settled predicate above,
including a `NOT EXISTS` on undelivered `status_decision_effects` for
the same company, issue and decision.
- The unscoped candidate scan adds `NOT settled OR run in the upkeep
page`.
- `nextSettledCommittedUpkeep`: up to
`NATIVE_SETTLED_COMMITTED_UPKEEP_PER_PASS` (10) settled run IDs after an
in-process cursor, ordered by run ID, wrapping at the end.
- New constants `NATIVE_SETTLED_COMMITTED_GRACE_MS` (1 hour) and
`NATIVE_SETTLED_COMMITTED_UPKEEP_PER_PASS`, plus
`resetSettledCommittedUpkeepForTests`.
- New
`server/src/__tests__/native-finalization-settled-candidates.test.ts`.
- `doc/execution-semantics.md`: describes the settled rule and the
upkeep rotation.

## Verification

- `pnpm exec vitest run
src/__tests__/native-finalization-settled-candidates.test.ts` (in
`server/`): 4 passed. The test mocks the timing hook to capture the
candidate count and stop the pass after the scan:
- 25 settled rows give 10, 10, 5, then 10 again over four passes
(rotation and wrap).
- A row finished 10 minutes ago, a running row, and a terminal row
without `finished_at` are all candidates.
  - A settled row whose decision has a `failed` effect is a candidate.
  - A scoped pass with an explicit run ID still selects a settled row.
- `pnpm exec vitest run` for `native-finalization-recovery`,
`legacy-finalization-regression`, `native-run-finalizer-telemetry`,
`native-workspace-finalizer`, `native-finalization-reconciler-timing`,
`external-chat-wait.integration` and `native-status-arbiter-corpus`: 316
passed.
- `heartbeat/recovery.test.ts`: the 8 tests that do not need a database
passed. The 20 database tests were skipped locally because embedded
PostgreSQL could not start (the host's shared-memory segments were
exhausted by other processes). CI runs them.
- `pnpm typecheck` in `server/`: passed.

## Risks

- A settled row that needs a late repair (for example a missing chat
presentation) waits for its turn in the upkeep rotation, up to
`ceil(settled rows / 10)` passes, instead of the next pass.
- The upkeep cursor is in memory. After a restart the rotation starts
from the beginning; the work is idempotent.
- A run left `running` with a committed finalization is never considered
settled, so it is still projected before the reaper's active-run loop.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), 1M context, through Claude Code
with tool use (shell, file edits, test runs, database and log analysis).
A separate Claude agent mapped the recovery call tree.

## 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>
canary/v2026.1016.0-canary.0
2026-10-15 16:42:15 -07:00
Nicky LeachandPaperclip c3ffa3f0dd test(adapter-utils): fix feedback request index in bridge round trip (#16507)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Sandbox agents use the callback bridge to call the Paperclip API.
> - The bridge round-trip test checks request delivery and credential
removal.
> - #12785 added an accepted API-key request before the feedback
request.
> - The feedback assertions still read request 2, which is now the
GitHub credentials request.
> - This PR checks request 3 and verifies that all four accepted
requests reached the host.
> - This restores the shared test gate while retaining the Unicode
payload and token checks.

## Linked Issues or Issue Description

Related PR: #12785 introduced the stale feedback index. The reported
failure appears on #16486 and master. Searches for `workspaces-b`,
`sandbox-callback-bridge`, `surrogate`, and `EncoderFallbackException`
found no duplicate fix.

**What happened?**

The `General tests (workspaces-b)` job fails on PRs and master. The
hosted PR job and AWS master job both report
`System.Text.EncoderFallbackException` for an unpaired `\uD83D`. The
runner exits during the test step and does not upload that step's log.

The unchanged master test reproduces a deterministic assertion failure
at the feedback request. It compares `/runtime-tools/github/credentials`
with `/api/companies/company-1/agent-commentary` and prints a diff
containing 5,000 crying emoji. The assertion output is the likely
trigger for the runner's Unicode logging crash.

- [Reported PR
job](https://github.com/paperclipai/paperclip/actions/runs/38818474563/job/116719880033)
- [Master job with the same runner
exception](https://github.com/paperclipai/paperclip/actions/runs/38821128038/job/116717690951)

**Expected behavior**

The test checks the actual feedback request, verifies its full payload,
and confirms that its authorization header is removed. Both shared CI
lanes complete normally.

**Steps to reproduce**

1. Check out master at `c4a16cbe981f96e2756c7014103c88da67e5514f`.
2. Install dependencies with pnpm 9.15.4.
3. Run `node_modules/.bin/vitest run
packages/adapter-utils/src/sandbox-callback-bridge.test.ts -t
'round-trips localhost bridge requests'`.
4. Observe the feedback assertion reading the preceding GitHub
credentials request.

**Paperclip version or commit**

Master at `c4a16cbe981f96e2756c7014103c88da67e5514f`.

**Deployment mode**

Built from source. GitHub-hosted PR CI and AWS post-merge CI.

## What Changed

- Assert that four accepted requests reached the host.
- Check feedback delivery and authorization stripping on request 3.
- Retain the large Unicode payload and all existing authentication
checks.

## Verification

- Unchanged master: the focused round-trip test fails at the stale
feedback index.
- Fixed branch: all 68 tests in `sandbox-callback-bridge.test.ts` pass
with real localhost gateways.
- `pnpm --filter @paperclipai/adapter-utils typecheck` passes.
- `git diff --check` passes.
- Exact CI group: `node scripts/run-vitest-stable.mjs --mode general
--group general-workspaces-b` passes (4,800 tests passed, 28 skipped
across 17 projects).
- `pnpm --filter @paperclipai/adapter-utils build` passes.
- Greptile reviewed current head
`3de15d3374354809ea84ca4df5f93c2473715f02`: 5/5, success, no comments or
actionable findings.
- All GitHub checks pass on current head
`3de15d3374354809ea84ca4df5f93c2473715f02`: 55 successful checks and 2
intentional skips, including `workspaces-b`, build, typecheck, Runner
verification, and e2e.
- Full local `pnpm test:run` was started and stopped after the CI test
lanes passed; it did not complete locally. No worktree-owned test
processes remained afterward.
- Full local typecheck and build were attempted. Both stop at the Runner
Rust commands because `cargo` is absent from the shell PATH. The changed
package typecheck passes. The complete repository typecheck and build
pass in CI.
- The first sandboxed local reproduction could not bind localhost
(`EPERM`). It was repeated with listener permission and reached the
deterministic assertion failure described above.

## Risks

Low risk. This changes three test lines. Runtime behavior, credentials,
workflow settings, and payload coverage stay the same. The GitHub runner
Unicode logging defect remains upstream; this fix removes the incorrect
assertion that triggers it in this test.

## Model Used

OpenAI GPT-6 in Codex, with reasoning, repository inspection, and
command execution. The exact runtime model identifier and context window
size are not exposed in this session.

## 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
#` 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>
canary/v2026.1015.0-canary.24
2026-10-15 16:32:20 -07:00
8aaef59339 fix(pi-local): add stdout compaction to stop quadratic run-log growth (#10249)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The `pi_local` adapter runs the `pi` CLI in `--mode json` and
records its NDJSON stream as a run-log per turn
> - In JSON mode `pi` emits one `message_update` event per streaming
token, each carrying the **full cumulative assistant message**, so a
single turn's run-log grows quadratically with output length (measured
~77 MB for one thinking-model turn)
> - The root cause is in `pi`, but the Pi project declined to add a
compact mode (earendil-works/pi#3905, closed the same day), so Paperclip
has to filter on the consumer side
> - This PR adds a dedicated, config-gated compactor at the `pi_local`
stdout boundary (`stdoutLogMode`: `raw` | `compact`, default `compact`)
that strips the cumulative snapshots while preserving streaming deltas,
terminal events, and parser/UI output
> - The benefit is linear run-log growth (~150× smaller in practice)
with **no change** to `parsePiJsonl` results, token usage, or live
typing — keeping storage and replay bounded

## Linked Issues or Issue Description

- **Refs #5699** — same root cause ("full accumulated partial state" →
46 MB log, per that PR). This PR takes a **different, single-concern
approach**: a dedicated `stdout-compaction.ts` module with an explicit
`stdoutLogMode` config (default `compact`, fail-safe fallback to `raw`),
scoped to `pi_local` only. #5699 instead filters inline in
`buffered-on-log.ts`, bundles an unrelated `claude-local` change, and is
currently **conflicting** + stale (last updated 2026-05-14). Happy to
coordinate with @ukint-vs on a merged direction.
- **Refs earendil-works/pi#3905** — proposed adding a compact JSON mode
upstream in `pi`; closed by the maintainer, which is why this filters on
the Paperclip side rather than the producer.
- **Related #1846** (run-log/retention TTL) — complementary: this PR
reduces what's stored per run; #1846 addresses how long rows live.
- No public GitHub issue exists that is an exact duplicate of the
`pi_local` quadratic-growth symptom; the problem is tracked via #5699
above. (No internal/instance-local references used.)

## What Changed

- **`packages/adapters/pi-local/src/server/stdout-compaction.ts`** (new,
123 lines) — `createPiStdoutLogCompactor(mode)`. `raw` passes lines
through unchanged; `compact` strips the cumulative
`message`/`assistantMessageEvent.partial` from `message_update` events
while preserving streaming deltas, terminal events, and passthrough of
unknown event types. Default mode `compact`; an invalid explicit mode
fails safely to `raw`.
- **`packages/adapters/pi-local/src/server/execute.ts`** — read the mode
via `resolvePiStdoutLogMode(config)` and apply the compactor at the
stdout → `onLog` (run-log) boundary.
- **`packages/adapters/pi-local/src/index.ts`** — expose the
`stdoutLogMode` config option (documented inline: `string`, optional,
default `"compact"`).
- **`packages/adapters/pi-local/src/ui/parse-stdout.ts`** — confirm the
stdout parser consumes compacted output unchanged.
- **Tests** — new `stdout-compaction.test.ts` (359 lines) covering
modes, edge cases, and `parsePiJsonl` replay equivalence; updated
`execute.remote.test.ts`.

## Verification

- **Unit:** `pnpm --filter @paperclipai/adapter-pi-local exec vitest
run` → **121/121 passing (12 files)**.
- **Typecheck:** `pnpm --filter @paperclipai/adapter-pi-local exec tsc
--noEmit` → **clean, 0 errors**.
- **Real-data replay:** a real `pi --mode json` stream (`zai/glm-5.2`,
thinking=high) compacted line-by-line through this compactor:
- Raw **76.96 MB** → Compact **525.5 KB** = **99.33% reduction
(≈150×)**.
- 4503 lines in, 4503 out (**0 dropped**); **0** `message_update` with a
cumulative `.message` remaining; **4489** streaming deltas preserved.
- **Parser equivalence:** `parsePiJsonl(raw) ≡ parsePiJsonl(compact)` —
**0 errors** on both; identical `finalMessage` (13,369 chars); identical
`usage` (`inputTokens 2896, outputTokens 4491, cachedInputTokens 6848`).
Result, token accounting, and live-typing are unchanged.

## Risks

- **Behavioral shift:** default `compact` changes what is stored in
run-logs vs today. Operators who need the verbatim stream can set
`stdoutLogMode: "raw"`. Flagging for reviewer preference — happy to flip
the default to `raw` for upstream if that's preferred.
- **Ordering with secret redaction (#8027 / #9856):** compaction is
structural and runs at the stdout boundary **before** redaction; it only
removes cumulative duplicates and never alters token content, so it does
not change what redaction sees.
- **Unknown event types** pass through unchanged (forward-compatible
with future `pi` events).
- Low risk otherwise — the change is contained to the `pi_local`
adapter.

## Model Used

OpenAI `gpt-5.6-luna` — via pi's `openai-codex` provider (272K context
window). Used for implementation and for the real-data measurement in
Verification.

Reasoning/thinking: extended thinking enabled at level `high` (pi
`--thinking high`; maps to OpenAI reasoning effort). `gpt-5.6-luna` is a
reasoning-capable model.

## 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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: commitperclip[bot] <284812584+commitperclip[bot]@users.noreply.github.com>
2026-10-15 16:24:06 -07:00
Huanyi Xie c4a16cbe98 fix(plugins): follow local package path changes in dev watcher (#15247)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Local plugin development uses file changes to restart the plugin
worker.
> - A plugin can move to a different local package directory.
> - The watcher rejected every later registration for the same plugin
ID.
> - This pull request replaces the watcher when the resolved package
path changes.
> - The benefit is that current plugin files trigger reloads and
previous files stop doing so.

## Linked Issues or Issue Description

Refs: #14955. This change addresses its second observation: the watcher
keeps the previous local directory after its package path changes. The
issue also tracks stored configuration replay as a separate problem.

Related PRs: #11336 shares the lifecycle manager across routes and the
dispatcher. #14381 controls when production file watching is enabled.
#10100 orders configuration delivery. Those changes address other parts
of plugin reload behavior.

## What Changed

- Store the resolved package path with each watcher. Keep repeated
registrations of the same path as a no-op. Replace the watcher for a
different path.
- Cancel pending changes from the previous directory. Ignore late
change, error, and timer callbacks from a replaced watcher.
- Apply only the latest pending directory lookup. Manual registration,
unwatching, and shutdown invalidate earlier lookups.
- Stop a failed watcher without cancelling its pending replacement
lookup. Explicit unwatching, disable, unload, and shutdown still cancel
pending registrations.
- Stop the old watcher when the new directory is unavailable or the
plugin no longer has a local package path.
- Document the path-change behavior and add twelve regression cases.

## Verification

- `pnpm exec vitest run server/src/__tests__/plugin-dev-watcher.test.ts
server/src/__tests__/plugin-lifecycle-restart.test.ts`: 18 tests passed.
Regressions cover path changes, stale callbacks, lookup order, explicit
cancellation, and watcher errors during a pending replacement. The
current-watcher error case fails before the stop-versus-cancel fix; the
stale-watcher error control passes.
- A local smoke check used real Chokidar watchers and two real temporary
plugin directories. It confirmed that a pending old change is cancelled,
later old-directory changes are ignored, and a new-directory change
triggers one restart.
- `pnpm -r typecheck` passed on the path and lookup guards. The final
watcher-error fix passed `pnpm --filter @paperclipai/server exec tsc
--noEmit`.
- `pnpm build` passed on the initial path-change fix. The final source
passed `pnpm --filter @paperclipai/server build`.
- `git diff --check`: passed.
- Greptile reviewed `bef3182` at 5/5 with no new findings. The
watcher-error review thread is resolved. CI is still running.
- The full `pnpm test:run` suite was not repeated for this watcher-only
change. The affected suites ran locally. The broad test shards will run
in CI.

## Risks

- Low risk. The watcher follows the current registered path. An
unavailable replacement stops watching until another valid registration
arrives.
- Closing a filesystem watcher is asynchronous. Callback identity checks
prevent an old watcher from acting on its replacement.
- No database migration, API shape, or dependency change is required.

## Model Used

- OpenAI Codex based on GPT-6, with reasoning, tool use, and code
execution. The runtime does not expose the exact serving model ID or
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 (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
2026-10-15 16:13:35 -07:00
Aashish Mahato 457d3209b1 fix: name the real reason a connection request expires on reassignment (#14067)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - When an agent's selected AI account is unavailable, the issue thread
shows a `connection_intent` card. A person must reconnect the account or
pick another connection.
> - If the issue assignment changes while that card is still pending,
the server expires it. This is the ownership-change path in
`expireConnectionIntentsForOwnershipChange`.
> - The expired card showed the reason "The task assignment changed".
That text is wrong in two ways. It does not say that the connection
request was never answered, and it does not say the connection is still
missing.
> - Worse, the reason never reached the screen at all: the expired
branch of the card hard-codes "This request is no longer active.", so
any guidance the server stores is discarded. My first round of this PR
claimed the opposite, which was incorrect — the UI change is required
for the fix to be visible.
> - Issue #14040 reported the symptom: users saw a run fail with
"assignment" blame and no hint that a connection needed attention. It
happened on both `claude_local` and `codex_local` agents, so the message
— not one adapter — is the defect.
> - This pull request therefore fixes both ends: the server stores a
sentence that names what happened and what to do next, per kind of
request, and the card prints the stored reason instead of a generic
string.
> - The issue asks for a larger behavior change (hold reassignment until
the card resolves). That needs a product discussion, so this PR keeps
the scope to the message and where it is displayed.
> - The benefit is that the card tells the truth: the blocker is the
connection, not the assignment, and it says so on screen.

## Linked Issues or Issue Description

- Fixes: #14040

## What Changed

- `server/src/services/issue-thread-interactions.ts`:
`expireConnectionIntentsForOwnershipChange` now selects each pending
row's payload before expiring it and writes a per-request reason: "The
task was reassigned before the connection request was answered, so the
AI account is still not connected. Reconnect it or choose an available
connection." for AI-account intents, and the same sentence naming "the
`<serviceName>` connection" for tool intents. The subject is chosen from
`payload.purpose === "ai"`, falling back to "the requested connection"
for a payload that carries neither, because this one path expires both
kinds and the recovery step differs.
- `ui/src/features/connections/ConnectionIntentInteractionBody.tsx`: the
expired branch now renders `interaction.result?.reason` and keeps "This
request is no longer active." only as the fallback for results stored
before this change. The `superseded` outcome keeps its own wording. This
component is the shared body for both the compact task card and the full
thread card, so both views get the reason.
- `server/src/__tests__/issues-service.test.ts`: the expiry test now
expires a tool (`Notion`) and an AI (`claude_local`) intent in the same
run and asserts both stored reasons, so neither wording can regress into
the other.
-
`ui/src/features/connections/ConnectionIntentInteractionBody.test.tsx`:
new test renders an expired card whose result carries a reason and
asserts the reason is on screen and the generic fallback string is not.

## Verification

- `npx vitest run server/src/__tests__/issues-service.test.ts
ui/src/features/connections/ConnectionIntentInteractionBody.test.tsx` —
151/151 passed (127 server, 24 UI; 33.4s, Node 22.22.2, local). My first
revision of this body cited 168 for the same set; that count came from a
run on the wrong branch, and 151 is what this command prints on
`fix/connection-intent-expiry-reason`.
- The pinned server test drives the real path: it inserts pending
`connection_intent` rows plus a live `toolOauthStates` row, reassigns
the issue through `svc.update(...)`, then asserts both expired reasons
and the deleted OAuth state row.
- Mutation checks: forcing the AI subject for every row makes the expiry
test fail on the tool-row assertion (`1 failed | 126 skipped`, `expected
[ { …29 fields } ] to deeply equal [ ObjectContaining{…} ]`), and
deleting the `result.reason` branch in the card fails exactly `shows the
stored reason on an expired request` (`1 failed | 23 passed`). Both
revert clean. The reverse wording mutation (tool subject forced onto AI
rows) was not run, so that half of the test is pinned by its assertion
rather than by a demonstrated mutation.
- No schema, wire-format, or payload change. Only the free-text
`result.reason` value differs and the card now reads the field it was
already given.

## Risks

- Low risk. The value is display text on a terminal interaction outcome.
No code parses the old string, and no stored data is migrated; cards
that expired before this change keep their old text and now render it as
the stored reason they carry.
- The deeper request in #14040 (do not expire a pending
`connection_intent` on reassignment) is intentionally out of scope here
and needs a maintainer decision.

## Model Used

- Anthropic Claude (Opus 4.x, agentic CLI coding assistant with file
edit and shell tool use) assisted with the code change, the test
updates, and this PR text. A human reviewed the diff and the issue
context before submission.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` /
`Closes: #` / `Refs: #` OR (b) described the issue in-PR following the
relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (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-10-15 16:11:35 -07:00
Phorel e15435b651 fix(cli): accept npm 12 array output when resolving published version (#14000)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The CLI `install` and `update` commands resolve the published
version with `npm view paperclipai@<spec> version --json`
> - npm 12 changed the output of this command. It wraps a single value
in an array: `["2026.916.1"]` instead of `"2026.916.1"`
> - `parseResolvedVersion()` accepts only a bare JSON string, so it
throws "npm returned an unexpected version response"
> - Node 26 ships with npm 12, so a new install with the official
`install.sh` fails for these users
> - This pull request makes the parser accept a one-element string array
> - The benefit is that install and update work with npm 12 and with
older npm versions

## Linked Issues or Issue Description

Fixes #12226

## What Changed

- `cli/src/commands/install.ts`: `parseResolvedVersion()` returns the
value when npm returns a one-element array of strings. Other shapes
still throw the same error.
- `cli/src/__tests__/install-command.test.ts`: add a test for npm 11
output, npm 12 output, and plain-text output. The test also checks that
a multi-element array still throws.

## Verification

- Before the fix: on Node 26.8.2 with npm 12.0.2, `bash install.sh`
fails with `npm returned an unexpected version response: [ "2026.916.1"
]`.
- `npm view paperclipai@latest version --json` gives `["2026.916.1"]`
with npm 12.0.2 and `"2026.916.1"` with npm 11.20.0.
- `cd cli && npx vitest run src/__tests__/install-command.test.ts
src/__tests__/update-command.test.ts src/__tests__/channels.test.ts`: 40
tests pass.
- Without the source change, the new test fails with the same error as
the bug report.

## Risks

- Low risk. The change adds one accepted input shape. Existing accepted
shapes do not change.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use
(shell and file editing). It helped to find the cause, write the change
and the test, and run the tests.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] 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
- [ ] 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)
2026-10-15 16:08:11 -07:00
Prompt-SurferandClaude Opus 5.5 fa062b202d fix(ui): ignore MDXEditor initial markdown normalization in MarkdownEditor (#13915)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators edit agent instructions, routine descriptions, and other
Markdown fields in the web UI
> - All of these fields use the shared `MarkdownEditor`, which wraps
MDXEditor
> - MDXEditor re-serializes the initial Markdown when it loads, and it
sends the result through `onChange`
> - MDXEditor marks that event with the second `onChange` argument,
`initialMarkdownNormalize`, but `MarkdownEditor` ignores the flag
> - Thus each parent with dirty tracking shows unsaved changes before
the user types, and each parent must add its own interaction gate
(#12502, #13485)
> - This pull request drops the flagged event inside `MarkdownEditor`
> - The benefit is that no consumer reports unchanged content as
modified on load, and new consumers do not need their own gate

## Linked Issues or Issue Description

Refs #13472

Related:
- #12502 (merged) adds an interaction gate for agent instructions in
`AgentDetail.tsx`.
- #13485 (open) adds the same gate to the two routine description
editors. Its description names "a shared fix inside `MarkdownEditor`" as
a follow-up. This PR is that follow-up. It does not conflict with
#13485. The two changes can merge in any order.

## What Changed

- `MarkdownEditor.tsx`: read the second `onChange` argument
(`initialMarkdownNormalize`). When it is `true`, do not call the parent
`onChange`.
- The check runs after the empty-document mount guard. That guard still
restores content when the first event is empty.
- `latestValueRef` keeps the parent value. Thus the prop-sync effect
does not push the original Markdown back into the editor.
- `MarkdownEditor.test.tsx`: the MDXEditor mock can now send a flagged
normalization event on mount. A new test makes sure that the parent
`onChange` does not run and that the editor keeps its content.

## Verification

- `pnpm --filter @paperclipai/ui exec vitest run
src/components/MarkdownEditor.test.tsx`: 48 tests pass.
- The new test fails without the change ("expected vi.fn() to not be
called at all, but actually been called 1 times"). It passes with the
change.
- `pnpm --filter @paperclipai/ui exec vitest run` (full UI suite): 634
test files and 6653 tests pass.
- `pnpm --filter @paperclipai/ui typecheck`: passes with 0 errors.
- I read the MDXEditor 4.2.3 source (`dist/plugins/core/index.js`). The
flag is `true` only for the first export after the initial Markdown is
set. The editor sets it to `false` after that export. User edits and
`setMarkdown` echoes get `false`.
- Manual check: we tested the same guard in a real browser on an older
Paperclip base (MDXEditor 3.55), together with two changes that `master`
already has from #12502. We opened an agent Instructions tab with `-`
bullets. The Save bar and the unsaved-changes prompt did not show until
we typed. After an edit, one prompt showed for each navigation, and Save
still stored the edit.

Manual steps for a reviewer:
1. Put an instruction file with `-` bullets on an agent, for example `-
first` and `- second`.
2. Open the agent Instructions tab. Do not type.
3. Make sure that the Save bar does not show.
4. Go to another tab. Make sure that no unsaved-changes prompt shows.
5. Do the same steps with a routine description that has `-` bullets.

## Risks

- Low risk. The change affects only the first `onChange` event after the
editor loads.
- A consumer that uses the first event to learn the normalized Markdown
no longer gets it. We found no such consumer in the 24 files that render
`MarkdownEditor`, and the full UI suite passes.
- There are no API, database, migration, or visual changes.

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

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`), 1M-token context
window, run as a Claude Code agent with tool use (shell, file edits,
test runs). The model wrote the code, the test, and this description. A
human operator approved them before submission.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (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
docs describe this behavior)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending: CI runs after push)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending: runs after push)
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 16:06:53 -07:00
馨冉and馨冉 7c6d977929 fix(wake-queue): skip malformed queued comment ids so one bad id cannot block an issue's wakes (#13912)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The wake queue decides whether a finishing run's `@`-mention
actually wakes the mentioned agent, by checking the liveness of the
comment ids queued in the wake payload
> - `getQueuedCommentLiveness` passes every queued comment id straight
into an SQL `inArray`. Postgres rejects the whole statement on the first
malformed uuid, so one bad id (for example an 8-char short form written
into a wake payload) silently blocks every queued wake for that issue
> - The run that triggers the check still records `succeeded`, so from
the board the agent finished its work and the mentioned agent simply
never responds; the reporter hit this twice on the same long-lived issue
> - This pull request filters the queued ids with the existing
`isUuidLike` helper before the query, and skips the query entirely when
nothing valid remains
> - The benefit is that a malformed id is skipped instead of poisoning
the batch — it can never match a comment row anyway, so the downstream
liveness semantics do not change

Fixes: #13908

Related issue class (malformed ids reaching database queries): #887,
#7639, #8093, #11018, #13504 — this is the first report for the
wake-queue site.

## What Changed

- `server/src/modules/wake-queue/adapters/postgres.ts`: in
`getQueuedCommentLiveness`, filter `queuedCommentIds` through
`isUuidLike` before building the `inArray` predicate; when no valid uuid
remains, skip the query and return an empty row set.
- The post-processing loops now iterate the filtered id list, which is
equivalent because a malformed id can never match a comment row.

## Verification

- New embedded-Postgres regression tests in
`server/src/modules/wake-queue/adapters/postgres.test.ts`:
- a batch mixing one live comment id, one malformed short-form id, and
one valid-but-missing uuid returns only the live comment instead of
throwing;
- an all-malformed batch returns nothing live instead of generating
`inArray ()`.
- `./node_modules/.bin/vitest run
server/src/modules/wake-queue/adapters/postgres.test.ts` — 58/58 pass.

## Risks

Low risk. The only behavioral change is that a wake batch that would
previously fail the whole statement now dispatches the valid entries. A
malformed id previously matched no rows (the statement errored before
any filtering), so treating it as not-live is exactly what the old
downstream filters would have concluded had the query run.

## Model Used

Codex (OpenAI GPT-5, coding agent, tool use, repo-level context) —
assisted with locating the call site, authoring the guard, and the
regression tests; human review before submission.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (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: 馨冉 <xinxincui239@gmail.com>
2026-10-15 16:04:57 -07:00
lorenzozane 1b2632facc fix: serialize concurrent issue reference syncs (#13898)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The server keeps issue references synchronized with issue title and
description changes.
> - Concurrent updates can rebuild the same source issue's reference
mentions at the same time.
> - The replacement writes can then collide on the unique mention key
and return a 500 error.
> - This pull request serializes each source issue's reference rebuild
and adds concurrent regression coverage.
> - The benefit is that repeated unchanged descriptions complete without
duplicate-key failures.

## Linked Issues or Issue Description

Fixes #13731

## What Changed

- Lock the source issue row before replacing title and description
reference mentions.
- Add a 20-way concurrent regression test with repeated `COM-2`
references.

## Verification

- `pnpm exec vitest run
server/src/__tests__/issue-references-service.test.ts
packages/shared/src/issue-references.test.ts` passed with 10 tests.
- `pnpm --filter @paperclipai/server typecheck` passed under Node
22.22.1; the repository declares Node >=24.11 and emitted engine
warnings.
- `git diff --check` passed.

## Risks

- The row lock serializes rebuilds for one source issue and may reduce
concurrency for simultaneous updates to that issue.
- A theoretical lock-order risk remains for concurrent syncs of
different mutually referencing source issues. That path is outside this
fix and is not covered by the focused regression.
- Remote CI and Greptile review have not run yet.

## Model Used

OpenCode runtime, model `supercodes5/gpt-5.6-luna`; context window size
is not exposed. Repository tool use and reasoning 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
- [ ] 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-10-15 16:02:45 -07:00
cc549691a1 fix(claude-local): stop sealing max-turns failures as model_not_found (#13885)
<!-- Written in Simplified Technical English -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The `claude_local` adapter runs the Claude Code CLI and maps each
finished process to a terminal result. The `errorCode` in that result
decides which recovery path the issue takes.
> - `execute.ts` resolves the error code with one long ternary chain.
The order of the branches is the precedence rule.
> - The model-not-found branch sits before the max-turns branch, and it
is driven by `isClaudeModelNotFoundError()`, which regex-scans the run's
full stdout for the phrase `model_not_found` (also `model not found`,
`unknown model`, `model does not exist`).
> - The agent's own tool output is part of that stdout. A run that greps
adapter source, tails a log, or echoes an issue body about this defect
matches the detector on text the agent printed, not on text the provider
returned.
> - A real `subtype=error_max_turns` failure therefore never reaches
`max_turns_exhausted`. Downstream, `model_not_found` counts as a
configuration-incomplete failure, so the issue goes to `blocked` with a
wrong "configuration incomplete" notice instead of the normal max-turns
continuation.
> - This pull request gates the model-not-found branch behind the two
structured signals that the `providerQuota` and `transientUpstream`
branches above already apply.
> - The benefit is that a structured CLI signal outranks a regex scan of
untrusted output, so exhausted runs continue instead of blocking the
issue.

## Linked Issues or Issue Description

No public issue exists for this defect. I searched the open pull request
list for `packages/adapters/claude-local` and for the `model_not_found`
and `max_turns_exhausted` error codes, and found no duplicate or related
pull request. The description follows the bug template.

**What happened**

A run that fails with `subtype=error_max_turns` is recorded with
`error_code=model_not_found`.

In `packages/adapters/claude-local/src/server/execute.ts`, the
resolved-error-code ternary evaluates the model-not-found branch before
the max-turns branch:

```ts
: failed && isClaudeModelNotFoundError({ parsed, stdout: proc.stdout, ... })
? "model_not_found"
: failed && clearSessionForMaxTurns
? "max_turns_exhausted"
```

`isClaudeModelNotFoundError()` in `parse.ts` pushes `input.stdout` into
its candidate list and tests it against `CLAUDE_MODEL_NOT_FOUND_RE`. The
run's stdout holds the agent's own tool output, so any run that prints
the phrase poisons its own classification. Because the branch is first,
a genuine max-turns failure never reaches `max_turns_exhausted`.

This is not cosmetic. Downstream, `model_not_found` is treated as a
configuration-incomplete failure. The issue moves to `blocked` with a
misleading "configuration incomplete" recovery notice instead of the
normal max-turns continuation path.

**Steps to reproduce**

1. Run a `claude_local` agent on a task whose tool output contains the
literal string `model_not_found`. Grepping the adapter source or reading
a bug report about this defect is enough.
2. Let the run exhaust its turn budget, so the CLI emits
`subtype=error_max_turns`.
3. Observe that the recorded `error_code` is `model_not_found` and not
`max_turns_exhausted`.

**Expected behavior**

A run whose parsed result carries `subtype=error_max_turns` is
classified `max_turns_exhausted`, whatever phrases appear in its stdout.
A structured CLI signal outranks a regex scan of untrusted output.

**Paperclip version or commit**

`ghcr.io/paperclipai/paperclip@sha256:23090eb6`. The branch is rebased
onto current `master` (`b41ccf0`). The adapter is
`@paperclipai/adapter-claude-local` on the `engine=cli` lane.

**Deployment mode**

Self-hosted, Docker Compose.

## What Changed

- `packages/adapters/claude-local/src/server/execute.ts`
- Gate the model-not-found branch with `!clearSessionForMaxTurns &&
!poisonedPreviousMessageId`, the same guard the `providerQuota` and
`transientUpstream` branches ~30 lines above already apply. One line, no
new state.
- Add a comment that records why the order matters, so a later reorder
is a deliberate decision.
-
`packages/adapters/claude-local/src/server/execute.max-turns-classification.test.ts`
(new)
- A parsed `error_max_turns` result whose stdout carries the literal
`model_not_found` phrase still classifies as `max_turns_exhausted`.
- A real provider 404 (`API Error: 404 model not found`) still
classifies as `model_not_found`, so the guard does not hide the genuine
case.

Only the parsed-result path changes. The unparsed fallback path (`if
(!parsed)`) is left alone: neither guard variable is in scope there, and
a run with no parsed result has no structured max-turns signal to
honour.

## Verification

- `pnpm exec vitest run
src/server/execute.max-turns-classification.test.ts` in
`packages/adapters/claude-local`: 2/2 pass.
- Negative control: with the one-line guard reverted, the first test
fails with `expected "max_turns_exhausted", received "model_not_found"`.
The test pins the defect and not the surrounding code.
- `pnpm exec vitest run` for the whole adapter: 313 pass, 1 skipped, 3
fail. The 3 failures are in `acp.test.ts` and `acp.auth.test.ts`, all
with `claude_acp_node_unsupported` because the local machine runs Node
v22.23.1 and the repository requires Node >= 24.11.0. They fail the same
way on `master` without this branch, and they do not touch the CLI
classification path.
- `tsc --noEmit -p tsconfig.json` for the adapter: no errors.

## Risks

- Low risk. The change is one added condition on one branch of an
existing ternary.
- A real model-not-found failure still classifies correctly, because
neither `clearSessionForMaxTurns` nor `poisonedPreviousMessageId` is set
on that path. The second test covers this.
- The narrow case that changes behaviour is a run that is both out of
turns and pointed at a missing model. That run now reports
`max_turns_exhausted` first. The next run reports the model error
against a clean session, which is the same outcome the quota and
transient-upstream branches already accept.

## Model Used

- Provider: Anthropic Claude.
- Exact model ID: `claude-opus-5` (Claude Opus 5).
- Reasoning mode: extended thinking.
- Capability details: tool use for shell, file editing 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
- [ ] I have updated relevant documentation to reflect my changes (no
user-facing documentation covers adapter error-code precedence)
- [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: Builder <builder@nublo.net>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-10-15 16:01:40 -07:00
Ash Simons 550e959934 fix(task-watchdogs): deliver board instructions and stopped leaves in the wake payload (#13771)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task watchdogs wake an agent when a watched issue subtree stops
moving.
> - An operator can write `instructions` on the watchdog record. The
wake prompt has a slot for this text in the "Task Watchdog Mandate"
block.
> - The producer writes `customInstructions` and `stoppedLeaves` at the
top level of the wake payload. The normalizer reads them only from the
nested `taskWatchdog` object. Thus the normalizer drops both fields on
every watchdog wake.
> - The agent then sees "No board-supplied watchdog instructions"
although the record holds an instruction. The operator cannot see this
failure, and the agent cannot see it either.
> - This pull request puts both fields in the nested `taskWatchdog`
object.
> - The benefit is that operator instructions and the stopped-leaf list
reach the agent again.

## Linked Issues or Issue Description

No public issue exists. This is the bug description (bug_report
template).

**What happened?**
`watchdogWakeContext()` in `server/src/services/task-watchdogs.ts`
writes `customInstructions` and `stoppedLeaves` at the top level of the
wake payload. `normalizePaperclipWakePayload()` in
`packages/adapter-utils/src/server-utils.ts` reads
`payload.taskWatchdog` only. The two fields are lost. The "Task Watchdog
Mandate" block always shows "No board-supplied watchdog instructions.
Apply the mandate above." The "Terminal / stopped leaves to verify"
section never shows.

**Expected behavior**
When the watchdog record has non-empty `instructions`, the wake prompt
shows that text. When the subtree has stopped leaves, the prompt lists
them.

**Steps to reproduce**
1. Create a task watchdog on an issue. Set a non-empty `instructions`
value.
2. Let the watched subtree stop, so that the watchdog triggers.
3. Read the wake prompt of the watchdog agent. It shows the "No
board-supplied watchdog instructions" line.
4. `GET /api/issues/{id}/watchdog` still returns the non-empty
`instructions` value.

**Paperclip version or commit**
Reproduced on `master` before this change. The new test in this PR fails
on `master`.

**Agent adapter(s) involved**
Not adapter-specific (core bug). The drop occurs in the shared
wake-payload normalizer.

## What Changed

- `server/src/services/task-watchdogs.ts`: add `customInstructions` and
`stoppedLeaves` to the nested `taskWatchdog` object. Map each stopped
leaf to the shape the normalizer expects (`issueId` becomes `id`). Keep
the top-level copies, so that other consumers do not change.
- Add a test for the full path: real service, real wake payload,
`normalizePaperclipWakePayload`, then `renderPaperclipWakePrompt`. The
test also asserts that the "No board-supplied watchdog instructions"
line is absent.

## Verification

- Run from the repo root:
  ```
  npx vitest run server/src/__tests__/task-watchdogs-scheduler.test.ts \
server/src/__tests__/task-watchdogs-classifier.test.ts \
server/src/__tests__/task-watchdogs-incident-fixture.test.ts \
                 server/src/__tests__/issue-watchdogs-routes.test.ts
  npx vitest run packages/adapter-utils/src/server-utils.test.ts
  ```
- Local result: watchdog suites 52 of 52 pass. `server-utils.test.ts`
117 of 117 pass.
- The new test fails on `master` because
`taskWatchdog.customInstructions` is absent. It passes on this branch.
- All Paperclip CI gates on this PR are green. Greptile gives 5/5.

## Risks

- Low risk. The change adds fields to a nested object. It does not
remove fields.
- No schema change, no migration, no data write.
- Behavior change: agents now receive operator instructions that they
did not receive before. This is the intended result.
- Rollback: revert the single commit.
- Note: the normalizer field name is `terminalLeafSummaries`, but the
classifier fills `stoppedLeaves` with non-terminal stopped leaves. The
renderer heading ("Terminal / stopped leaves to verify") covers both.
This PR does not rename a public payload field. Maintainers can align
the names in a separate 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

- Provider: Anthropic Claude, Opus family, used through Claude Code as
an agent with tool use and shell execution.
- This PR description revision: `claude-opus-5-5`, 1M context window.
- The model found the cause, wrote the fix and the test, and ran the
test suites. A human owns the account that opened the PR.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] I have updated relevant documentation to reflect my changes (not
applicable: no documented behavior changed)
- [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

Duplicate search: I searched open and closed PRs and issues for
"watchdog instructions" and "watchdog customInstructions". I found no
related PR or issue.

Branch name: the branch name has an internal prefix. If the maintainers
want it, I can open the same commit again from a `fix/...` branch.
2026-10-15 15:47:54 -07:00
SkilLab HR and TechandPaperclip 92bedaac4a fix(server): treat a scheme-less public base URL as absent instead of crashing startup (#13727)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - At boot the server selects the origin that agents must dial for the
control plane. It exports that origin to every run as
`PAPERCLIP_API_URL`
> - The selection starts from `auth.publicBaseUrl`. An operator writes
this value by hand
> - If the operator writes `host:port` and omits the scheme, the server
does not reject the value. The value is a valid URL with an opaque
scheme. The `origin` of such a URL serializes to the *string* `"null"`
> - The string `"null"` is truthy. Thus it gets through the `if (value)`
guard. The enclosing `try/catch` does not help, because the parse did
not fail. A later `new URL("null")` then throws `TypeError: Invalid URL`
on the startup path
> - The result is that the server does not start. The error message
names neither the configuration key nor the bad value
> - This pull request parses the public base URL in one helper. The
helper treats an unusable origin as absent configuration. This is the
same treatment that input which fails to parse gets today
> - The benefit is that a missing `https://` falls back to the existing
derived candidates. It no longer stops the boot

## Linked Issues or Issue Description

Fixes: #13576 — that issue reports this defect and includes a standalone
reproduction.

**Related PRs found by the duplicate search (none fix this defect).** I
searched the open PR list
for `runtime-api`, `publicBaseUrl`, `public base url`,
`PAPERCLIP_API_URL` and `Invalid URL`. I then
read the diff of every close neighbour. Refs #10517, Refs #9916, Refs
#7342, Refs #12886,
Refs #11564, Refs #9228. No one of them contains a test for an opaque
origin, and no one of them
compares an origin against `"null"`. They change *which reachable origin
wins*. This pull request
changes *how one unusable value is rejected*. The two subjects do not
overlap.

Refs #10517 and Refs #9916 are duplicates of each other. Neither touches
`server/src/runtime-api.ts`.

**One neighbour needs attention.** Refs #11564 adds a new function
`resolveRuntimeApiUrl`. That
function contains this code:

```ts
try {
  const origin = new URL(explicitPublicBaseUrl).origin;
  return { url: origin, reason: "explicit-public-base-url", ... };
} catch {
  // Fall through to derived candidates if config parsing drifted.
}
```

This is the same defect in a third location. A scheme-less value parses
correctly. Thus the `catch`
does not run, and the function returns the literal string `"null"` as
the URL. The lines are pure
additions, so there is no text conflict with this pull request. But if
#11564 merges after this one,
it puts the defect back in a new function. The helper in this pull
request is module-private and
available to that function. I can add the one-line change to this branch
if the maintainers prefer
that.

## What Changed

`server/src/runtime-api.ts` — one new module-private helper, used at the
two existing call sites:

```ts
function parsePublicOrigin(rawUrl: string | null | undefined): string | null {
  const trimmed = rawUrl?.trim();
  if (!trimmed) return null;
  let origin: string;
  try {
    origin = new URL(trimmed).origin;
  } catch {
    return null;
  }
  return origin === "null" ? null : origin;
}
```

- `choosePrimaryRuntimeApiUrl` now calls the helper. Its inline
`try/catch` is removed.
- `buildRuntimeApiCandidateUrls` now calls the helper. Its inline IIFE
is removed.
- `server/src/__tests__/runtime-api.test.ts` gets two new cases. One
case covers the wrong `"null"`
  result. The other case covers the startup exception.

Counts: **+20 / −17** in `runtime-api.ts`. Five of the added lines are
the comment that explains the
opaque scheme. The test file is **+23 / −0**. There is no new
dependency. There is no `any` type. No
exported signature changes. The duplicated parse logic at the two call
sites becomes one function.

## Verification

I ran every step below against `master` at commit
`c65fc9e3c81c41aafe421aa90a00514b84343285`. That
commit is the merge base of this branch.

- **Unit tests**, with the repository runner (`vitest`), on the two
added cases:
- Before the fix: `Tests 2 failed | 6 passed (8)`. The two failures are
exactly the two new cases.
    The assertion error is
`AssertionError: expected 'null' to be
'http://runtime-host.example.test:3102'`.
  - After the fix: `Tests  8 passed (8)`.
- Both new cases are pure additions. No existing expectation changes,
because no existing test
    uses a scheme-less `authPublicBaseUrl`.
- **Standalone reproduction with 8 arms** (attached to #13576): **6/8**
on unpatched `c65fc9e3`, and
**8/8** with the patch. The 5 regression arms and the 1 control arm give
the same result on both
sides. The control arm uses input that truly fails to parse. Thus the
reproduction discriminates
  between the two builds. It does not pass by accident.
- **Typecheck**: `tsc --noEmit --strict --noEmitOnError --target ES2023
--module NodeNext` exits 0 on
  the patched file. It also exits 0 on the unmodified file.

## Risks

**Low risk.** Reasons:

- The change only affects input that makes the server crash today. No
operator can depend on the
  present behaviour, because the present behaviour stops the boot.
- The new path is not new code. The helper returns `null`, and `null`
goes to the fallback that
  already exists for input that fails to parse.
- The second call site previously put the string `"null"` into the
candidate list. It now omits that
entry. A `"null"` origin is not reachable, so no caller can lose a
usable candidate.

**The one behaviour change a reviewer must agree with.** A bad value now
degrades quietly instead of
failing loudly. An operator who writes `host:port` gets a server that
starts on a derived origin. The
operator does not get an error.

I did not add a warning log, for one reason: the existing parse-failure
path is also silent. A log
would change behaviour that this pull request does not target, and it
would enlarge the diff. If the
maintainers want the warning, the correct fix is one log line inside
`parsePublicOrigin`, which
covers both input classes at once. This is now possible only because the
two call sites share one
function. Tell me if you want that, and I will add it.

**Migration:** none. No schema change, no data change, no configuration
change.

## Model Used

- Provider and model: Anthropic Claude, Opus 5
- Exact model ID: `claude-opus-5[1m]`
- Context window: 1M tokens
- Mode: extended thinking
- Capabilities used: tool use, shell execution, file editing, and GitHub
API access
- The human operator reviewed and authorized this submission. A human
approved the publication of
  this pull request before I opened it.

## 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 — I read
the file. It has no item for the runtime API URL, for startup URL
validation, or for
      configuration parsing.
- [x] I have searched GitHub for duplicate or related PRs and linked
them above — 5 queries, and I
read the diff of each close neighbour. See the linked-issues section.
- [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 — the branch is
      `fix/scheme-less-public-base-url`
- [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 — no
user-facing document
      describes this internal fallback, so no document change applies.
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green — not yet true, and I do not
check a box that is not true. On
commit `13ffb649`: 43 checks pass, 7 checks still run, 2 checks fail.
Both failures are the
PR-description gate that this update repairs. I will check this box when
the gates are green.
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
not yet true. Greptile gave
4/5 with one P2. That P2 is "Required PR sections missing", which this
update repairs.
- [x] I will address all Greptile and reviewer comments before
requesting merge — this update is
      that work for the first review round.

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-15 15:46:11 -07:00
514d8738cf fix: keep the JWT run-mismatch audit when the claimed run is missing (#13183)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Rejected agent JWTs whose run header disagrees with the signed claim
are audited into `activity_log` so operators can see spoof attempts
> - The audit insert writes the claimed run id into `run_id`, which is a
real foreign key to `heartbeat_runs`; a bogus claim violates the FK and
the catch swallows it, so the audit row is silently lost exactly when it
matters most
> - This pull request verifies the claimed run exists before trusting it
into the FK column; the claim itself stays in `entityId` and `details`
> - The benefit is a reliable audit trail for rejected credentials

## Linked Issues or Issue Description

Refs #10498 — the anonymous fall-through half was fixed on master
already (every failed-credential branch now returns an explicit
401/403/422); this closes the residual FK-swallowed audit defect.

## What Changed

- `server/src/middleware/auth.ts`: `auditAgentJwtRunHeaderMismatch`
looks up the claimed run id and populates `runId` only when the row
exists; `entityId` and `details.claimRunId` carry the claim either way.
- `server/src/__tests__/agent-auth-middleware.test.ts`: a mismatch whose
claimed run does not exist still produces the audit row with `runId`
omitted.

## Verification

- `pnpm --filter @paperclipai/server vitest run
src/__tests__/agent-auth-middleware.test.ts` — 17 tests pass.
- Before: a run-header mismatch whose claimed run id was absent from
`heartbeat_runs` violated the `activity_log.run_id` FK, and the catch
swallowed it — no audit row. After: the row lands with `runId` null and
the claim preserved in `entityId`/`details`.

## Risks

- Low risk. One extra indexed select on a rare rejection path; the audit
payload is unchanged apart from `runId` now being conditional on
existence.

## Model Used

- Anthropic Claude — SWE-2 Max agent via Devin CLI, 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
- [ ] 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: Waseem Ilyas <1478353+Waseemilyas@users.noreply.github.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-15 15:43:56 -07:00
880d4ae60f fix: reject done when an executionPolicy replace discards a live review cycle (#13178)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The issue PATCH route applies executionPolicy edits and status
changes through one transition function, so both can arrive in a single
request
> - A replacement executionPolicy regenerates stage ids, which orphans
the stored currentStageId; the orphaned-stage path clears executionState
> - When a review cycle is live (pending or changes_requested), that
clear lets a caller-supplied `status: "done"` stand although the review
never ran
> - This pull request rejects `done` on that combined write and leaves
every other requested status — including board cancellation — on the
established abandon-and-clear path
> - The benefit is that a review gate cannot be bypassed by attaching a
fresh policy object to a completion request

## Linked Issues or Issue Description

Fixes #13176

Refs #13001 (same transition function, distinct defect)

Supersedes #13164 — same fix, resubmitted on a correctly named branch
with the review findings addressed: the guard no longer rejects board
cancellation, and the internal ticket reference is gone.

## What Changed

- `server/src/services/issue-execution-policy.ts`: in the orphaned-stage
branch, reject the transition with 422 `unexecuted_review_stage` only
when a live review cycle (pending or changes_requested) meets a
requested status of `done`. All other requested statuses — including
`cancelled`, where abandoning the cycle is the intent — fall through to
`clearExecutionStatePatch` exactly as a bare policy edit does.
- `server/src/__tests__/issue-execution-policy.test.ts`: regression
coverage for the rejected `done`, the preserved board cancellation, a
routine non-`done` status change on the same path, the unchanged-policy
`done` route to the stored reviewer, and the bare policy edit.

## Verification

- `pnpm --filter @paperclipai/server vitest run
src/__tests__/issue-execution-policy.test.ts` — 75 tests pass locally.
- `pnpm --filter @paperclipai/server typecheck` passes.
- Manual reasoning check: PATCH `{executionPolicy: <new object>, status:
"done"}` on an issue whose `executionState.status` is `pending` or
`changes_requested` now returns 422; the same request with `status:
"cancelled"` or with no status clears the orphaned stage as before.

## Risks

- Low risk. The only newly rejected request is one that previously
discarded a live review while claiming completion — a silent-bypass bug,
not a supported workflow. Callers intending to cancel or repurpose an
issue mid-review are unaffected.

## Model Used

- Anthropic Claude — SWE-2 Max agent via Devin CLI, tool use and code
execution; the original fix commit was authored by an earlier agent pass
on the same task.

## Checklist

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

---------

Co-authored-by: Waseem Ilyas <1478353+Waseemilyas@users.noreply.github.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-15 15:38:28 -07:00
DottaandPaperclip becdc3b08e perf(slack): remove delays before mention acknowledgements (#16491)
Slack mentions now start eyes reactions after the authorized task/comment commits, independently of attachments, subscription and scheduler acceptance. Omit unused optional URL-preview polling while retaining message text, links and files. Keep receipt failures and missing-file warnings independent, including terminal cleanup of older multi-line diagnostics.

Verified with full current-head CI and E2E, Superagent and legacy security checks, Apex 5/5 with no unresolved findings, 19 selected real-PostgreSQL receipt/authorization/cleanup cases, and full frozen-source typecheck/build. The first CI attempt's unchanged Cursor probe timeout is retained in the PR; the one failed-only retry passed under unchanged deadlines. Live Slack latency still requires deployed acceptance.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-15 17:36:50 -05:00
81cea89d6b fix(cli): require confirmation for instructions file deletes (#11504)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The CLI exposes board/operator controls for managing agent
configuration and instructions.
> - Agent instructions files influence future agent behavior, so
deleting one is a destructive configuration mutation.
> - Nearby destructive CLI commands already require explicit `--yes`
confirmation before sending DELETE requests.
> - `agent instructions-file:delete` skipped that local confirmation
guard and could delete a bundle file from a scripted or mistyped
command.
> - This pull request aligns that command with the existing
destructive-command safety convention.
> - The benefit is a small, predictable operator-safety guard with
focused regression coverage.

## Linked Issues or Issue Description

Bug report:

### Pre-submission checklist

- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I am on the latest released version of Paperclip (or can reproduce
on `master`).
- [x] I have confirmed the error originates in Paperclip itself — not in
my agent adapter, API provider, or local configuration.

### What happened?

`paperclipai agent instructions-file:delete` deleted an agent
instructions bundle file without requiring explicit CLI confirmation.

### Expected behavior

Destructive CLI delete operations should require `--yes` before sending
a DELETE request.

### Steps to reproduce

1. Run `npx paperclipai agent instructions-file:delete <agent-id> --path
AGENTS.md`.
2. Observe that the CLI immediately issues `DELETE
/api/agents/:agentId/instructions-bundle/file?path=AGENTS.md`.
3. No local confirmation guard blocks the destructive request.

### Paperclip version or commit

`fd472d02ba950519ca5eff75ed9fd55cf976f4c2`

### 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

Not database-related

### Access context

Board (human operator)

### Node.js version

`v20.x`

### Operating system

macOS

### Relevant logs or output

```text
Current behavior is local CLI behavior: the command proceeds to the DELETE call without requiring `--yes`.
```

### Relevant config (if applicable)

None.

### Additional context

This command already sits next to other destructive CLI actions such as
`agent delete`, which require explicit confirmation. Aligning the guard
makes the CLI safety model consistent.

### Privacy checklist

- [x] I have reviewed all pasted output for PII (usernames, file paths,
API keys, tokens, company names) and redacted where necessary.

## What Changed

- Added a `--yes` confirmation option to `agent
instructions-file:delete`.
- Refuse the command locally before resolving API context or sending a
DELETE request when confirmation is missing.
- Updated CLI parity coverage for confirmed and refused
instructions-file deletion.
- Updated the CLI documentation example to include `--yes`.

## Verification

-
`/Users/xinran/Downloads/dev/paperclip-pr-work/node_modules/.bin/vitest
run cli/src/__tests__/agent-lifecycle.test.ts --config
cli/vitest.config.ts` equivalent executed from the PR worktree using
temporary dependency symlinks: 3 tests passed.
- `git diff --check`

## Risks

- Low risk: this only changes one destructive CLI command and matches
existing `agent delete`, `issue delete`, and other delete-command
confirmation behavior.
- Scripts that intentionally delete instructions files must add `--yes`,
which is the intended explicit confirmation path.
- No database, API contract, or lockfile 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, GPT-5-based coding agent in Codex desktop with
shell/tool use and local test execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [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: 馨冉 <xinxincui239@gmail.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-15 15:36:23 -07:00
Michel Tomas 5b15de9568 fix(adapter-utils): accept the bridge token in X-API-Key (#12785)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - An agent can run inside a sandbox. The sandbox callback bridge is
the loopback gateway that lets the agent call the Paperclip API from
inside that sandbox
> - The gateway authenticates each local call with a per-run bridge
token. It read that token from the `Authorization: Bearer <token>`
header only
> - The run hands the agent that token in `PAPERCLIP_API_KEY`, next to
`PAPERCLIP_API_URL` (`execution-target.ts`). The token is named an API
key, and the public Paperclip API documents that an API key goes in the
`X-API-Key` header
> - An agent that follows the documentation therefore sends `X-API-Key`
first, and its first call fails with 401 `Invalid bridge token.`
> - The agent then retries with a bearer header and the same token. The
run still succeeds, but it wastes a turn and it can leave a confused
agent trying unrelated repairs
> - This pull request makes both generated gateways read the bridge
token from `X-API-Key` when the request carries no bearer token
> - The benefit is that a documented header convention stops producing a
needless failure, at no cost to the token check

## Linked Issues or Issue Description

No existing issue found. I searched the repository for open pull
requests matching `x-api-key` and `bridge token`. Nothing addresses the
in-sandbox gateway header convention. The closest neighbours are
unrelated: #12632 (Composio `mcp.headers.x-api-key`) and #12120 (the
HTTP/2 transport for this bridge, already merged). The problem follows
`bug_report.yml`:

**What happened?**

An agent running in a sandbox calls the Paperclip API through the
loopback callback bridge. When it sends the credential as `X-API-Key:
<bridge token>`, the gateway answers `401` with `{"error":"Invalid
bridge token."}`. The token in that header is the correct per-run bridge
token.

**Expected behavior**

The gateway accepts the correct per-run bridge token in either header
convention. The public API documents `X-API-Key`, so an agent that
follows the documentation succeeds on its first call.

**Steps to reproduce**

1. Start a run whose agent executes in a sandbox execution target, so
the callback bridge is active.
2. The run exports `PAPERCLIP_API_URL` (the loopback bridge origin) and
`PAPERCLIP_API_KEY` (the bridge token) into the sandbox environment.
3. From inside the sandbox, call the bridge with that key in the
documented header:
   ```bash
curl -i -H "X-API-Key: $PAPERCLIP_API_KEY"
"$PAPERCLIP_API_URL/api/agents/me"
   ```
4. The response is `401 {"error":"Invalid bridge token."}`.
5. Repeat the same call with `-H "Authorization: Bearer
$PAPERCLIP_API_KEY"`. The response is `200`.

**Paperclip version or commit**

Branched from `master` at `06c0e883f`.

**Deployment mode**

Self-hosted Kubernetes, with the agent in a sandbox execution target and
a local model behind an OpenAI-compatible endpoint.

**Agent adapter(s) involved**

Not adapter-specific (core bug). Observed with a local-model adapter on
a sandboxed lane. Any agent that follows the public API documentation
sends this header.

**Additional context**

The variable name is the trigger. The run calls the credential
`PAPERCLIP_API_KEY`, and an agent that has read the public API
documentation knows an API key goes in `X-API-Key`. Paperclip's own
in-prompt guidance uses `Authorization: Bearer $PAPERCLIP_API_KEY`, so
the two conventions sit side by side.

Across 12 observed runs on that lane, the agent's first bridge call used
`X-API-Key` in 6 runs. Every one of those runs recovered by retrying
with a bearer header and the same token. In some runs the agent first
tried unrelated repairs, such as writing a test comment, before it
changed the header.

## What Changed

- Add one `readBridgeToken(req)` helper to the generated in-sandbox
gateway source. The helper returns the bearer token from `Authorization`
when the header carries one. It otherwise returns the `x-api-key` header
value, or an empty string.
- Use that helper in both generated gateway request handlers: the
file-mode queue gateway and the HTTP/2 gateway. Both previously repeated
the same two lines of bearer parsing.
- The token still goes to the same `timingSafeEqual` comparison. The
change does not alter the comparison, the token, or its lifetime.
- Extend the file-mode round-trip test to cover an `X-API-Key` request
with the right token, an `X-API-Key` request with a wrong token, and the
fact that the header never reaches the host.
- Add one HTTP/2-path test that covers the same three cases, and that
proves a bearer token still wins when a request carries both headers.

## Verification

Commands run on this branch:

```
node_modules/.bin/vitest run packages/adapter-utils/src/sandbox-callback-bridge.test.ts
  Test Files  1 passed (1)
       Tests  52 passed (52)

cd packages/adapter-utils && ../../node_modules/.bin/tsc --noEmit
  0 errors in the two files this pull request touches
```

Both new tests are real regression guards. With the test file in place
and the source change reverted, each one fails with `expected 401 to be
200`: the file-mode round-trip test and the HTTP/2 test. Both pass with
the source change applied.

A reviewer can confirm the security properties by reading two things.
First, `tokensMatch` in the generated source is unchanged, so the
constant-time comparison against the same per-run token is unchanged.
Second, `allowedHeaders` in the same source is a closed allowlist of
`accept`, `content-type`, `if-match`, and `if-none-match`.
`normalizeHeaders` drops every other header, so neither `authorization`
nor `x-api-key` leaves the sandbox on either transport. The independent
host-side check in `http2-bridge-server.ts` is untouched, because each
gateway sets `authorization: Bearer <token>` on the request it forwards.

**ROADMAP.md check:** I grepped `ROADMAP.md` for `bridge`, `x-api-key`,
`redaction`, and `sandbox callback`. There were no matches. This is a
small bug fix in an existing subsystem, so it does not overlap planned
core work.

**Duplicate search:** I searched pull requests in this repository for
`x-api-key` and `bridge token`. No open or merged pull request changes
how the in-sandbox gateway reads the token.

## Risks

Low risk.

- The change widens which header the gateway reads. It does not widen
what the gateway accepts. A request still needs the exact per-run bridge
token, and it still passes the same constant-time comparison.
- A bearer token keeps priority. A request that carries both headers
behaves exactly as it did before, which a test now pins.
- A caller that presents a non-bearer `Authorization` header and a valid
`X-API-Key` header now succeeds instead of failing. This is intentional:
the bridge only ever issues bearer tokens, so no bridge caller relied on
that failure.
- The header allowlist already excluded `x-api-key`, so no forwarded
request changes shape.
- No migration, no configuration change, and no behavior change for a
caller that already sends a bearer token.

## Model Used

Claude Opus 5 (`claude-opus-5`), extended thinking at high effort —
verify before merge.

## 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 — no
documentation describes the bridge's header convention, so there is
nothing to update
- [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

---
<sub><em>**[@superbiche](https://github.com/superbiche)** · contributor
· drafted with Claude Opus 5, reviewed before posting; the voice and
decisions are mine.</em></sub>
2026-10-15 15:31:31 -07:00
DottaandPaperclip 67a9d5cc37 perf(runner): bypass skill cache waits for empty selections (#16498)
Native runs with no skill files no longer queue behind company skill-cache work. Preserve saved empty selections and the existing exclusion for selected and legacy operational skills.

Verified with real PostgreSQL held-lock regressions, 20 distinct affected cases, full repository typecheck/build, fresh full CI/E2E/security and exact-head Apex 5/5. Hosted latency remains to be verified.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
canary/v2026.1015.0-canary.23
2026-10-15 17:01:57 -05:00
Nicky LeachandPaperclip ec3eb1968b feat(runner): expose credential updates through PRP (#16463)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Runner uses PRP to send harness events to the host.
> - A harness can replace its stored OAuth tokens during a run.
> - The host has no PRP signal that tells it to read the updated
credential.
> - This pull request adds a durable credential update event and
observers for supported stores.
> - The host can react before it acknowledges the event. Token values
stay outside PRP.

## Linked Issues or Issue Description

**Subsystem affected**

`packages/paperclip-runner`: native provider execution, PRP schemas, and
host event delivery.

**Problem or motivation**

A Paperclip host that consumes PRP needs to react when a harness changes
its refresh-token credential. Existing credential copy-back paths do not
provide a common event during a warm session.

**Proposed solution**

Add `auth.credentials.updated` as a P0 runner event. Include only the
provider, binding ID, revision, and detection method. Observe native
Codex and Codex/Grok ACPX credential files. Retain each pending event
across runner restart until the durable outbox accepts it. Raw PRP
consumers opt in with `observeCredentials: true` before session open.
The bundled transport opts in for fresh sessions.

**Alternatives considered**

`harness.diagnostic` would mix a runner observation with harness
diagnostics. Sending token values through PRP would expose credentials
to event logs. Both approaches are unnecessary.

**Roadmap alignment**

I checked `ROADMAP.md`. This change extends the existing Runner protocol
and does not duplicate a planned core feature.

**Additional context**

Refs: #11094, #14056, #12696. These describe credential copy-back and
staging work. This PR adds the notification contract. It does not
replace those private credential channels or claim to fix their full
scope.

## What Changed

- Add strict credential update payload validation to PRP v1, v2, and v3.
Regenerate validators, the manifest, and the event catalog.
- Watch supported credential directories with `notify`. Add a periodic
read as a fallback and check at terminal and close boundaries.
- Persist a private baseline and pending event together. Notify on
account identity changes and retain the new baseline for later
refreshes. Ignore incomplete files, unsafe files, and metadata changes.
- Forward the event through the host transport. Test commit-before-ACK
delivery and replay.
- Commit pending runner observations before suspend and shutdown
commands complete. Preserve the provider event backlog.
- Create the private ACPX runtime directory before configuring
observation. Cover fresh session startup with observation enabled.
- Fence asynchronous PRP commits to the current authenticated connection
and run authority. Ignore replaced callbacks while preserving the
maintenance drain of admitted work.
- Deliver observations found during final provider cleanup before
clearing terminal recovery state. Retain the outbox and recovery state
when the host ACK is lost.
- Advance the pending Copilot candidate profile to v19 for the changed
shared PRP source hashes. Preserve its v18 history and pending
qualification status. Include the current fixture in the Docker build
context.
- Commit credential updates through an awaited host callback before PRP
ACK, including during idle and close. Persist them in a separate run-log
source namespace with company, agent, session, and runner checks.
- Preserve public event metadata through server redaction. Register the
event in task run details.
- Document opt-in behavior, supported stores, event semantics, and the
local run-log path.

## Verification

- All 45 durable runner control tests and 24 native executor integration
tests pass.
- Ten Rust credential observer tests pass. They cover atomic
replacement, polling, recovery, account-only changes and later refreshes
for Codex and Grok, unsafe files, fresh runtime creation, and the Grok
close handoff. Account identity stays private.
- The full Runner TypeScript suite passes: 3,565 tests in 221 files. An
additional 12 tests are skipped by the suite.
- All 214 focused tests pass for the contract, Codex event routing,
catalog, and durable host delivery.
- All 13 Node protocol contract tests pass.
- The exact Docker context integrity command passes: `docker buildx
build --file .github/docker-context-checks.Dockerfile .`.
- Rust formatting and generated protocol checks pass.
- `pnpm -r typecheck` and `pnpm build` pass. The full local `pnpm
test:run` was attempted with CI-compatible `init.defaultBranch=master`,
then stopped after a reproducible failure outside this change:
`git-workspace-expiration.test.ts` expects one re-expired workspace but
gets zero. The same case fails in isolation on this macOS checkout. Full
repository validation is provided by CI; the focused Runner and host
tests pass locally.
- All 253 native transport and restart tests pass against the final
release binary.
- All 3 durable recovery tests and 139 candidate profile and native
admission tests pass.
- All 41 host redaction and task protocol surface tests pass. `pnpm
check:token-gates` passes.
- All 467 CI policy tests pass, including the 15 CI profiling tests. The
upstream secret-reference test fix is retained from master. No workflow
changes are included.

- The Apex host-delivery fix passes 381 targeted Runner tests, six host
database tests, repository typecheck, and the production build. The
Runner tests cover commit-before-ACK, retry after a rejected host
commit, warm attachment, and close after the turn queue ends.
- Prior head `2f125cfcb` passed CI, standard Greptile 5/5, and Apex 5/5.
One CI shard was interrupted by GitHub runner shutdown; its single rerun
passed. Both Apex findings are addressed: idle/cleanup host delivery and
late callback reconnect fencing. Current head `5db78f082` passes CI (56
successful statuses and four optional skips) and standard Greptile 5/5.
Apex review of the account-change update is 5/5 on this exact head, with
no actionable issues.
- All 121 durable control-plane and credential callback tests pass,
including account-only change delivery, subsequent refresh on the new
account during close, host commit retry, late success/failure races,
forward cursor delivery on a replacement connection, and the existing
maintenance retirement contract. All 27 native provider and durable
recovery integration tests pass after the account-change update.

## Risks

- File changes signal a stored credential update. They do not prove that
an OAuth refresh succeeded. Changes between reads can be combined.
- Unix Codex and Codex/Grok ACPX stores are supported. Other harnesses
and credentials held only in memory need their own integration.
- Hosts must update their event schemas before opting in. Older hosts
omit the flag and receive no new event. Older runners ignore the flag.
- The host must use its existing private credential channel and account
checks to read or copy the token. Account switches trigger notification
but do not authorize accepting the new account. The event grants no
credential access.
- Tests use synthetic credentials. No live OAuth refresh has been
forced.

## Model Used

OpenAI Codex, exact model ID `gpt-6.1-sol`, with high reasoning effort,
repository inspection, tool use, and code execution. The session does
not expose its 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-15 14:49:00 -07:00
23e7ee9a2d fix(issues): exclude terminal children from helper cap (#11542)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issue service lets agents create bounded child work through a
helper.
> - The helper counted completed and cancelled children against its
safety limit.
> - A recurring coordinator could reach the limit even when almost all
historical children were terminal.
> - The safety limit must still bound current delegated work.
> - This pull request counts only non-terminal children when it applies
the limit.
> - The benefit is that recurring parents can keep their lineage without
losing the active-work guardrail.

## Linked Issues or Issue Description

**What happened?**

The child-creation helper rejected a new child after a parent had 25
historical children. It counted children with `done` and `cancelled`
status.

**Expected behavior**

The helper must apply its limit to non-terminal children. Terminal
children must remain attached to the parent for lineage and audit
history.

**Steps to reproduce**

1. Create one parent issue.
2. Create 25 child issues and move them to `done` or `cancelled`.
3. Use the issue-service child helper to create another child.
4. Observe that the current code rejects the new child.

**Paperclip version or commit**

`1a17cbf23`

## What Changed

- Exclude `done` and `cancelled` children from the helper cap query.
- Keep the limit at 25 non-terminal children.
- Add a regression test with terminal history and the active limit
boundary.
- Clarify the limit error so it says `active child issues`.

## Verification

- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts -t
"counts only non-terminal children toward the helper child limit"`
passed: 1 test passed and 118 skipped.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `pnpm test:run` completed the general server and UI groups
successfully. The CLI group then reported one unrelated environment
failure because port 3199 was already in use. The failing test was
`doctor > re-runs repairable checks so repaired failures do not remain
blocking`.

```test-evidence:
sha: 071edbf019
command: pnpm exec vitest run server/src/__tests__/issues-service.test.ts -t "counts only non-terminal children toward the helper child limit"
exit: 0
totals: 1 test passed; 118 skipped; 1 test file passed
worktree: clean before and after; local == remote == PR head
finished_at: 2026-08-17T14:03:53Z
```

## Risks

- Low risk. The change affects only the helper-specific count query.
- The helper still rejects creation when 25 non-terminal children exist.
- No schema or migration changes are present.

> 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 with reasoning, repository tools, code execution,
and test execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] 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: Codex Engineer <agent+codex-engineer@agents.techwright.io>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: commitperclip-bot <305974387+commitperclip-bot@users.noreply.github.com>
canary/v2026.1015.0-canary.22
2026-10-15 14:36:48 -07:00
Dylan RoyandClaude Fable 5 7b09e3da73 fix: count writes from unscoped heartbeat runs against the cross-issue cap instead of rejecting them (#12621)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents act inside heartbeat runs, and the server attributes every
issue comment and task update to a run so the per-run cross-issue
influence cap can be counted
> - A timer heartbeat that wakes an agent with no checked-out issue
stores no `issueId` in the run context snapshot
> - The influence gate treats that missing source issue as "no valid
run" and rejects every comment and PATCH from the run with
`cross_issue_influence_run_context_required`, even though the run row
exists and fully attributes the write
> - Issue creation is not gated, so an unscoped run can open an issue it
then cannot describe, update, or comment on
> - This pull request counts writes from unscoped runs against the
cross-issue cap instead of rejecting them
> - The benefit is that unscoped heartbeat runs keep their write
channels while the anti-spray cap and the audit trail stay fully intact

## Linked Issues or Issue Description

No public issue exists. Description follows the bug report template.

**What happened?**

An agent woken by a plain heartbeat timer (no assigned issue) received a
403 with code `cross_issue_influence_run_context_required` on every
`POST /api/issues/{id}/comments` and `PATCH /api/issues/{id}` request.
The agent JWT was fresh and its `run_id` matched a live `heartbeat_runs`
row. The error text said the request "arrived without a valid run", but
the run was valid. `POST /api/companies/{companyId}/issues` succeeded
with the same token, so the run created an issue it could not then
update.

**Expected behavior**

A live, authenticated run must be able to comment and update issues. A
run with no source issue has no same-issue exemption, so each of its
writes must spend the per-run cross-issue cap. The
`cross_issue_influence_run_context_required` error must fire only for
malformed, unknown, or mismatched run ids.

**Steps to reproduce**

1. Let an agent wake from the heartbeat timer with no checked-out issue.
2. From that run, call `PATCH /api/issues/{id}` or `POST
/api/issues/{id}/comments` with the agent JWT.
3. The server returns 403 `cross_issue_influence_run_context_required`,
because `observeCrossIssueInfluence` throws when `readRunSourceIssueId`
returns null.

**Paperclip version or commit**

master (c7ebc089c); also reproduced on the current published npm
package.

**Deployment mode**

Local single instance (embedded Postgres).

**Installation method**

npx paperclipai

## What Changed

- `observeCrossIssueInfluence` no longer throws when the persisted run
has no source issue. It counts the write as cross-issue influence and
records `sourceIssueId: null` in the activity log entry.
- The same-issue exemption applies only when the run has a source issue.
- Replaced the test that pinned the old rejection with two tests: an
unscoped run's write is counted and allowed, and an unscoped run is
still capped once its budget is spent.

## Verification

- `cd server && pnpm vitest run
src/__tests__/cross-issue-influence-limit.test.ts` — 11 tests pass.
- `cd server && pnpm vitest run
src/__tests__/cross-issue-influence-limit-postgres.test.ts
src/__tests__/interaction-resolution-cross-issue-cap-postgres.test.ts` —
8 tests pass.
- Manual: wake an agent from a plain heartbeat timer, then PATCH any
issue with the run's JWT. Before the fix the server returns 403
`cross_issue_influence_run_context_required`; after the fix the write
succeeds and an `issue.cross_issue_influence_observed` activity row
appears with `sourceIssueId: null`.

## Risks

- Behavioral shift: writes from unscoped runs were rejected and are now
allowed up to the existing per-run cap of 20. This is the conservative
direction — with no source issue there is no same-issue exemption to
abuse, and every write is still attributed to a locked run row and
logged.
- No schema or migration changes. The activity log
`details.sourceIssueId` field can now be null; it was previously always
set on these rows.

## Model Used

- Claude Fable 5 (Anthropic, model id `claude-fable-5`, 1M context,
extended thinking, tool use enabled) authored the change and tests,
driven through Claude Code.

## Checklist

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

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

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-10-15 14:34:23 -07:00
DottaandPaperclip 89ef37d7e0 fix(slack): accept linear DM receipt destinations (#16494)
Accept the pinned Slack SDK linear DM address for receipt add/remove while preserving channel, source message, credential and timeout checks.

SDK regressions, 106 affected tests, repository typecheck/build, full CI/E2E, Superagent, legacy Snyk and exact-head Apex 5/5 passed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-15 16:28:19 -05:00
DottaandPaperclip 8843887c24 fix(runner): preserve managed GitHub access in OpenCode (#16488)
## Thinking Path

> - Paperclip manages AI agents and their authorized connections.
> - Native OpenCode agents use managed Git and GitHub launchers for
shell work.
> - Each operation must obtain credentials from the active run's broker.
> - Two OpenCode environment filters dropped that broker binding before
the shell started.
> - The shell therefore ran anonymously even when connector tools had
authorized access.
> - This pull request preserves the explicit broker environment through
both filters.
> - The benefit is working GitHub shell access with current identity and
revocation checks.

## Linked Issues or Issue Description

**What happened?**

Native OpenCode shell commands reported `GitHub capability_missing`
despite an authorized GitHub connection. Both runner startup and
provider startup dropped the controller's broker settings. Connector MCP
calls used a separate path and could still succeed.

**Expected behavior**

A managed shell operation obtains credentials for the current run. A
later operation uses the new identity after rotation and loses access
after revocation. Ambient host credentials do not supply authority.

**Steps to reproduce**

1. Start the native GitHub broker with an active run binding.
2. Pass its environment through native OpenCode startup.
3. Run `gh api user` from the provider shell.
4. Observe anonymous access before this fix.

**Paperclip version or commit**

Reproduced on master `6943904bad` before this change.

**Deployment mode**

Native Paperclip Runner with OpenCode, including remote execution.

Related: #16401 fixes the separate oversized OpenCode SSE failure and is
already merged. The same live-driver regression was verified locally
against its exact pre-fix source and current source. This PR does not
duplicate that fix. Searches found no open PR for this native broker
propagation defect; #15309 concerns the legacy OpenCode adapter.

## What Changed

- Use the existing GitHub credential environment projection at both
OpenCode launch boundaries.
- Preserve explicit controller authority while excluding an ambient host
broker.
- Exercise the real launcher and broker from an OpenCode child process,
directly and through runner projection.
- Check operation-time identity rotation, revocation, and exclusion of
controller API credentials and persistent GitHub tokens.
- Document the managed OpenCode shell contract.

## Verification

- Red: both new `native-github-access.test.ts` cases returned
`anonymous` instead of `fixture-a` before production changes.
- Green: both cases pass after the fix, including `fixture-a` to
`fixture-b` rotation and anonymous operation after revocation.
- Full native GitHub broker suite: 18 tests pass.
- OpenCode, provider-event, and configured-environment suites: 141 tests
pass across eight files.
- Separate SSE red/green verification: the existing live-driver
regression times out on `fd057f6ac4^` and passes with merged #16401. The
full OpenCode group also passes after restoration.
- Full repository build and typecheck pass locally. All CI jobs pass for
`23d734293f6a5c6d6128cae0c8773661816daf9a`. Final GitHub rollup: 55
successful checks/statuses, two skipped checks, and one neutral check;
none pending or failing. Greptile gave that commit 5/5 with no findings
or unresolved threads.
- Paperclip Review spent over 23 minutes preparing its runtime without a
provider assessment. Its run was cancelled for recovery and ended
neutral (review execution did not complete). This neutral result is not
a code assessment. No check policy or branch protection was changed.
- The full local Vitest attempt first hit embedded PostgreSQL startup
failures. Running the unchanged `initdb --version` and `postgres
--version` independently showed missing native library symlinks. The
dependency had been installed with scripts disabled. Running its bundled
`hydrate-symlinks.js` restored startup; all 15 affected native-session
cases and all 112 AI connection tests then passed without application
code changes. The original full-suite attempt was stopped after the
setup failure and its log is retained. A complete local suite pass is
not claimed.
- The broker and executable are local fixtures. No live GitHub account,
credential change, or test PR is required by these regressions.

## Risks

The change affects credential delivery into OpenCode processes. It
reuses the existing bounded GitHub projection and accepts broker
authority only from explicit input. Each shell operation still uses the
normal broker, scope, identity, and revocation rules. Unrelated
controller secrets remain excluded. There are no database or
permission-policy changes.

## Model Used

OpenAI `gpt-6.1-sol`, through Codex, with xhigh reasoning and repository
tools/code execution. The session does not report its 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 the focused regression and affected suites locally and
they pass; the full local attempt and setup failure are disclosed above
- [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 (Paperclip Review is
neutral/incomplete, as disclosed above)
- [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-15 16:15:38 -05:00