Commit Graph
1602 Commits
Author SHA1 Message Date
Dotta 8da49d6ea7 fix(ci): make runner image tests deterministic (#12452)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Pull request checks protect the runtime and authorization boundaries
> - The same checks must produce the same result on GitHub and RunsOn
Ubuntu images
> - One runtime test assumed that a shell PID always owns the listening
socket
> - One watchdog test denied issue reads while it tried to test
assignment denial
> - These assumptions caused image-sensitive failures during the AWS
runner canary
> - This pull request tests the production contracts directly
> - The benefit is a reliable CI result across both runner images

## Linked Issues or Issue Description

Refs #12350

## What Changed

- Verify stale service ownership through the existing process-group
ownership helper.
- Allow normal issue reads in the watchdog reassignment fixture.
- Assert that the watchdog reassignment reaches and denies the
`tasks:assign` guard.

## Verification

- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/workspace-runtime.test.ts
-t "does not reuse a stopped auto-port service port while another
process owns it"`
- `pnpm exec vitest run
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts -t
"still enforces normal assignment guards for watchdog reassignment"`
- Ran the complete issue agent mutation ownership suite six times. All
522 test executions passed.
- Ran the runtime regression case eight times. All eight test executions
passed.

## Risks

- Low risk. This pull request changes test fixtures and assertions only.
- The process-group assertion matches the ownership rule that the
runtime already uses.
- The watchdog fixture still denies `issue:mutate` and `tasks:assign`.

## Model Used

- OpenAI Codex with GPT-5.6 (`gpt-5.6-sol`). The model used reasoning,
tool use, and code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with Fixes, Closes, or
Refs OR (b) described the issue in-PR following the relevant issue
template
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [x] I have run relevant 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 change is required for this test-only fix.
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-28 13:01:59 -05:00
Nicky LeachandPaperclip d9449e636e feat(onboarding): sign in to an agent provider during onboarding (#12440)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - New organizations create their first agent through the onboarding
wizard
> - The wizard does not show provider sign-in when a host credential is
absent or unknown
> - The create step also gives unclear feedback when the provider needs
authentication
> - This pull request adds a safe auth signal and a provider sign-in
step for sandbox drivers
> - The benefit is a clearer onboarding path with no token or account
data in the signal

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting (server API, shared types, and UI)

**Problem or motivation**

The onboarding wizard can fail when the selected provider needs
authentication. It does not tell the person how to complete sign-in.

**Proposed solution**

Add a status-only provider auth signal. Show the sign-in panel for
sandbox drivers when the signal says `absent` or `unknown`. Apply a
stored Claude login to the new agent and block creation when the adapter
test reports missing authentication.

**Alternatives considered**

The wizard could hide the sign-in panel when the signal read fails. This
would hide a needed action, so this pull request shows the panel when
the signal is unknown.

**Roadmap alignment**

The change supports the roadmap goal for scoped and audited credential
bindings.

**Additional context**

The auth signal returns only `present`, `absent`, or `unknown`. It never
returns a token, identifier, or account name.

## What Changed

- Add `GET /api/companies/:companyId/adapters/:type/auth-signal` with
company and permission checks.
- Add shared auth-signal types and the UI query path.
- Apply a stored Claude login by reference without reading its token.
- Show the provider sign-in panel only for sandbox drivers with
interactive terminal support.
- Block agent creation when the provider test reports missing
authentication.
- Add route, wizard, and end-to-end test coverage.

## Verification

- `pnpm --filter @paperclipai/server test adapter-auth-signal-routes`
passes 50 tests.
- `pnpm --filter @paperclipai/ui test OnboardingWizard` passes 69 tests.
- `pnpm --filter @paperclipai/ui exec tsc --noEmit` exits with code 0.
- The `e2e_shards` lane runs `tests/e2e/onboarding.spec.ts`.

## Risks

The route reads a host-local readiness signal. It returns `unknown` on
read errors and never exposes credential data. The UI may add a sign-in
step when the signal is unavailable.

## Model Used

OpenAI Codex, GPT-5, extended reasoning, tool use, and code execution.
The exact context window was not provided.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-28 10:20:37 -07:00
Nicky LeachandPaperclip 5f1e25c112 test(server): load the company-skills route module graph once per file (#12426)
## Thinking Path

> - Paperclip is an open source app that helps people manage AI agents
for work.
> - The server provides company-scoped routes for company skills and
their test runs.
> - The authorization tests for these routes fail intermittently in
continuous integration.
> - The failure returns HTTP 500 instead of the expected HTTP 403.
> - The test file resets and rebuilds its module graph before each test.
> - This rebuild imports an unmocked issue service and raises a
TypeError.
> - This pull request loads the module graph once per describe block.
> - The change keeps the test result stable and preserves all 53 tests.

## Linked Issues or Issue Description

**What happened?**

The company-skill test-run authorization tests failed intermittently in
continuous integration. One test returned HTTP 500 instead of HTTP 403.

**Expected behavior**

Each unauthorized request must return HTTP 403. The test file must keep
all 53 tests and skip none.

**Steps to reproduce**

1. Run `npx vitest run
server/src/__tests__/company-skills-routes.test.ts` before this change.
2. Repeat the run in continuous integration.
3. Observe the intermittent HTTP 500 result in an authorization case.

**Paperclip version or commit**

Commit `651d26a96f6e24811d336759d3e67ff3abb5ec29`.

**Deployment mode**

Built from source. Continuous integration runs the test suite.

**Agent adapter(s) involved**

Not adapter-specific. This issue affects server test module setup.

**Database mode**

Not database-related.

## What Changed

- Load the mocked route module graph once for each describe block.
- Use the existing `hoistModuleGraph` helper, as the cost service test
does.
- Remove twelve per-test `vi.doUnmock` calls and the redundant module
rebuild.
- Keep the change in
`server/src/__tests__/company-skills-routes.test.ts` only.

## Verification

- Run `npx vitest run
server/src/__tests__/company-skills-routes.test.ts`.
- Confirm that 53 tests pass and 0 tests skip.
- Confirm that the diff changes only
`server/src/__tests__/company-skills-routes.test.ts`.
- Confirm that all Paperclip continuous integration checks pass.

## Risks

Low risk. The change affects test setup only. It does not change
production code, route behavior, or assertions.

## Model Used

OpenAI GPT-5 (Codex), exact model ID `gpt-5`, tool use and code
execution enabled. The runtime does not expose the context window size.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-28 08:09:30 -07:00
Devin Foley 8316ceb0b9 Add the Better Auth issuer column so signup and sign-in work (#12396)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A self-hosted install in `authenticated` mode signs users in with
Better Auth, mounted at `/api/auth` over a hand-written Drizzle
`account` table in `packages/db`
> - Better Auth 1.7.0 added a required `issuer` field to that `account`
model, plus a unique index on `(issuer, accountId)`
> - The dependency bump in #11886 changed only `server/package.json` and
the lockfile, so the Drizzle table never grew the column
> - The Drizzle adapter checks the model against the schema on every
write, so `linkAccount` throws and sign-up answers 500 with an empty
body; a fresh install cannot create its first user, and an upgraded
install locks out every existing user
> - This pull request adds the `issuer` column and its unique index, and
migrates the column in with a backfill that covers every existing row
> - The benefit is that sign-up and sign-in work again, on a new install
and after an upgrade

## Linked Issues or Issue Description

No existing issue. Describing it inline, following
`.github/ISSUE_TEMPLATE/bug_report.yml`.

Refs #11886 (the dependency bump that introduced the required field).
Refs #12269 (an earlier attempt at this fix; its backfill covers only
`provider_id = 'credential'`).

**What happened?**

Sign-up fails on a self-hosted install. `POST /api/auth/sign-up/email`
answers HTTP 500 with a zero-byte body. The server log carries:

```
[Better Auth]: The field "issuer" does not exist in the "account" Drizzle schema.
# SERVER_ERROR: [BetterAuthError: The field "issuer" does not exist in the "account" Drizzle schema.]
```

The request writes the `user` row and then fails on the `account` row.
The address is stuck after that: a second sign-up answers 422
`USER_ALREADY_EXISTS`, sign-in answers 401, and password reset answers
400 `RESET_PASSWORD_DISABLED` because the account that would hold the
password does not exist.

An upgraded install is worse. `sign-in/email` matches the credential
account on `account.issuer === 'local:credential'`. Rows written before
the upgrade have no issuer, so every existing user is locked out.

**Expected behavior**

`POST /api/auth/sign-up/email` answers 2xx and writes both the `user`
row and its credential `account` row. `POST /api/auth/sign-in/email`
then answers 2xx and sets a session cookie. An install that upgrades
keeps its existing users.

**Steps to reproduce**

1. Start a server from `master` with
`PAPERCLIP_DEPLOYMENT_MODE=authenticated` against an empty database.
2. `curl -X POST http://127.0.0.1:<port>/api/auth/sign-up/email -H
'Content-Type: application/json' -H 'Origin: http://127.0.0.1:<port>'
--data
'{"name":"A","email":"a@example.com","password":"a-long-password"}'`
3. The response is HTTP 500 with an empty body.

**Paperclip version or commit**

`master` at 4436cf0. The defect starts at 69e8585 (#11886), which moved
Better Auth from 1.6.28 to 1.7.0.

**Deployment mode**

`authenticated`. `local_trusted` does not sign users in, so it is not
affected. Hosted tenants are not affected either: that path resolves the
actor from a trusted header and never reads `account`.

**Database mode**

Both. Embedded PostgreSQL and external PostgreSQL use the same Drizzle
schema.

**Relevant logs or output**

Reproduced in a test by reverting the schema change:

```
stderr | better-auth-credential-signup.integration.test.ts
[Better Auth]: The field "issuer" does not exist in the "account" Drizzle schema.
AssertionError: expected 500 to be 200
```

## What Changed

- `packages/db/src/schema/auth.ts`: adds `issuer` (text, NOT NULL) to
`authAccounts`, and the `(issuer, account_id)` unique index that mirrors
the index Better Auth declares on the model. The field name, type,
requiredness, and index all come from
`@better-auth/core/dist/db/get-tables.mjs` in 1.7.0.
- `packages/db/src/migrations/0230_better_auth_account_issuer.sql`: adds
the column, backfills every existing row, sets NOT NULL, and creates the
unique index.
- `packages/db/src/migrations/meta/0230_snapshot.json` and
`_journal.json`: regenerated with `pnpm --filter @paperclipai/db
generate`.
- `packages/db/src/better-auth-account-issuer-migration.test.ts`: new.
Asserts the schema shape, then rewinds the migration on a real database,
seeds pre-upgrade rows, and re-applies it.
-
`server/src/__tests__/better-auth-credential-signup.integration.test.ts`:
new. Real sign-up and sign-in through the Better Auth mount, against the
real Drizzle schema and a migrated PostgreSQL.
- `cli/src/__tests__/worktree.test.ts`: the worktree seed fixture writes
a credential `account` row, so it now writes `issuer` too.

`server/package.json` and `pnpm-lock.yaml` are untouched. The dependency
is correct; the schema was what was missing.

### The issuer values, and where they come from

Better Auth builds these itself, in
`@better-auth/core/src/db/schema/account.ts`:

```ts
export function createLocalAccountIssuer(providerId: string): string {
  return `local:${encodeURIComponent(providerId)}`;
}
export function createOAuthAccountIssuer(providerId: string): string {
  return `local:oauth:${encodeURIComponent(providerId)}`;
}
```

Sign-up and sign-in both call `createLocalAccountIssuer("credential")`,
so a credential account is `local:credential`. An OAuth account whose
provider declares no `accountIssuer` of its own is
`local:oauth:<providerId>` — no built-in social provider declares one.
The migration writes exactly those two forms:

```sql
ALTER TABLE "account" ADD COLUMN IF NOT EXISTS "issuer" text;
UPDATE "account"
SET "issuer" = CASE
  WHEN "provider_id" = 'credential' THEN 'local:credential'
  ELSE 'local:oauth:' || "provider_id"
END
WHERE "issuer" IS NULL;
ALTER TABLE "account" ALTER COLUMN "issuer" SET NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS "account_issuer_account_id_uq" ON "account" USING btree ("issuer","account_id");
```

Two limits are worth stating plainly. The OAuth branch reproduces
`createOAuthAccountIssuer` for provider ids that need no
percent-encoding, which covers every built-in provider id; a provider id
with a character `encodeURIComponent` would escape would get a slightly
different string. And a generic-OAuth provider that sets `accountIssuer`
explicitly (Okta, Auth0, Keycloak, Slack, Line) uses the real issuer
URL, which this migration cannot know. Neither case can arise on
Paperclip today: `createBetterAuthInstance` configures
`emailAndPassword` only and registers no social or generic-OAuth
provider, so every existing row is a credential row. The OAuth branch is
there so the backfill stays total rather than leaving a NULL that aborts
`SET NOT NULL`.

## Verification

- `pnpm --filter @paperclipai/db check:migrations` — passes.
- `pnpm --filter @paperclipai/db typecheck` — passes.
- `packages/db` suite: 30 files, 107 tests, all pass.
- `npx tsc --noEmit` in `server/` — no error in any changed file. (The
wrapped `pnpm typecheck` builds the runner vendor first, which needs
cargo; that toolchain was not available here, so the pre-existing
"cannot find module" errors from the unbuilt workspace packages remain
in the bare run.)
- `node --test scripts/__tests__/run-vitest-stable-shard.test.mjs` —
passes with the new server suite in the file list.
- The two new tests were confirmed to fail without the fix:
- Reverting `packages/db/src/schema/auth.ts` to its `master` content
makes the server test fail with the reported error and `expected 500 to
be 200`.
- Narrowing the backfill to `WHERE "issuer" IS NULL AND "provider_id" =
'credential'` makes the migration test fail with `column "issuer" of
relation "account" contains null values` — the failure mode of #12269.
- End to end against a server built from this branch, started with
`PAPERCLIP_DEPLOYMENT_MODE=authenticated` on embedded PostgreSQL:
  - `POST /api/auth/sign-up/email` → 200 with a user and token.
  - `POST /api/auth/sign-in/email` → 200 with a session cookie.
  - `GET /api/auth/get-session` → 200 with the session.
- The stored row is `issuer = 'local:credential'`, `provider_id =
'credential'`, `account_id = user_id`, and `pg_indexes` lists
`account_issuer_account_id_uq`.
- `scripts/docker-onboard-smoke.sh` was not used as proof: it installs
`paperclipai` from npm inside the container, so it exercises a published
release rather than this branch.

## Risks

- **Migration.** The migration backfills every existing row before `SET
NOT NULL`, so an install that upgrades keeps working and its users keep
signing in. `account` is one row per user per provider, so the
full-table `UPDATE` and the index build are cheap;
`packages/db/src/table-size-estimates.ts` already classes `account` as
small, and `check:migrations` passes with no new safety finding.
- **New unique index.** `(issuer, account_id)` is the key Better Auth
resolves accounts by, so a duplicate would already be a defect. Better
Auth writes one credential account per user keyed on the user id, so the
pair is unique by construction. An install that somehow holds a
duplicate would fail the index build rather than corrupt anything, and
the migration is a single transaction.
- **Orphaned users are not repaired.** An address that hit the broken
window has a `user` row and no `account` row. This migration does not
delete or repair those rows, so that address stays unusable after the
upgrade: sign-up says the user exists, and there is no credential
account to sign in as or reset. Only installs that ran a build
containing #11886 are affected, and the repair — deleting the orphaned
`user` rows — is a judgment call about live data that does not belong in
an automatic migration.
- **Not a behavior change anywhere else.** Only the `account` table
changes. Hosted tenants resolve their actor from a trusted header and
never read it.

## Model Used

Claude (Anthropic), Claude Opus, 1M context, extended thinking, agentic
tool use via Claude Code.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip supports opt-in Sentry error monitoring for server and
browser errors.
> - The hosted image must include the server package when an operator
sets SENTRY_DSN.
> - The server package is an optional peer in the source tree, so the
image did not include it.
> - This pull request installs the declared server package in the hosted
image and checks the result.
> - The benefit is a hosted tenant can send server errors without a
manual package install.

## Linked Issues or Issue Description

No public issue exists for this change.

**What happened?**

The hosted image did not include the declared @sentry/node server
package. A hosted tenant could set SENTRY_DSN, but the server could not
load the package from the image.

**Expected behavior**

The hosted image must include the exact @sentry/node version from
server/package.json. The self-hosted image must remain without this
optional package.

**Steps to reproduce**

1. Build or pull the hosted image.
2. Resolve @sentry/node from the server package path.
3. Compare its version with server/package.json.
4. Confirm that the tsx loader path still resolves.

**Paperclip version or commit**

Commit b6ff556a33ebdbe764b7f495951cd59009776608.

**Deployment mode**

Docker hosted image.

## What Changed

- Add a cloud-server-deps Docker stage that installs the declared
@sentry/node version in isolation.
- Copy the isolated package into the cloud image without changing the
production image.
- Add a probe that checks the tsx loader and the resolved Sentry
version.
- Run the probe after the hosted image push in the Docker workflow.
- Add server tests and update the observability documentation.

## Verification

- Run `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/cloud-image-sentry.test.ts`.
- Confirm that the changed test passes in CI.
- Confirm that all pull request checks pass.
- Note that the Docker workflow does not run for pull requests. It runs
after a push to master, for configured tags, or after manual dispatch.

## Risks

- Low risk. The production image body stays unchanged.
- The cloud image adds the declared Sentry package and a small
dependency tree.
- The workflow probe fails if the image loses the tsx loader or resolves
a different Sentry version.

## Model Used

OpenAI GPT-5; exact model version supplied by the execution service;
tool use and code execution; context window not specified.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-27 19:10:49 -07:00
Nicky LeachandPaperclip b7fd6c59b6 fix(server): load the costs-service route module graph once per file (#12375)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The server test suite checks budget and cost routes
> - The costs-service test rebuilt the full route module graph before
every test
> - Synchronous graph rebuilds caused long stalls under CPU load
> - This pull request loads the mocked graph once for each describe
block and keeps per-test mock setup
> - The benefit is a stable 17-test file without production code changes

## Linked Issues or Issue Description

**What happened?**

The costs-service route test rebuilt its full mocked module graph before
every test. Under CPU load, a rebuild sometimes stalled a test past the
15-second timeout.

**Expected behavior**

The test file should load its mocked route graph once for each describe
block while each test keeps isolated mock behavior.

**Steps to reproduce**

1. Run the costs-service route test under synthetic CPU load.
2. Repeat the file test 30 times.
3. Observe intermittent test timeouts before this change.

**Paperclip version or commit**

The test used the current master branch at the time of this change.

**Deployment mode**

Built from source.

## What Changed

- Add `hoistModuleGraph` to load the mocked route graph once for each
describe block.
- Keep per-test mock setup in `beforeEach` so test isolation stays
unchanged.
- Keep all 17 tests and their assertions.
- Remove the module graph rebuild from the per-test path.

## Verification

- Run `npx vitest run src/__tests__/costs-service.test.ts` from
`server/`.
- Confirm that the file reports 17 tests and zero skipped tests.
- Confirm that 30 runs under the same synthetic CPU load report 0.0%
failure after the change, compared with 10.0% before the change.
- Confirm that mutation checks still fail when each authorization guard
is broken.

## Risks

This change affects test setup only. The main risk is weaker test
isolation if a mock keeps state between tests. Each test still re-arms
its mock behavior in `beforeEach`, and the full assertion set remains.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. This bug fix does not add a
core feature.

## Model Used

OpenAI GPT-5. The model used tool calls and code execution. The exact
context window and reasoning configuration were not exposed by the
runtime.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` /
`Closes: #` / `Refs: #` OR (b) described the issue in-PR following the
relevant issue template
- [x] I have 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 (the submitting engineer
ran the file before handoff)
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-27 18:37:30 -07:00
Nicky LeachandPaperclip 036600d922 fix(db): close test database clients before the embedded Postgres cluster stops (#12335)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip uses database clients and embedded PostgreSQL test
fixtures
> - A fixture stopped its embedded PostgreSQL cluster while clients
still held connections
> - The postgres.js driver then scheduled a write on a stopped
connection
> - That write escaped the timer callback and caused a test process to
exit with an error
> - This pull request closes registered clients before the fixture stops
its cluster
> - The benefit is stable test teardown and clear failure reporting in
continuous integration

## Linked Issues or Issue Description

Refs: #10869

**What happened?**

An embedded PostgreSQL test fixture stopped its cluster while database
clients still held open connections. The postgres.js driver then
scheduled a deferred write on a dead connection. The write caused an
unhandled error after the test shard reported success.

**Expected behavior**

The fixture closes all live clients for its cluster before it stops the
embedded PostgreSQL cluster. Tests then finish without a deferred write
on a dead connection.

**Steps to reproduce**

1. Run the database regression test with the embedded PostgreSQL
fixture.
2. Stop the fixture while its database client still has an open
connection.
3. Observe the deferred write and the process exit status.

**Paperclip version or commit**

Branch base: bdd8f1bed. Change head:
93e85d2ba1.

**Deployment mode**

Local dev with the embedded PostgreSQL test fixture.

**Installation method**

Built from source with pnpm.

**Agent adapter(s) involved**

Not adapter-specific. This change covers database test infrastructure.

**Database mode**

Embedded PGlite.

**Additional context**

The change keeps client references weak and keys them by host and port.
It does not retain credentials. It also handles connection URLs that the
driver accepts when the URL parser rejects them.

## What Changed

- Add a registry for live database clients in the database package.
- Close registered clients before the embedded PostgreSQL fixture stops
its cluster.
- Add a regression test for the teardown race.
- Handle driver-compatible URLs that the standard URL parser rejects.
- Add cleanup for the shared route test harness.

## Verification

- Run the full `packages/db` suite.
- Run `tsc --noEmit` in `packages/db`.
- Run the server suite that uses `route-test-harness.ts`.
- Run the teardown regression test five times.
- Confirm that the negative control fails three times.
- Confirm that no shard reports green tests and exits with an error.

## Risks

The registry changes client cleanup for embedded test fixtures. Weak
references limit retained memory in long-lived processes. The registry
uses host and port only, so it does not retain credentials. No
migration, schema, API, telemetry, authentication, or cryptography
change exists.

## Model Used

OpenAI Codex, GPT-5, tool use and code review support, standard
reasoning mode. The implementing engineer supplied the code and
verification results.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-27 13:39:42 -07:00
Nicky LeachandPaperclip 666f5a6e69 fix(server): make the wake-claim lease test deterministic (#12331)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The server test suite checks question response delivery and wake
claims
> - One test used wall-clock time and could start a second delivery
under load
> - The second delivery reused one promise resolver and could hang for
15 seconds
> - This pull request uses the injected clock and one resolver for each
wakeup
> - The benefit is a deterministic test that fails at once if a second
wakeup occurs

## Linked Issues or Issue Description

**What happened?**

The wake-claim lease test slept for 70 milliseconds before it ran the
pending sweep. Under load, the lease could look stale during that
interval. The sweep then started a second delivery. The second delivery
reused one promise resolver, so the test hung until the 15-second suite
timeout.

**Expected behavior**

The test must control the time used by the service. One wakeup must use
one resolver. An unexpected second wakeup must fail at once.

**Steps to reproduce**

1. Run the question response delivery test under CPU load.
2. Let the test sleep before the pending sweep.
3. Observe that a second delivery can start and the test can reach the
15-second timeout.

**Paperclip version or commit**

Commit `0dd735e53a5cc9f6d3395834b826dfb0b1da2ea9`.

**Deployment mode**

Built from source with the server test suite.

**Installation method**

Built from source.

**Agent adapter(s) involved**

Not adapter-specific (core test issue).

**Database mode**

Not database-related.

**Additional context**

The change affects one test file. It does not change production source
code.

## What Changed

- Drive the test with the service's injected clock.
- Give each wakeup call its own promise resolver.
- Assert that lease renewal advances the last attempt time.
- Assert that the wakeup runs one time and the attempt count stays at 1.

## Verification

- Run `pnpm exec vitest run
server/src/services/__tests__/question-response-delivery.test.ts`.
- The changed file reports 29 passing tests.
- Run the changed file 25 times, including 5 runs under CPU load.

## Risks

Low risk. The change affects one test file and test setup only. It does
not change production behavior.

## Model Used

OpenAI GPT-5, exact runtime model ID supplied by the Paperclip agent
environment, tool use and code review assistance.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-27 13:11:15 -07:00
Devin Foley bc1a21564f Remove the company brand color and per-company attachment limit (#12291)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A company is the top-level container, and the company General page
holds its settings
> - Two of those settings did almost nothing: the brand color only
tinted the generated company icon, and the attachment size limit sat
under the deployment-level `PAPERCLIP_ATTACHMENT_MAX_BYTES` cap that
already bounded every upload
> - A setting that changes one icon hue, and a setting that can only
lower a limit the operator already set, are not worth the page space or
the code that carries them
> - This pull request deletes both settings from the UI, the validators,
the API contract, the server, and the database
> - With the deployment cap as the only limit left, the message a person
sees when an upload is rejected has to name that limit in terms they can
act on, so the raw byte count becomes a human-readable size
> - The benefit is a shorter company General page for every deployment,
one attachment limit instead of two, and less code between an upload and
its ceiling

## Linked Issues or Issue Description

No existing issue. The description below follows
`.github/ISSUE_TEMPLATE/enhancement.yml`.

**What existing behavior does this improve?**

The company General page (`/company/settings`), the `PATCH
/api/companies/{companyId}` and `PATCH
/api/companies/{companyId}/branding` request contracts, and the
attachment upload limit on task, case, and company-import uploads.

**Subsystem affected**

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

**Current behavior**

The company General page shows an "Appearance" section with three
controls: Logo, Brand color, and Attachment size limit. The brand color
is a hex value that feeds one thing — the hue of the generated company
pattern icon. Companies that never set one already get a hue derived
from the company name. The attachment size limit is a per-company byte
count stored on `companies.attachment_max_bytes`. Every upload path
clamps it against the deployment-level `PAPERCLIP_ATTACHMENT_MAX_BYTES`
cap, so the per-company value can only lower a limit the operator
already chose.

**Proposed behavior**

The Appearance section keeps the Logo control only. The company pattern
icon always derives its hue from the company name. Every attachment path
reads the deployment cap directly, so `PAPERCLIP_ATTACHMENT_MAX_BYTES`
is the single limit. An upload rejected by that limit says so in human
units — "File is larger than the 10 MB limit" rather than a raw byte
count. The `companies.brand_color` and `companies.attachment_max_bytes`
columns are dropped, and both fields leave the company API contract.

**Reason and benefit**

Both settings ask an operator to make a decision that changes almost
nothing. The brand color moves one icon hue on a page that also lets you
upload a real logo, which overrides the icon entirely. The attachment
limit reads as a real control but cannot raise anything, so it is a
second place to look when an upload is rejected. Removing both shortens
the page every deployment sees, removes a company-scoped read from the
task attachment upload path, and leaves one attachment limit to reason
about instead of two.

**Breaking changes**

The company API responses no longer include `brandColor` or
`attachmentMaxBytes`, and `GET /api/invites/{token}` no longer includes
`companyBrandColor`. `PATCH /api/companies/{companyId}/branding` is
strict, so a request that sends `brandColor` now returns 400; the
non-strict `PATCH /api/companies/{companyId}` schema strips it. Company
packages exported by older versions still import: the portability
company manifest schema is non-strict, so the retired keys are stripped
and ignored rather than rejected. Companies that stored a brand color
lose it — their icon reverts to the name-derived hue that every company
without a color already used.

## What Changed

- Removed the "Brand color" and "Attachment size limit" fields from the
company General page, along with their state, dirty checks, save
payload, and Save-button gating.
- Removed `brandColor` and `attachmentMaxBytes` from
`createCompanySchema`, `updateCompanySchema`, and
`updateCompanyBrandingSchema`, and deleted the now-orphaned
`DEFAULT_COMPANY_ATTACHMENT_MAX_BYTES` and
`MAX_COMPANY_ATTACHMENT_MAX_BYTES` constants.
- Removed both fields from the `Company` type, the portability manifest
type and schema, and the `companiesApi.update` payload allowlist.
- Dropped `brandColor` from `CompanyPatternIcon` and its callers, so the
icon hue always comes from the company name. Deleted the now-unused
`hexToHue` helper and the now-unused `pickTextColorForSolidBg` export.
- Stopped emitting `brandColor` from the company service selection and
from the invite-summary and invite-branding payloads in
`server/src/routes/access.ts`.
- Replaced `normalizeIssueAttachmentMaxBytes` with the deployment cap:
task attachments, case attachments, and company import now use
`MAX_ATTACHMENT_BYTES` directly. The helper is deleted.
- Added `formatAttachmentSize()` next to `MAX_ATTACHMENT_BYTES` and
routed every over-limit message through it, so a rejected upload names
the limit in human units instead of raw bytes: `Image exceeds 10485760
bytes` becomes `Image is larger than the 10 MB limit`. Enforcement is
unchanged — the same single cap, the same multer limits, the same status
codes and response shapes.
- Added migration
`0229_drop_company_brand_color_and_attachment_max_bytes.sql` and removed
both columns from the Drizzle `companies` schema.
- Kept legacy imports working: the portability company manifest schema
is non-strict, so older packages carrying the retired keys still import
with the keys ignored.
- Updated the skill API reference and the implementation spec, and
pruned the token-extraction allowlist entries that the removed code made
stale.

## Verification

Commands run from the repository root:

- `pnpm --filter @paperclipai/shared typecheck` — pass
- `pnpm --filter @paperclipai/db typecheck` — pass (includes
`check:migrations`, which validates the new migration number and journal
entry)
- `pnpm --filter @paperclipai/ui typecheck` — pass
- server typecheck via `node_modules/.bin/tsc --noEmit` in `server/` —
pass. `pnpm --filter @paperclipai/server typecheck` could not run
locally because it builds the Rust runner first and `cargo` is not
installed on this machine; the TypeScript step it wraps is the command
above.
- `npx vitest run packages/shared/src/validators/company.test.ts` — 6
passed
- `npx vitest run server/src/__tests__/company-portability.test.ts` — 90
passed
- `npx vitest run server/src/__tests__/attachment-types.test.ts
server/src/__tests__/assets.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/company-portability.test.ts
server/src/__tests__/cases-routes.test.ts` — 165 passed (the
human-readable limit messages)
- `npx vitest run server/src/__tests__/company-branding-route.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/invite-summary-route.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/companies-route-cross-company-authz.test.ts` — all
passed
- `npx vitest run cli/src/__tests__/company.test.ts
cli/src/__tests__/company-delete.test.ts` — 27 passed
- `npx vitest run` in `ui/` — 4425 passed, 1 pre-existing failure
unrelated to this change (`OnboardingWizard.test.tsx` "renders instead
of throwing when the browser denies storage access", which also fails on
`master`)
- `npx vitest run` in `server/` — see the note below
- `node scripts/check-token-gates.mjs` — no new violations; the only
reported violations are the pre-existing `PillGuy.tsx` ones present on
`master`

New tests added:

- `packages/shared/src/validators/company.test.ts` — the create and
update schemas strip the retired keys, the strict branding schema
rejects `brandColor`, and the portability manifest schema accepts a
legacy entry carrying both keys and drops them.
- `server/src/__tests__/company-branding-route.test.ts` — `PATCH
/api/companies/{companyId}/branding` returns 400 for `brandColor` and
does not call the company service.
- `server/src/__tests__/company-portability.test.ts` — a legacy package
that declares `brandColor` and `attachmentMaxBytes` imports
successfully, and neither key reaches `companies.create`.
- `server/src/__tests__/issue-attachment-routes.test.ts` — the effective
task attachment limit is the deployment cap, and the route no longer
loads the company to size an upload.
- `server/src/__tests__/attachment-types.test.ts` —
`formatAttachmentSize()` renders the default cap as `10 MB`, keeps one
decimal place for fractional sizes and drops a trailing `.0`, falls back
to KB and bytes for small caps, steps up to GB, and never emits `NaN`
for a degenerate input.
- `server/src/__tests__/assets.test.ts` — the asset-image and
company-logo routes both return the human-readable limit message on an
over-cap upload.

## Merge with master

`master` moved while this was open, and the merge needed two
resolutions:

- **`ui/src/pages/CompanySettings.tsx`.** #12243 reworded the
user-facing
copy from "company" to "organization", and that rewording landed inside
  the "Brand color" and "Attachment size limit" hints — the two fields
this change deletes. Both fields are removed, so the conflicted block is
dropped whole. The Logo field and every other copy change from #12243
are
  kept.
- **Migration renumbered 0228 -> 0229.** #12307 landed
  `0228_nasty_grim_reaper`, so this migration is now
`0229_drop_company_brand_color_and_attachment_max_bytes`. Its snapshot
is
rebuilt from master's `0228_snapshot.json` with only the two `companies`
  columns removed, and `meta/_journal.json` is master's journal plus a
single `idx: 229` entry. `pnpm --filter @paperclipai/db
check:migrations`
  passes.

The snapshot was rebuilt by hand rather than taken from `drizzle-kit
generate`, because master's `0228_snapshot.json` has drifted from
master's
own schema: `issue_question_response_deliveries.error_count` is created
by
master's 0228 SQL but missing from its snapshot, and the snapshot still
carries `decision_archive_notification_outbox.error_count`. Regenerating
folds both into this migration, and the resulting `ADD COLUMN
error_count`
would fail on a fresh database where master's 0228 already created that
column. Rebuilding from master's snapshot leaves that drift exactly
where
it is and keeps this migration to the two column drops. The drift is
pre-existing on master and is not addressed here.

## Risks

- **The migration is a destructive column drop.**
`0229_drop_company_brand_color_and_attachment_max_bytes.sql` removes
`companies.brand_color` and `companies.attachment_max_bytes`. It is safe
because both features are removed in the same change and nothing reads
either column after it. The statements use `DROP COLUMN IF EXISTS`,
matching the convention of the recent drop migrations in this
repository. The drop is not reversible: a downgrade after this migration
loses any stored values.
- **Stored brand colors are lost.** A company that had set a color now
renders the name-derived icon hue that every company without a color
already used. No other surface changes, and an uploaded logo still
overrides the icon.
- **API response shape narrows.** `brandColor` and `attachmentMaxBytes`
leave the company payloads, and `companyBrandColor` leaves the invite
summary payload. A client reading those fields now sees `undefined`. The
bundled UI and CLI are updated in this change.
- **Legacy imports are covered.** Packages exported by older versions
still carry both keys. The manifest schema is non-strict, so the keys
are stripped rather than rejected, and a test locks that in.
- **The over-limit message strings changed.** Anything matching on the
old `... exceeds N bytes` text — a test, a script, or a client that
string-matches `body.error` — needs updating. The status codes (422) and
response shapes are unchanged, so structured clients are unaffected.
- **Attachment limits can only widen.** A deployment that had lowered a
company below the deployment cap now allows uploads up to the cap for
that company. Lower `PAPERCLIP_ATTACHMENT_MAX_BYTES` if a smaller
ceiling is needed.
- **Storybook visual baselines shift** for the `CompanyPatternIcon`
matrix story, because those fixtures had brand colors. That workflow
runs only on a PR labeled `storybook-visual`, so it does not gate this
PR; regenerate the baselines if the label is added.

## Model Used

Claude (Anthropic), Claude Opus, agentic tool use via Claude Code.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - An instance can turn on the `enableManagedSandboxOnly` feature,
which hides the local environment and runs every agent in the
platform-managed environment
> - That feature already gated the environment pickers, the onboarding
wizard, and the server-side run selection, but many other screens still
showed absolute paths on the execution host and still let the user pick
an execution engine
> - On such an instance those controls name a filesystem the user cannot
reach; a path written there is stored and then ignored, which reads as a
broken control
> - This pull request hides the remaining host-path and execution-engine
surfaces behind the same feature, adds a server rule that refuses a
project-workspace path write while the feature is on, and closes a
related route gap in the isolated-workspace pages
> - The benefit is that a managed instance shows no host path and no
folder picker anywhere, and a write that carries a path now fails with a
clear message instead of being silently discarded

## Linked Issues or Issue Description

No public issue exists. The description below follows
`.github/ISSUE_TEMPLATE/enhancement.yml`.

**What existing behavior does this improve?**

The `enableManagedSandboxOnly` instance feature, and the UI surfaces
that show
a host filesystem path: project properties, the new-project dialog, the
project
workspace and execution workspace detail pages, the workspace and task
cards,
plugin local folders, and the agent configuration form with its
per-adapter
fields. It also improves route gating for `enableIsolatedWorkspaces`.

**Subsystem affected**

Cross-cutting (`ui/` and `server/`).

**Current behavior**

When `enableManagedSandboxOnly` is on, the local environment disappears
from the
environment pickers and the server refuses to run an agent on the local
host.
Everything else stays visible. A user still sees:

- the project "Local folder" row, its absolute path, and the
Set/Change/Clear buttons
- the "Local folder" field and its "Choose" folder picker in the
new-project dialog
- the "Local path" field and fact row on a project workspace
- the "Paths" and "Lifecycle commands" groups on an execution workspace
- the working directory on workspace cards, task properties, and runtime
service rows
- the plugin "Local folders" section
- "Working directory (deprecated)", "Command", "Execution engine",
"ACP server command", "ACP state directory", and "Agent instructions
file" in
  the agent configuration form

A path typed into any of these names a filesystem no agent on the
instance uses.
The project workspace API also accepts a `cwd` write and stores it.

Separately, `/workspaces`, `/execution-workspaces/*`, and
`/projects/:projectId/workspaces/:workspaceId` render for anyone who
types or
bookmarks the URL, even with `enableIsolatedWorkspaces` off. Only the
sidebar
entry reads that flag.

**Proposed behavior**

With `enableManagedSandboxOnly` on, none of those surfaces render. A
project
whose codebase came from a managed checkout keeps its one-line
"Paperclip-managed folder." label and shows no path. The non-path
controls stay:
repo URL, branch, service URL, port, command output, ACP session mode,
ACP
non-interactive permissions, Codex fast mode, and the sandbox toggles.

The project-workspace create and patch routes, and the nested workspace
on
project create, answer `422` with
"This instance runs agents only in the platform-managed environment;
local
folders are not configurable." when the payload carries a non-null
`cwd`.
A `cwd: null` write still passes, so an instance that just turned the
feature on
can clear a stale path.

With `enableIsolatedWorkspaces` off, the three workspace route groups
redirect to
the dashboard.

**Reason and benefit**

A control that cannot do anything is worse than a missing control: the
user fills
it in, saves, and gets no error and no effect. The server rule turns
that silent
no-op into a clear refusal. The route gate stops a feature that an
instance has
turned off from staying reachable by URL, which is the same standard the
Cases,
Pipelines, and hidden-settings pages already meet.

**Breaking changes**

None for a default instance: both flags are off by default for
self-hosted and
managed instances, so nothing changes unless an operator turns them on.
Stored
`adapterConfig` values are never cleared, so turning the feature off
restores
every previous value.

## What Changed

- Add `ui/src/hooks/useManagedSandboxOnly.ts`, modelled on
`useAppsEnabled`, for
components that do not already read the experimental settings. It
exposes
`hideHostPaths`, which fails closed while the settings query is in
flight, so
a cold cache never flashes a host path before the policy resolves.
Components
that keep their own settings read compute the same gate from
`isFetched`.
- Add `managedSandboxOnly` to `AdapterConfigFieldsProps` and populate it
where
`AgentConfigForm` builds the adapter field props. Resolve the effective
instructions-file gate once as `hideInstructionsFile || hideHostPaths`,
so
  every adapter hides that path field with no per-adapter edit.
- Hide under the flag: the project "Local folder" block and its
absolute-path
  edit panel (a managed checkout keeps its label, without the path); the
new-project "Local folder" field; the project-workspace "Local path"
field and
fact row; the execution-workspace "Paths" and "Lifecycle commands"
groups; the
working directory on the workspace summary card, the task workspace
card, the
task properties "Folder" row, and the runtime service rows; the plugin
"Local
folders" section; "Working directory (deprecated)" and "Command" in the
agent
form; and the per-adapter "Execution engine", "ACP server command", and
"ACP state directory" for `claude_local`, `codex_local`, and
`gemini_local`.
- Drop two working-directory fallbacks that had no gate to read: the
close-workspace
dialog now falls back to "No additional details", and the reuse-existing
  workspace label and picker subtitle fall back to a neutral phrase.
- Refuse a non-null `cwd` with `422` on `POST /projects/:id/workspaces`,
`PATCH /projects/:id/workspaces/:workspaceId`, and the nested workspace
on
  `POST /companies/:companyId/projects`, following the
  `assertNoAgentHostWorkspaceCommandMutation` precedent on those routes.
- Add `IsolatedWorkspacesRouteGate` and wrap the `/workspaces`,
`/execution-workspaces/*`, and
`/projects/:projectId/workspaces/:workspaceId`
  routes with it.
- Leave the SSH "Remote workspace path" and the workspace file browser
alone,
  with a comment explaining why.

## Verification

Automated:

- `pnpm --filter @paperclipai/ui exec vitest run` — 479 of 480 files
pass
(4455 of 4456 tests). The one failure is `OnboardingWizard.test.tsx >
renders
instead of throwing when the browser denies storage access`, which also
fails
  on `origin/master` and is unrelated to this change.
- `pnpm --filter @paperclipai/server exec vitest run project workspace
instance-settings`
— 45 of 50 files pass. Four files fail on macOS for reasons unrelated to
this
  change: `workspace-instance-cleanup`, `workspace-runtime`,
  `execution-workspace-runtime-control-conflict`, and
  `workspace-runtime-exposure` compare `/var/...` against the resolved
`/private/var/...` or bind real ports. The same files fail on a clean
`master`
  checkout on the same machine.
- `pnpm --filter @paperclipai/ui typecheck`
- `tsc --noEmit` in `server/` (after
`pnpm --filter @paperclipai/plugin-sdk ensure-build-deps`). The package
`typecheck` script also builds the Rust runner, which needs `cargo`; it
is not
  installed on the machine that ran this.

New and extended tests:

- `ui/src/adapters/managed-sandbox-only-config-fields.test.tsx` — the
three
adapters drop the execution engine, the ACP paths, the instructions-file
path,
and every "Choose" button when the flag is on, and keep the non-path
controls.
- `ui/src/components/AgentConfigForm.render.test.tsx` — flag-on and
flag-off
renders for the working directory, the command, the engine, the ACP
paths, and
  the resolved adapter field props.
- `ui/src/components/ProjectProperties.managed-sandbox.test.tsx`,
  `ui/src/components/NewProjectDialog.managed-sandbox.test.tsx`,
  `ui/src/pages/ProjectWorkspaceDetail.test.tsx`,
  `ui/src/components/ProjectWorkspaceSummaryCard.test.tsx`,
  `ui/src/components/WorkspaceRuntimeControls.test.tsx`.
- `ui/src/components/IsolatedWorkspacesRouteGate.test.tsx` — redirect
when off,
  render when on, and render nothing while the flag query is in flight.
- "Still loading" cases for the project properties, the new-project
dialog, the
workspace summary card, the runtime service rows, and the agent
configuration
form, each asserting that no host path renders before the policy
resolves.
-
`server/src/__tests__/project-workspace-managed-sandbox-routes.test.ts`
— the
  `422` on all three write paths, the `cwd: null` pass-through, and the
  flag-off pass-through.

Manual check to reproduce: turn on Managed Environment Only in instance
experimental settings, then open a project, the new-project dialog, an
agent's
configuration, and a workspace page. No path, folder icon, or "Choose"
button
appears. Turn the setting off and each control returns with its stored
value.

No documentation change was needed. The operator-facing text for both
settings
lives in the feature catalog entry, which already states the contract
this pull
request now enforces across the UI.

## Risks

- Low. Both flags default to off, so a default instance is unchanged.
- The hidden fields are presentation only. No stored `adapterConfig`
value is
cleared, because an import carries adapter configuration written on
another
instance and clearing it would break that flow. Turning the setting off
shows
  every previous value again.
- The `422` is the one behavior change for an API caller, and only while
the
setting is on. `cwd: null` still passes so a stale path can be cleared.
- The route gate renders nothing until the flag query settles, so an
instance
with isolated workspaces on never flashes a redirect. An instance with
the
  feature off now redirects a bookmarked workspace URL to the dashboard.
- Every host-path guard fails closed while the settings query is in
flight, so a
default instance shows those controls a moment later than before on a
cold
load. That is the safe direction: the alternative flashes a path a
managed
  instance must never show.
- Two path surfaces stay on purpose, each with a comment: the SSH
"Remote
workspace path" is a path on the user's own remote host, and the
workspace file
browser shows workspace-relative paths. The instance Adapters page also
keeps
its "Local path" install option, since that page is an instance-admin
surface
the hosting operator can already hide through the hidden-settings
mechanism.

## Model Used

Claude (Anthropic), Claude Opus, 1M context window, extended thinking,
agentic
tool use 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-27 11:59:14 -07:00
Devin Foley 76f7019bdf feat(server): derive hosted-tenant issue prefixes from the company name and follow renames (#12292)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Every company has an issue prefix. It is the visible half of each
task and case identifier, and a self-hosted company derives it from the
name it was created with
> - A hosted or managed instance does not use the create-company flow.
The trusted-header auth path claims the tenant company instead
> - That path minted the prefix from a hash of the stack id, and it
wrote a placeholder description that nobody chose
> - So a hosted company showed opaque task IDs such as `PC7F2A-14`, and
a rename never changed them
> - This pull request derives the prefix from the company name on that
path too. It re-derives the prefix when the name changes on a managed
instance, and it rewrites the stored issue and case identifiers so
existing tasks follow the rename
> - It also repairs each company that an earlier build claimed. The
repair runs once, on the next authenticated request
> - The benefit is that task IDs on a hosted instance read like the ones
on a self-hosted instance, and they stay correct after a rename

## Linked Issues or Issue Description

No public issue exists. The description below follows
`.github/ISSUE_TEMPLATE/enhancement.yml`.

**What existing behavior does this improve?**

The tenant company claim in `resolveCloudTenantActor`
(`server/src/middleware/auth.ts`) and the company update in
`companyService.update` (`server/src/services/companies.ts`). Both
decide the
`issue_prefix` and the `description` of a company on a hosted or managed
instance.

**Subsystem affected**

`server/` — REST API and orchestration services. One small hint was also
added
in `ui/`.

**Current behavior**

A self-hosted company gets its issue prefix from its name. "Acme
Robotics"
becomes `ACM`, and its tasks read `ACM-14`.

A hosted or managed instance claims the company through the
trusted-header auth
path. That path wrote a different prefix: `"PC"` plus the first four hex
characters of the SHA-256 of the stack id. The same path also wrote a
placeholder description, `"Provisioned by ... for stack <stack id>."`.

The result is a task ID such as `PC7F2A-14`. It says nothing about the
company.
A later rename of the company does not change it, because nothing
re-derives
the prefix after creation.

**Proposed behavior**

The claim path derives the prefix from the company name, exactly as the
create-company flow does. It writes no description.

On a managed instance, a rename re-derives the prefix. The stored issue
and
case identifiers move with it, so `ACM-14` becomes `NOR-14` when "Acme
Robotics" becomes "Northwind Traders". A rename that keeps the same
three-letter
base keeps the current prefix, including any disambiguating suffix.

A self-hosted instance is unchanged. A rename there still keeps the
prefix the
company was created with.

Companies that an earlier build already claimed get a one-time repair on
their
next authenticated request. The repair re-derives the prefix from the
current
name, re-keys the identifiers, and clears the placeholder description.

**Reason and benefit**

A task ID is the primary handle for a task. People type it, paste it
into chat,
and read it in a URL. On a hosted instance that handle was an opaque
hash, and
it disagreed with the company name that the same user chose during
signup. The
name is the only prefix source a hosted user ever supplies, so the
prefix now
follows it.

**Breaking changes**

Yes, on hosted and managed instances only. A company rename now rewrites
the
stored issue and case identifiers. Links that carry an old identifier
stop
resolving after the rename. The company settings page states this before
the
user saves. The one-time repair applies the same rewrite once to
companies that
carry the old hash prefix. Self-hosted behavior does not change.

## What Changed

- Added `server/src/services/issue-prefix.ts`. It holds the prefix
helpers that
used to live inside the `companyService` closure:
`ISSUE_PREFIX_FALLBACK`,
  `deriveIssuePrefixBase`, `issuePrefixSuffixForAttempt`, and
  `isIssuePrefixConflict`. The companies service now imports them.
- Added `pickAvailableIssuePrefix` to that module. It reads the prefixes
in one
base family and returns the first free candidate. A standalone `INSERT`
can
retry on a unique violation, because each failed statement is its own
implicit
transaction. A caller that already holds a transaction cannot, because
the
violation aborts the whole transaction. Such a caller picks first, then
writes.
- Added `rekeyCompanyIssueIdentifiers` to that module. It rewrites the
prefix of
the stored `issues.identifier` and `cases.identifier` values of one
company in
  the caller's transaction, and it returns the two row counts.
- `companyService.update` re-derives the prefix when the name changes on
a
managed instance, re-keys both tables in the same transaction, and
writes a
  `company.updated` activity entry after the commit.
- `resolveCloudTenantActor` claims the company with a name-derived
prefix and a
null description. The claim retries with the next suffix when the prefix
is
  taken.
- `resolveCloudTenantActor` also runs a one-time repair for companies
that carry
the old hash prefix. An exact-match fence on the update lets a
concurrent
rename win. The repair is idempotent, because its guards stop matching
after
  it lands.
- The rename takes a row lock on the company before it compares anything
against
it, and it re-keys from the prefix it reads under that lock. Only patch
and
environment facts gate the lock, so no stale read can steer the
decision. Two
overlapping updates would otherwise leave a company whose prefix
disagrees with
its own identifiers, in either direction: two renames, where the second
re-keys
from a prefix the first already moved; or a rename plus a stale form
that
resubmits the original name, where the second sees an unchanged name,
skips
re-derivation, and restores the old name on top of the first rename's
prefix.
Only a managed instance takes the lock, and only for an update that
carries a
  name.
- Both helpers compare an exact identifier head instead of a LIKE
pattern. A
  stored prefix is data, so it must never be read as a pattern.
- The company settings page shows a hint under the name field on a
managed
  instance: renaming can change the task ID prefix.

## Verification

Automated tests:

```
pnpm --filter @paperclipai/server exec vitest run \
  src/services/issue-prefix.test.ts \
  src/__tests__/companies-service.test.ts \
  src/__tests__/cloud-tenant-company-provisioning.test.ts \
  src/middleware/cloud-tenant-actor.test.ts \
  src/__tests__/auth-session-route.test.ts \
  src/__tests__/cloud-routes.test.ts \
  src/__tests__/cloud-instance.test.ts \
  src/__tests__/company-branding-route.test.ts \
  src/__tests__/company-cloud-floor.test.ts \
  src/__tests__/companies-route-cross-company-authz.test.ts \
  src/__tests__/companies-route-path-guard.test.ts \
  src/__tests__/company-portability.test.ts
pnpm --filter @paperclipai/ui exec vitest run
pnpm --filter @paperclipai/ui typecheck
```

New coverage:

- `server/src/services/issue-prefix.test.ts` covers the derivation, the
suffix
  ladder, the cause-chain walk of the unique-violation detector, and
  `pickAvailableIssuePrefix` against a stubbed select.
- `server/src/__tests__/companies-service.test.ts` covers a managed
rename
against a real Postgres database: the prefix moves, both identifier
tables are
re-keyed, and the activity entry is written. It also covers a same-base
rename,
  a collision that takes the suffixed candidate, a non-name patch, and a
self-hosted rename that leaves the prefix alone. Two more tests drive
the
overlap cases: two concurrent renames of the same company, and a rename
racing
a stale form that resubmits the original name. Both assert that the
surviving
name's base matches the company prefix and that the stored identifiers
sit on
  that prefix.
- `server/src/__tests__/cloud-tenant-company-provisioning.test.ts`
covers the
claim path and the repair against a real Postgres database: a
name-derived
prefix, a null description, a suffixed prefix on collision, the full
repair,
  a second pass that changes nothing, a description-only repair, and an
  operator-written description that the repair leaves alone.
- `ui/src/pages/CompanySettingsRenameHint.test.tsx` covers the hint on a
managed
  instance and its absence on a self-hosted instance.

The `substring` cast in `rekeyCompanyIssueIdentifiers` is load-bearing
and the
database tests prove it. The driver binds the offset as text. Without
the
`::int` cast Postgres resolves the SQL-regex overload of `substring`,
and every
identifier becomes NULL.

## Risks

- **Re-keying changes existing identifiers and URLs.** This is
deliberate, and
it happens on hosted and managed instances only. After a rename, a link
that
carries an old task identifier stops resolving. The settings page warns
about
  this before the user saves.
- **Identifiers inside comment text are not rewritten.** Only the
`identifier`
columns of `issues` and `cases` move. A task ID that someone typed into
a
  comment, a description, or a document keeps the old prefix.
- **A lost prefix race inside the rename transaction surfaces as a
conflict.**
The rename picks a free prefix and then writes, because a unique
violation
inside a transaction aborts the whole transaction. Two *different*
companies
renamed onto the same base at the same moment can still collide. The
loser
sees its PATCH fail with the unique violation. The write is retryable by
the
client, and the window is a single statement wide. Two renames of the
*same*
company no longer race: the row lock serializes them, and the second one
  re-keys from what the first committed.
- **The rename holds a row lock.** A managed rename takes `SELECT ...
FOR UPDATE`
on its own company row for the rest of the transaction. It is one row,
and no
other path in the transaction locks a company row, so there is no
lock-order
cycle. A self-hosted instance and every non-rename company update never
reach
  the lock.
- **The one-time repair is best effort.** It runs inside a try/catch and
logs a
warning on failure, so it never blocks authentication. A failed pass is
retried
  on the next request, because its guards still match.
- No schema change and no migration.

## Model Used

- Provider: Anthropic (Claude)
- Model: Claude Opus, model id `claude-opus-5[1m]`
- Context window: 1M
- Reasoning mode: extended thinking
- Capabilities used: agentic tool use through Claude Code (file edits,
shell,
  test runs against an embedded Postgres database)

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - Hosting operators (a managed cloud, an internal shared server) tune
the settings surface with `PAPERCLIP_HIDDEN_SETTINGS`, but hiding a
control never changes its value
> - An instance whose stored feedback-sharing preference is still the
schema default ("prompt") keeps prompting users even when the operator
hid the control, leaving them no way to answer
> - More generally, operators have no supported way to change what a
setting defaults to without patching code
> - This pull request adds `PAPERCLIP_SETTING_DEFAULTS`, a generic
operator-supplied read-time default overlay for registry-listed general
settings
> - The benefit is that any hosting operator can pair "hide the control"
with "default the value", while explicit user choices and self-hosted
stock behavior stay untouched

## Linked Issues or Issue Description

No public issue exists; following the enhancement template:

**What existing behavior does this improve?**
Hosting operators need to supply the default value of selected instance
settings (first: `feedbackDataSharingPreference`) via configuration,
without patching code and without a hard-coded, opinionated constant in
the product.

**Subsystem affected**
Server (instance-settings service, feedback service, boot) and
`packages/shared` (settings schemas).

**Current behavior**
Setting defaults are fixed in the shared zod schemas.
`PAPERCLIP_HIDDEN_SETTINGS` can hide the feedback-sharing control and
floor writes, but the stored value stays "prompt", so issue-chat
surfaces keep prompting with no way to answer.

**Proposed behavior**
`PAPERCLIP_SETTING_DEFAULTS` takes a JSON object validated against a
shared registry of defaultable fields. The operator value substitutes
for the schema default at read time: a field whose effective value is
still the schema default resolves to the operator value; an explicit
non-default user choice always wins. Never persisted; unsetting the
variable restores stock behavior. Malformed JSON or an invalid value for
a known field refuses startup (fail closed); unknown field names warn
and are ignored (mixed-version fleet safe).

**Reason and benefit**
Any hosting operator can pair "hide the control" with "default the
value" without forking the product. Explicit user choices and
self-hosted stock behavior stay untouched.

**Breaking changes**
None. With the variable unset, every read path is byte-identical to
before.

## What Changed

- New `packages/shared/src/setting-defaults.ts`:
`SETTING_DEFAULTS_ENV_KEY`, `DEFAULTABLE_GENERAL_SETTINGS` registry
(currently `feedbackDataSharingPreference`), `parseSettingDefaults`
(fail-closed for policy content, warn-ignore unknown fields),
`applyOperatorGeneralDefaults` (pure read-time overlay),
`stripOperatorGeneralEchoes` (persist-time echo strip, see below),
re-exported from the package index.
- New `server/src/services/setting-defaults.ts`: parse-once accessor
mirroring `settings-visibility.ts`.
- `server/src/services/instance-settings.ts`: `toGeneralView` applies
the overlay in `get`/`getGeneral`/update responses; persisted writes
never carry operator values. Because general-settings writes materialize
every field, a stored schema-default value is treated as unchosen —
deliberate, documented, and covered by tests.
- `server/src/services/feedback.ts`: the preference-persistence branch
now checks the effective (overlaid) preference, so a stray prompt answer
cannot overwrite an operator default; its local normalize fallback now
returns full schema defaults.
- `server/src/index.ts`: boot-time fail-fast parse with a log line
naming the defaulted settings, mirroring the managed-config posture.
- The hidden-settings write floor (`assertNoHiddenSettingChanges`) keeps
comparing against effective values, so clients echoing a full GET
response keep working. To keep the overlay strictly read-time,
`updateGeneral` strips such echoes at persist time: a write of the
operator value over a field whose stored value is still the schema
default (unchosen) maps back to the schema default, so an echo cannot
promote the operator value into an explicit stored choice and later
changes to (or removal of) `PAPERCLIP_SETTING_DEFAULTS` still take
effect. A write of any other value, or over an explicit stored choice,
persists as given.
- Docs: `PAPERCLIP_SETTING_DEFAULTS` row + "Operator setting defaults"
section in `docs/deploy/environment-variables.md`.
- Tests: `packages/shared/src/setting-defaults.test.ts` (parse matrix,
overlay precedence, echo-strip matrix, immutability) and
`server/src/__tests__/instance-settings-operator-defaults.test.ts`
(accessor, substitution, explicit-choice wins, unset identity,
never-persisted, full-GET echo stays unchosen, explicit non-default
write persists).

## Verification

- `npx vitest run packages/shared/src/setting-defaults.test.ts
server/src/__tests__/instance-settings-operator-defaults.test.ts
server/src/__tests__/instance-settings-managed-overlay.test.ts` — 33
tests passing.
- `npx vitest run server/src/__tests__/instance-settings-routes.test.ts
server/src/__tests__/instance-settings-service.test.ts` — 57 passing;
`npx vitest run server/src/__tests__/feedback-service.test.ts
server/src/__tests__/issue-feedback-routes.test.ts` — 18 passing.
- `pnpm --filter @paperclipai/shared typecheck` and `pnpm --filter
@paperclipai/server typecheck` — clean.

## Risks

- Low. With the variable unset every read path is byte-identical to
before (identity overlay, covered by tests). The overlay is read-time
only and never persisted, so no migration and no data risk. Fail-closed
parsing means a bad policy value is a loud boot failure rather than
silent drift — consistent with the existing managed-config contract.

## Model Used

Claude (Anthropic), model id `claude-fable-5`, extended thinking,
agentic tool use via Claude Code.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - Humans oversee those agents in teams, so each person has a login and
a profile with an avatar
> - Avatars, logos and pasted images all go to one asset upload API,
which files each object under a namespace
> - The avatar namespace embeds the user id, and a deployment can take
user ids from an external identity layer, where a subject often holds
":", "|", "." or "@"
> - But the namespace validator accepted only letters, numbers, "/", "_"
and "-", so those users got a 400 "Invalid image metadata" error and
could not set a profile photo
> - This pull request widens the accepted characters, rejects "." and
".." path segments with a clear message, and cleans the namespace in the
upload client
> - The benefit is that profile photo upload works for every user, and a
namespace the API refuses now returns a message that says what is wrong

## Linked Issues or Issue Description

No existing issue or open pull request covers this. I searched the issue
and pull request lists for "avatar upload", "profile photo", "Invalid
image metadata" and "asset namespace" and found no duplicate. The bug
report follows.

**What happened?**

Profile photo upload fails. `ui/src/pages/ProfileSettings.tsx` sends the
namespace `profiles/${user.id}` to `POST
/api/companies/:companyId/assets/images`. When the user id comes from an
external identity layer it can contain ":", "|", "." or "@" — for
example `oidc:example|jane.example@example.com`.
`createAssetImageMetadataSchema` in
`packages/shared/src/validators/asset.ts` accepted only
`/^[a-zA-Z0-9\/_-]+$/`, so the route returned 400 "Invalid image
metadata" (`server/src/routes/assets.ts`). The image bytes were never
the problem, but the message pointed at the image, so the toast gave the
user nothing to act on.

A second case has the same cause. The agent instructions editor in
`ui/src/pages/AgentDetail.tsx` builds a namespace that ends with a
filename, such as `agents/<id>/instructions/SKILL.md`. The "." in the
filename also failed the check.

**Expected behavior**

A profile photo uploads for any user id the app itself issues, and an
image pasted into the agent instructions editor uploads for any
instruction filename. A namespace the API does refuse returns a message
that names the field and states the rule.

**Steps to reproduce**

1. Run Paperclip with an external identity provider, so `user.id` holds
an OIDC subject such as `oidc:example|jane.example@example.com`.
2. Open Settings, then Profile.
3. Choose an avatar image.
4. The upload fails and the page shows "Invalid image metadata".

Or, with no identity provider:

1. Open an agent, then the instructions editor, and select a file whose
name contains a "." such as `SKILL.md`.
2. Paste an image into the editor.
3. The upload fails with the same error.

**Paperclip version or commit**

`master` at eb86fcd49.

**Deployment mode**

Any deployment whose user ids come from an external identity layer. The
instructions-editor case reproduces on a plain self-hosted install too.

**Agent adapter(s) involved**

Not adapter-specific (core bug).

## What Changed

- `packages/shared/src/validators/asset.ts`: widen the namespace pattern
to `/^[a-zA-Z0-9\/_.:@|-]+$/`, and reject any "/"-separated segment
equal to "." or "..". A traversal attempt now gets a clean 400 from the
validator instead of an error from the storage provider.
- `packages/shared/src/validators/asset.ts`: add
`sanitizeAssetNamespace()`, which maps any string to a namespace the
schema accepts. It works per segment: it keeps the accepted characters,
turns the others into "-", collapses repeated dashes, drops empty and
dot-only segments, and caps the result at 120 characters. It returns
`undefined` when no segment survives, and the caller then sends no
namespace.
- `packages/shared/src/validators/asset.ts`: export
`ASSET_NAMESPACE_MAX_LENGTH` and `ASSET_NAMESPACE_RULE`, so the rule
text and the API error cannot drift apart.
- `ui/src/api/assets.ts`: run the namespace through
`sanitizeAssetNamespace()` in `uploadImage`. This is one choke point for
all callers, so no caller has to know the rule.
- `server/src/routes/assets.ts`: name the field in the 400 message —
`Invalid image metadata: "namespace" must be 1-120 characters of
letters, numbers, or / _ - . : @ |, and cannot contain "." or ".." path
segments`. The zod issue details stay in the response. The UI shows
`body.error`, so the toast is now actionable.
- Tests: a new `packages/shared/src/validators/asset.test.ts`
accept/reject matrix for the schema and the sanitizer; three cases in
`server/src/__tests__/assets.test.ts`; one case in
`ui/src/pages/ProfileSettings.test.tsx`.

## Verification

Targeted runs:

```
npx vitest run packages/shared/src/validators/asset.test.ts        # 22 passed
npx vitest run server/src/__tests__/assets.test.ts                 # 11 passed
npx vitest run ui/src/pages/ProfileSettings.test.tsx               # 2 passed
```

New cases:

- Schema: accepts identity-provider ids that hold ":", "|", "." and "@";
accepts `agents/<id>/instructions/SKILL.md`; rejects `profiles/bad
name!`, over-length input, and `.` or `..` segments.
- Sanitizer: passes identity-provider ids through unchanged, replaces
and collapses the other characters, drops the `.` and `..` segments
while keeping a segment of three or more dots, caps at 120 characters
without leaving a dot segment behind at the cut, and returns `undefined`
when nothing survives. One case asserts the sanitizer output always
parses.
- Route: 201 for `profiles/oidc:example|jane.example@example.com`, and
the storage service receives that namespace; 400 naming `namespace` for
`profiles/bad name!`; 400 for `profiles/../secrets`.
- UI: a session user id holding ":" and "|" uploads, and the namespace
reaches the API unchanged.

Typecheck:

```
pnpm --filter @paperclipai/shared typecheck   # clean
pnpm --filter @paperclipai/ui typecheck       # clean
cd server && npx tsc --noEmit -p tsconfig.json # clean
```

Package suites:

```
npx vitest run --project @paperclipai/shared --exclude "**/dist/**"   # 586 passed, 8 pre-existing failures in src/worktree-seed-source.test.ts
npx vitest run --project @paperclipai/ui --exclude "**/dist/**"       # 4402 passed
```

CI runs the server suite as ten shards (five general, five serialized),
which is the authoritative full run for this package. All shards pass on
this branch.

The `worktree-seed-source` failures reproduce on an unmodified checkout
of the same base commit and are unrelated to this change. The UI
failures seen in that run were 5-second test timeouts caused by running
two suites at once on one machine; each file passes when it runs alone.

No document states the namespace character rule — I checked `docs/` and
`doc/`, where the asset upload endpoint appears only in an OpenAPI
registry entry and a smoke-lab note, neither of which describes the
metadata fields. The rule now lives in one exported constant that the
API error reuses.

## Risks

Low risk.

- The wider character set does not widen what a caller can write to
disk. `server/src/storage/service.ts` already replaces every character
outside `[a-zA-Z0-9._-]` in each path segment, and
`server/src/storage/local-disk-provider.ts` already rejects "." and ".."
segments and any key that resolves outside the base directory. This
change moves the "." and ".." refusal earlier, to the validator, so the
caller gets a clear 400.
- The API is more permissive than before, so no request that used to
succeed can start failing.
- Namespaces stored before this change keep working. The namespace is
not a key that is looked up; it is a prefix under which new objects are
filed.
- One behavior change worth noting: the UI now cleans a namespace
instead of sending it as typed, so a caller that passes an unusable
namespace gets a cleaned prefix rather than a failed upload.

## Model Used

- Claude (Anthropic), Claude Opus, 1M context window, extended thinking,
agentic tool use 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-27 11:27:31 -07:00
Dotta 67f9867bc6 fix(interactions): deliver question answers durably (#12307)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents can pause a task and ask the user structured questions.
> - The answer is durable in the issue interaction, but delivery to the
next run is not durable.
> - A process restart can therefore leave an answered interaction
without a continuation attempt.
> - Native runners also need a provider-neutral question contract before
the task page can consume native events safely.
> - This pull request adds a content-free delivery outbox and an
optional native steering seam.
> - Direct adapters keep their existing heartbeat continuation path.
> - The benefit is reliable answer delivery without changing runtime
selection or task-page behavior.

## Linked Issues or Issue Description

Refs #12202. This pull request replaces the question-delivery foundation
from that stale task-thread pull request. The task-thread projection
will follow in a smaller pull request.

**What happened?**

Question answers were stored in the issue interaction. The server then
made one in-memory continuation wake. A server stop between those
operations could leave the answer stored but not delivered. The combined
native task-thread pull request also made this behavior hard to review
separately from UI changes.

**Expected behavior**

The answer and its delivery receipt must commit in one transaction. The
server must retry pending receipts after a restart. Existing direct
adapters must keep the current wake path. A native runtime may use the
optional steering seam, but this pull request does not enable native
steering in production.

**Steps to reproduce**

1. Create an `ask_user_questions` interaction.
2. Answer the interaction.
3. Stop the server before the continuation wake completes.
4. Start the server again.
5. On current master, no durable record tells the server to retry the
answer delivery.

**Paperclip version or commit**

Current `master` at `4d82f5eae`.

## What Changed

- Add the `issue_question_response_deliveries` table and migration.
- Store only routing state, a correlation ID, and a payload digest in
the delivery row. The answer remains in the existing interaction result.
- Commit an answered interaction and its pending delivery row in one
transaction.
- Add bounded claims, retry recovery, cumulative terminal state, and
content-free activity records.
- Keep every built-in direct adapter and external adapter on the
existing heartbeat wake path.
- Add an optional native steering seam. No production caller supplies
that seam in this pull request.
- Retain the provider-neutral `paperclip.question_set.v1` presentation
on recovered interactions.
- Run delivery immediately after an answer and sweep pending rows at
startup and on the existing server interval.
- Add focused database, service, route, startup, adapter-matrix, digest,
and duplicate-delivery tests.

## Compatibility Boundary

- This pull request does not change adapter selection.
- This pull request does not start runnerd.
- This pull request does not create native run records.
- Direct adapters never call the native steering seam.
- The existing interaction result stays authoritative for answer
content.
- The migration is additive and does not rewrite existing rows.
- This pull request has no UI, dependency, workflow, package-manager, or
lockfile changes.
- The diff has 19 files.

## Verification

- `pnpm exec vitest run
server/src/__tests__/question-response-delivery.test.ts
server/src/services/issue-thread-interactions.test.ts
server/src/__tests__/issue-thread-interaction-routes.test.ts
server/src/__tests__/server-startup-feedback-export.test.ts` — 4 files
and 120 tests passed.
- `pnpm -r typecheck` — passed for all applicable workspaces. This
includes Cargo format and check, protocol drift checks, and migration
safety.
- `pnpm build` — passed. This includes the Rust release binary, server
build, and UI production build.
- `git diff --check` — passed.
- Secret patterns were not present in the changed text files.
- The repository token gates currently report violations from unchanged
files on `master`. This pull request does not change those files.

## Risks

The main risk is routing a direct-adapter answer into a native session.
The service checks the persisted runtime mode, and the adapter matrix
proves that all direct adapters use only the existing wake path. The new
table is additive. It has foreign keys, unique correlation constraints,
bounded attempts, and status checks. Activity records omit question and
answer content.

## Model Used

OpenAI Codex, GPT-5 family. The client does not expose the exact
deployment ID or context window. Agentic reasoning, tool use, and code
execution were enabled.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes:` / `Closes:`
/ `Refs:` OR (b) described the issue in-PR following the relevant issue
template
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal
Paperclip ticket ID or instance-derived details
- [x] I have run the affected tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have documented the new contracts and compatibility boundary
- [x] I have considered and documented compatibility and security risks
above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-27 12:12:21 -05:00
Nicky LeachandPaperclip 1de105c475 fix(observability): pin the Sentry browser SDK and gate the optional Sentry server peer on the exact version (#12270)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip uses separate server and browser packages for runtime
services and the board.
> - Sentry integrations need an exact SDK version and safe optional
loading.
> - A version range can select an SDK that the privacy tests did not
audit.
> - Missing peer metadata does not describe the optional server SDK
contract.
> - This pull request pins the browser SDK and gates the optional server
SDK on its exact version.
> - The benefit is a clear SDK contract with fail-open startup behavior.

## Linked Issues or Issue Description

**What happened?**

The browser package used the range ^10.71.0, so a lockfile refresh could
select a newer SDK. The server loaded @sentry/node dynamically but did
not declare its optional peer contract.

**Expected behavior**

The browser package must use the audited 10.71.0 version. The server
must load @sentry/node only when the installed peer matches 10.71.0. The
server must start when the optional peer is absent.

**Steps to reproduce**

1. Install the project dependencies.
2. Inspect the browser Sentry version and the server package metadata.
3. Start the server without installing @sentry/node.
4. Confirm that the server starts and that the dynamic Sentry bootstrap
does not load an unsupported peer version.

**Paperclip version or commit**

9c57c0f119

**Deployment mode**

Built from source with pnpm dev or pnpm build.

**Installation method**

Built from source.

**Agent adapter(s) involved**

Not adapter-specific (core change).

**Database mode**

Not database-related.

## What Changed

- Pin @sentry/browser to exactly 10.71.0 as a UI development dependency.
- Declare @sentry/node as an optional server peer dependency at 10.71.0.
- Gate the dynamic server bootstrap on the exact peer version.
- Add tests for the browser pin, peer metadata, version gate, and
fail-open loading.
- Document the supported server SDK version.
- Keep the lockfile unchanged because the pull request workflow
regenerates it for manifest changes.

## Verification

- Server tests pass with six expected skips when @sentry/node is absent.
- UI tests pass.
- The UI build emits the lazy Sentry browser chunk.
- git diff --check passes.
- GitHub pull request checks must pass after this pull request opens.
- Greptile must return a 5/5 score with no open findings.

## Risks

The exact version gate prevents Sentry startup when an unsupported SDK
version exists. The integration remains optional and fail-open. The
lockfile workflow must regenerate the lockfile before frozen downstream
jobs run. The label-gated Storybook visual job must not run until it can
restore the generated lockfile artifact.

## Model Used

OpenAI Codex, GPT-5, tool use and code review support, exact context
window details are managed by the execution platform.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-27 07:20:03 -07:00
Nicky LeachandPaperclip d785b19213 test(server): fix the pre-bind race in the byte-ledger ceiling test (#12280)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The server manages duplex channels that carry data between workers
and hosts.
> - The aggregate byte-ledger ceiling test can race the channel bind.
> - The race can make channel open fail before the test checks the
ceiling rejection.
> - This pull request writes one byte after the open call binds the
channel.
> - The test now checks the post-bind rejection and the retained-byte
count.
> - The benefit is a stable test that checks the intended byte-ledger
behavior.

## Linked Issues or Issue Description

**What happened?**

The duplex aggregate byte-ledger ceiling test scripted data during
channel open. Under load, the host could process the data notification
before the open continuation bound the route. The test then saw
`DUPLEX_CHANNEL_OPEN_FAILED` instead of the intended post-bind
rejection.

**Expected behavior**

The test must open the channel first. It must then write one byte and
confirm that the serialized host-to-worker frame exceeds the four-byte
ceiling. The route must reject the write and retain no bytes.

**Steps to reproduce**

1. Run the focused server test file.
2. Repeat the test several times under load.
3. Observe that the old test can fail during channel open.
4. Run the updated test and confirm the post-bind rejection.

**Paperclip version or commit**

b64fbcd5b2

**Deployment mode**

Built from source with the server test runner.

**Installation method**

Built from source.

**Agent adapter(s) involved**

Not adapter-specific (core test).

**Database mode**

Not database-related.

**Additional context**

The change keeps the test-only scope to one file. A previous dependency
change used a separate pull request. This pull request covers the duplex
byte-ledger test fix only.

## What Changed

- Open the duplex channel without scripted data.
- Write one byte after the open call resolves.
- Update the test name and comments to describe the two reservations.
- Keep the change limited to
`server/src/__tests__/plugin-worker-manager-duplex-byte-ledger.test.ts`.

## Verification

- The focused test file passed five consecutive runs before this pull
request opened.
- The test passed with the four-byte ceiling.
- A control run with a 4096-byte ceiling failed only in this test case.
- GitHub Actions must pass the server test suite and all required gates.
- Greptile must return 5/5 with no open findings.

## Risks

Low risk. The change updates one test file and adds no production code.
The test now depends on the open call completing before the write, which
matches the route bind contract.

## Model Used

OpenAI Codex, GPT-5, tool use and code execution. The execution platform
manages the exact context window details.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-26 22:20:12 -07:00
Nicky LeachandPaperclip 06cd21ed0f fix(observability): declare the optional OpenTelemetry peer dependencies (#12249)
## Thinking Path

> - Paperclip manages AI agents for work.
> - Paperclip includes an observability path that operators can enable
for tracing.
> - The server loads several OpenTelemetry packages only when tracing is
enabled.
> - The documentation calls these packages optional peer dependencies,
but the server manifest does not declare them.
> - This gap hides supported versions and stops Dependabot from
maintaining the packages.
> - This pull request aligns package metadata, runtime checks, and
documentation with the opt-in tracing design.
> - The change gives operators clear installation behavior and keeps the
no-op default.

## Linked Issues or Issue Description

This pull request fixes a package metadata and installation defect.
Related observability work appears in
[#8476](https://github.com/paperclipai/paperclip/pull/8476) and
[#9672](https://github.com/paperclipai/paperclip/pull/9672).

The server documentation described optional OpenTelemetry peer
dependencies, but `server/package.json` did not declare them. Package
managers and Dependabot could not see the supported version ranges. The
UI and Claude local adapter also relied on automatic peer installation
for `yjs` and `@anthropic-ai/sdk`.

The package manifests now declare the optional runtime packages. A
default install does not install optional tracing peers. The server
keeps its no-op behavior when tracing is disabled or a peer is absent.

## What Changed

- Add seven optional OpenTelemetry packages to `server/package.json` and
mark each package as optional.
- Keep `@opentelemetry/api` as a normal dependency for the no-op
interface.
- Disable automatic peer installation in `.npmrc`.
- Declare `yjs` for the UI package and `@anthropic-ai/sdk` for the
Claude local adapter.
- Check declared peer versions before the server loads a dynamic
OpenTelemetry import.
- Keep the endpoint gate, dynamic imports, and fail-open behavior
unchanged.
- Update the observability and README documentation.
- Tell Dependabot that its npm parser does not read `peerDependencies`.

## Verification

- Targeted server tests pass: 34 passed and 2 skipped.
- The skipped tests require the real OpenTelemetry SDK and remain
pre-existing.
- The pull request workflow regenerates the lockfile because manifest
files and `.npmrc` changed.
- The policy job confirms that the pull request does not include
`pnpm-lock.yaml`.
- GitHub checks pass except `security/snyk (cryppadotta)`, which remains
pending after its authorized wait cap.
- Greptile Review reports 5/5 with no open findings.
- Server typecheck passes.

## Risks

- Optional peers can produce a diagnostic when the installed version
does not match the declared range.
- A missing optional peer does not stop the server.
- Disabling automatic peer installation can expose undeclared package
use in other workspaces.
- This pull request declares the affected packages and adds tests for
the changed behavior.
- This pull request makes no database or API changes.

## Model Used

OpenAI Codex, GPT-5, with repository inspection and pull request
preparation.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-26 17:34:53 -07:00
DottaandPaperclip 75b6d22aac fix(recovery): make silent-run detection UI-only (#12242)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The recovery service detects active runs that stop producing output.
> - The dashboard already shows suspicious and critical silence to the
board.
> - The recovery scan also creates delegated evaluation work for the
same signal.
> - Output silence alone does not prove that the run or source task
needs recovery.
> - This pull request keeps the signal and removes automatic recovery
artifacts.
> - The benefit is a visible watchdog signal without assignment changes,
wake requests, or issue noise.

## Linked Issues or Issue Description

- Refs #6596
- Refs #7036
- Refs #9475
- Refs #11544
- Refs #11839
- Refs #11961

## What Changed

- Keep the one-hour suspicious level and four-hour critical level in
active-run API summaries.
- Stop output silence from creating or changing issues, recovery
actions, comments, relations, assignments, and wake requests.
- Store snooze, continue, and false-positive decisions against the run
without an evaluation issue.
- Preserve terminal-source folding, orphan cleanup, and open legacy
evaluation links.
- Show informational watchdog copy and board controls without requiring
an evaluation-task link.
- Document the UI-only watchdog contract.
- Add focused server and UI coverage for artifact-free scans and board
decisions.

## Verification

- `pnpm -r typecheck`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-active-run-output-watchdog.test.ts
ui/src/components/IssueRunLedger.test.tsx` (32 tests passed)
- `pnpm build`
- `pnpm check:token-gates`
- `git diff --check`
- `pnpm test:run` completed locally with 4,772 passing tests. It found
30 unrelated macOS test-harness failures in eight workspace, skill,
listener, and runtime exposure files. The failures use `/tmp` and
`/private/tmp` as different paths, require Linux `/proc` listener data,
or derive invalid HMR ports from the macOS ephemeral range.
- The full Linux CI matrix passed on the latest commit. It includes
build, typecheck, server tests, worker tests, serialization tests,
canary, and e2e tests.
- Greptile reviewed the latest commit at 5/5 with no actionable
findings.

## Risks

- The recovery scan keeps its existing result shape, but its created and
escalated counts remain zero for output silence.
- A false-positive decision now suppresses the signal for the full life
of that run.
- Open legacy evaluation issues remain visible and manually resolvable.
The scan does not refresh or reprioritize them.
- There is no database migration and no API schema change.

> I checked `ROADMAP.md`. This change corrects existing watchdog
behavior and does not duplicate planned core work.

## Model Used

- OpenAI Codex, GPT-5, with extended reasoning, tool use, and code
execution.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-26 15:26:12 -05:00
Nicky LeachandPaperclip 8f1e3cfe24 feat(observability): add opt-in Sentry error monitoring for the server and the browser (#12190)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The server and the browser need clear error reports when an operator
enables external monitoring.
> - Paperclip already uses an opt-in OpenTelemetry pattern for server
traces.
> - Sentry can provide error reports for both runtime paths when the
operator sets one data source name.
> - This pull request adds one opt-in Sentry gate for the server and the
browser.
> - The benefit is faster diagnosis while the default setup sends no
Sentry data.

## Linked Issues or Issue Description

**What is improved?**

Paperclip gains optional error monitoring for server and browser
failures.

**Subsystem affected**

Cross-cutting (server, UI, and shared authentication data).

**Current behavior**

Paperclip has no built-in Sentry error capture for server failures or
browser boundary failures. Operators must inspect local logs and browser
tools.

**Proposed behavior**

When the operator sets `SENTRY_DSN`, the server and authenticated
browser use the same Sentry project. When the variable is absent, both
paths stay inactive. The server loads Sentry dynamically and fails open
when the optional package is absent.

**Reason and benefit**

Operators can inspect runtime errors in one Sentry project. The default
setup remains local and sends no monitoring data.

**Breaking changes**

None when `SENTRY_DSN` remains unset. Authenticated session responses
add the optional `sentryDsn` field.

**Additional context**

The implementation uses built-in Sentry privacy options. It disables
default HTTP context and breadcrumb integrations and keeps
`sendDefaultPii` false.

## What Changed

- Add an opt-in server Sentry gate with dynamic package loading and
fail-open behavior.
- Add the Sentry data source name to the authenticated session response.
- Add an authenticated browser Sentry gate and React error boundary
capture.
- Add tests for server, browser, route, and application error paths.
- Document activation, installation, privacy settings, capture behavior,
and operator controls.

## Verification

- Run `npx vitest run server/src/__tests__/sentry.test.ts`.
- Run `npx vitest run ui/src/lib/sentry.test.ts`.
- Run `npx vitest run server/src/__tests__/auth-routes.test.ts
server/src/__tests__/shutdown.test.ts`.
- Confirm that the full continuous integration suite passes on this pull
request.
- Leave `SENTRY_DSN` unset and confirm that the server and browser gates
stay inactive.
- Set `SENTRY_DSN` and install the optional Sentry packages before a
manual capture check.

## Risks

The operator controls the Sentry project and accepts the data risk when
the operator enables the feature. Error objects can contain messages,
stacks, or cause chains with private values. The default configuration
sends no data because the feature stays off without `SENTRY_DSN`. A
missing optional server package does not stop server boot.

## Model Used

OpenAI Codex, GPT-5, with tool use, repository inspection, GitHub CLI
operations, and code review support.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-26 11:15:45 -07:00
Nicky LeachandPaperclip 198fc8b281 fix(adapter-utils): harden the referenced-project ignore scan (#12214)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agent adapters stage referenced projects into controlled sandboxes
> - The ignore scan must preserve exact Git path bytes and fail closed
on unsafe input
> - Unbounded ignored-path data and raw diagnostics can harm resource
use or expose host details
> - This pull request adds exact path parsing, input bounds, fixed
failure categories, and saturation-only retry
> - The benefit is safer and more predictable referenced-project staging

## Linked Issues or Issue Description

**What happened?**

The referenced-project ignore scan trimmed NUL-delimited Git paths. It
also accepted a large ignored-path set and exposed raw failure details
through staging errors and warnings.

**Expected behavior**

The scan must preserve leading and trailing whitespace in Git paths. It
must reject oversized ignored-path data and expose only fixed failure
categories.

**Steps to reproduce**

1. Run the referenced-project ignore scan with paths that start or end
with whitespace.
2. Provide more than 10,000 ignored entries or more than 2 MiB of path
bytes.
3. Trigger a scan failure and inspect the reported reason.

**Paperclip version or commit**

d560bc2ae2

**Deployment mode**

Built from source.

**Installation method**

Built from source.

**Agent adapter(s) involved**

Not adapter-specific.

**Database mode**

Not database-related.

**Additional context**

This change covers the overlay diff, untracked, deleted, and ignored Git
paths. It also retries only typed scheduler saturation failures.

## What Changed

- Preserve all bytes in NUL-delimited Git path records.
- Bound ignored-entry count and total UTF-8 path bytes during parsing.
- Redact scan failure details to three fixed reason categories.
- Retry only the typed scheduler saturation error, with three total
attempts and 1 second then 2 second waits.
- Add tests for path whitespace, limits, diagnostics, retry behavior,
and scheduler code parity.

## Verification

- `npx tsc --noEmit` in `packages/adapter-utils` passed.
- `npx vitest run packages/adapter-utils` passed with 977 tests and 4
skipped.
- Continuous integration must run the server suite and the full
repository gates.

## Risks

The scan now rejects ignored-path data above fixed limits. Saturation
retries add up to 3 seconds before a final failure. The resolver still
fails closed for all other errors.

## Model Used

OpenAI GPT-5. The model used tool calls, code inspection, and command
execution. The exact context window and reasoning mode are not exposed
by the runtime.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-26 08:30:42 -07:00
DottaandPaperclip a9d0927fe8 fix(adapters): restore Paperclip skill for legacy runners (#12225)
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Legacy local adapters run agents that use the Paperclip skill for
the control-plane workflow.
> - PR #7029 removed the required-skill fallback and made runtime skill
selection depend only on stored preferences.
> - No migration or runtime fallback replaced that behavior for existing
agents or non-CEO agents.
> - PR #12138 added core skills to new CEOs, and PR #12147 added Claude
skill discovery. These changes did not mount the operational skill for
all legacy agents.
> - This pull request makes the operational skill a legacy adapter
runtime invariant. It keeps all other skills configurable.
> - The native runner stays unchanged because its protocol supplies the
control-plane contract.
> - The benefit is that new and existing legacy agents can always
operate through Paperclip.

## Linked Issues or Issue Description

Refs #7029

Refs #12138

Refs #12147

**What happened?**

A skill-capable legacy local agent could start without
`paperclipai/paperclip/paperclip`. This happened when the agent had no
stored skill preference. An explicit empty preference also removed the
skill. The agent then reported that the Paperclip skill was not
available.

**Expected behavior**

Every skill-capable legacy local adapter must mount the Paperclip
operational skill when the runtime inventory contains it. Optional
skills must remain configurable. The native runner must keep its current
protocol-based behavior.

**Steps to reproduce**

1. Create a non-CEO `codex_local` agent without `paperclipSkillSync`
preferences.
2. Start a legacy heartbeat.
3. Inspect the managed `CODEX_HOME/skills` directory.
4. Observe that the Paperclip skill is absent before this change.

**Paperclip version or commit**

The problem reproduces on `master` before this pull request. PR #7029
introduced the configured-only selection behavior.

**Deployment mode**

Local development and self-hosted legacy local adapters.

## What Changed

- Added a shared legacy skill resolver that always selects the canonical
Paperclip operational skill when it is available.
- Applied the resolver to direct adapter execution, ACPX execution,
skill snapshots, and persistent skill sync.
- Added Hermes skill materialization at sync and run boundaries.
- Aligned Cursor, Gemini, and OpenCode execution-time injection with the
configured child `HOME`.
- Made Hermes stop execution when another installation blocks the
required operational skill.
- Kept optional skills controlled by `paperclipSkillSync.desiredSkills`.
- Kept `paperclip_runner` on the configurable-only resolver.
- Added regression coverage for missing preferences, empty preferences,
each skill-capable legacy adapter, ACPX, Hermes, and native runner
isolation.
- Documented the legacy runtime invariant.

## Verification

- `pnpm -r typecheck` passed on the pushed commit.
- `pnpm build` passed on the pushed commit.
- The adapter utility regression suites passed: 236 tests.
- The changed server adapter suites passed: 48 tests across 12 files.
- The OpenCode adapter suite passed: 8 tests.
- The Hermes adapter suite passed: 7 tests.
- `git diff --check` passed.
- `pnpm test:run` is not clean on this macOS host. The command reported
failures in unchanged workspace and filesystem suites. An isolated rerun
of `company-skills.test.ts` and `company-skills-service.test.ts`
reproduced 11 failures because macOS resolved `/var/...` paths as
`/private/var/...`. The changed adapter suites pass independently.

## Risks

- This change deliberately makes the operational skill non-removable for
skill-capable legacy local adapters.
- Existing agents receive the skill on their next list, sync, or run
boundary. No database migration is required.
- The resolver does not create a skill when the runtime inventory does
not contain the canonical entry.
- Hermes aborts a run if another installation occupies the required
operational skill target.
- Hermes removes only an undesired Paperclip-owned symlink that still
points to the known Paperclip source.
- The native runner does not receive the legacy default.

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

## Model Used

OpenAI Codex based on GPT-5. The exact serving model ID and context
window were not exposed. The agent used reasoning, tool use, and code
execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-26 09:45:24 -05:00
DottaandPaperclip 6524d2b67f fix(dev): honor --data-dir isolation (#12193)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The development runner starts the local API, UI, and embedded
PostgreSQL services.
> - Developers need separate data roots when they run more than one
local checkout.
> - The runner forwarded `--data-dir` to a child process that did not
use it.
> - The migration check and server therefore continued to use the
default Paperclip home.
> - This pull request applies the data root before worktree setup and
migration checks.
> - The benefit is an isolated database and state root for each
requested development run.

## Linked Issues or Issue Description

Refs #7466

## What Changed

- Parse and consume `--data-dir`, `--data-dir=<path>`, and `-d` in the
development runner.
- Set isolated default home, config, and context paths before worktree
setup and migration checks.
- Keep explicit config and context paths unchanged.
- Include the normalized data root in the local service identity.
- Let `dev:list` and `dev:stop` select the matching isolated service
registry.
- Keep explicit option environments independent of ambient process
instance values.
- Add regression tests and development documentation.

## Verification

- `PAPERCLIP_INSTANCE_ID=ambient-test-instance pnpm exec vitest run
server/src/__tests__/dev-runner-options.test.ts` passes with 8 tests.
- `pnpm --filter @paperclipai/server typecheck` passes.
- `pnpm --filter @paperclipai/adapter-utils build` passes.
- `pnpm -r typecheck` passes.
- `pnpm build` passes.
- `pnpm dev:list --data-dir ./tmp/dev-service-review-fixture` selects
the isolated registry.
- A live run of `pnpm dev --data-dir ./tmp/data-dir-pr-smoke` became
healthy on port 3101 while another checkout used port 3100.
- The live run used `./tmp/data-dir-pr-smoke/instances/default/db` on a
separate PostgreSQL port.
- The latest-head Linux CI matrix passes, including build, typecheck,
canary, all general and serialized test shards, and all e2e shards.
- `pnpm test:run` was attempted on macOS. Current `master` has unrelated
workspace path failures because `/tmp` resolves to `/private/tmp`. The
focused regression suite passes, and the full Linux matrix is green.

## Risks

- Risk is low. The change only affects development runs that pass
`--data-dir` and matching service-management commands.
- Explicit `PAPERCLIP_CONFIG` and `PAPERCLIP_CONTEXT` values still take
priority.

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

## Model Used

- OpenAI Codex with `gpt-5.6-sol` produced this change. The run used
tool-enabled reasoning and code execution. The runtime did 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-08-26 09:08:31 -05:00
Nicky LeachandPaperclip dc30dc4f34 fix(setup-token): pin the start guard to the served adapter (#12179)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandbox agents use adapter login routes to start authenticated
sessions
> - The setup-token start route accepted adapter types that later routes
and cleanup did not serve
> - This mismatch could create sessions that no route or reaper could
reach
> - The OpenAPI body schema and synchronous login capability defaults
also differed from the enforced behavior
> - This pull request pins the start guard to the served adapter, shares
the adapter constant, aligns the schema, and exposes login capabilities
early
> - The benefit is consistent session access, cleanup, API
documentation, and login UI behavior

## Linked Issues or Issue Description

Refs: #11730

Refs: #11286

**Subsystem affected**

Cross-cutting server and UI login behavior.

**Problem or motivation**

The setup-token start route accepted a non-served adapter type.
Follow-up routes and the reaper only handled the served adapter. This
could create an unreachable session that held its slot. The OpenAPI
schema and early capability defaults also did not match the route
behavior.

**Proposed solution**

Pin the start guard, follow-up key, and reaper filter to one exported
served-adapter constant. Derive the OpenAPI body from the strict shared
schema. Add the login capability projection to synchronous defaults.

**Alternatives considered**

Keep separate adapter constants and add another guard at each follow-up
route. This would preserve duplicate sources of truth and leave future
drift possible.

**Roadmap alignment**

This change supports the Cloud / Sandbox agents milestone in
`ROADMAP.md`.

## What Changed

- Reject a setup-token start request when its adapter type is not the
served adapter.
- Reuse one exported adapter constant for the start guard, follow-up
key, and reaper filter.
- Derive the company adapter login-sessions start body from the strict
shared schema.
- Add the `login` capability projection to the synchronous Claude and
Codex adapter defaults.
- Add regression coverage for the rejected non-served adapter request.

## Verification

- The setup-token route suite passes, including the non-served adapter
regression test.
- The setup-token session-service suite passes.
- The setup-token reaper suite passes.
- The OpenAPI suite passes.
- The server TypeScript check passes.
- The UI TypeScript check passes.
- GitHub Actions must confirm all required checks after pull request
creation.

## Risks

The start route now rejects adapter types that follow-up routes cannot
serve. No database migration exists. Revert the one commit to roll back
the change.

## Model Used

OpenAI Codex, GPT-5, exact runtime model ID not exposed, large context
window, reasoning, tool use, and code execution. The implementing
engineer used AI assistance.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-25 19:48:44 -07:00
Devin Foley ca02d2463a fix(ui): keep the installed service worker fresh on parked tabs (#12198)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The web UI registers a service worker (`/sw.js`) that caches the app
shell for an offline fallback.
> - Browsers only re-fetch a worker script on navigation or a ~24h
timer, and Paperclip is a parked-tab SPA: a tab can sit open for weeks
without one navigation.
> - An installed worker — and the shell it cached — can therefore keep
serving an old bundle long after a deploy, and the server let `sw.js`
inherit the generic 1h static TTL on top of that.
> - This pull request adds explicit update checks (tab-visible +
hourly), applies a discovered update with one reload while the tab is
hidden, and serves `sw.js` with `Cache-Control: no-cache`.
> - The benefit is that a deploy reaches every open tab within about an
hour, instead of some tabs silently running stale UI indefinitely.

## Linked Issues or Issue Description

Refs #11292 (the network-first `sw.js` fallback fix; this PR closes the
delivery gap that can keep clients pinned on a pre-#11292 worker).

**What happened?**

A browser that had an older cache-first worker installed kept rendering
a stale app shell — old feature set, old naming — while the server was
verified to be running the current release. Nothing on the client checks
for a new worker outside navigations, so a parked tab never picked up
the fixed worker, and `sw.js` was served with a 1h cache TTL that
further delayed update checks.

**Expected behavior**

Every open tab converges to the deployed bundle shortly after a release,
without users unregistering workers in DevTools or hard-reloading.

**Steps to reproduce**

Install a build's service worker, deploy a newer build, and leave the
tab parked (no navigation): the tab keeps running the old bundle
indefinitely; the worker update check only happens if the user
navigates, and even then a cached `sw.js` can answer it.

## What Changed

- New `ui/src/lib/service-worker-updates.ts`: registers `/sw.js`, runs
`registration.update()` when the tab becomes visible and on an hourly
timer, and on `controllerchange` of a previously-controlled page applies
the update with a single reload — only while the tab is hidden, so an
update never yanks the page mid-session; a takeover while visible defers
the reload to the next hidden transition. First-ever installs never
reload.
- `ui/src/main.tsx`: replaces the fire-and-forget `register()` with the
new module.
- New `server/src/static-ui-cache.ts` (`staticUiCacheControl`):
`index.html` and `sw.js` are served `Cache-Control: no-cache`; other
non-hashed statics keep the 1h default. `server/src/app.ts` uses it in
the static middleware.

## Verification

- `npx vitest run ui/src/lib/service-worker-updates.test.ts` — 8 tests:
registration, hidden-takeover reload (once), deferred reload on visible
takeover, no reload on first install, visibility-triggered and
timer-triggered update checks, cleanup, no-container no-op.
- `npx vitest run server/src/__tests__/static-ui-cache.test.ts` — 3
tests incl. the `sw.js.map` lookalike keeping the default TTL.
- `tsc -b` (ui) and `tsc --noEmit` (server) clean; `pnpm check:tokens`
clean.

## Risks

- Behavioral shift: tabs now reload once, while hidden, after a deploy
lands. Unsaved in-page state in a hidden tab is lost at that moment —
the same exposure as a browser discarding a background tab, which SPAs
must already tolerate.
- Self-hosted behavior is otherwise unchanged: same worker script, same
registration URL, one added conditional header.
- Low risk on the server side: the header change only widens
revalidation.

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — agentic
coding session with tool use and extended thinking.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - The app can run self-hosted or as a cloud-managed instance, where a
hosting platform provisions the instance with its company already
materialized (the existing `isCloudManagedInstance()` predicate and
`cloud_managed` floors)
> - The company Import/Export surface lets an operator materialize whole
companies from an export bundle; on a cloud-managed instance this
bypasses the existing `cloud_managed` company-creation floor and
conflicts with platform-owned provisioning
> - Importing should be disabled on cloud-managed instances, while
export stays open as the data-portability escape hatch
> - This pull request floors every import route with 403
`code=cloud_managed` on cloud-managed instances and hides the Import UI
there, using the existing predicate and the established floor pattern
> - It also extends the operator-hidden settings registry with keys for
every top-level company settings page, so a hosting operator can hide
any of them with `PAPERCLIP_HIDDEN_SETTINGS` alone next time
> - The benefit is one consistent managed-instance policy: cloud-managed
instances cannot import companies, self-hosted installs keep the full
import surface unchanged

## Linked Issues or Issue Description

No public issue exists; the underlying problem follows the enhancement
template.

**What existing behavior does this improve?**

The company import surface (`/api/companies/import*`,
`/api/companies/:companyId/imports/*`) and its UI entry points on
cloud-managed instances.

**Subsystem affected**

Server routes (`server/src/routes/companies.ts`) and UI navigation/pages
(settings sidebar, settings tabs, org chart, `/company/import` route).

**Current behavior**

A cloud-managed instance floors direct company creation (`POST
/api/companies` answers 403 `cloud_managed`), but the import routes
still accept company bundles, so an import can materialize companies the
hosting platform did not provision. The UI offers Import entry points
that lead to a surface that is not available on cloud-managed instances.

**Proposed behavior**

On instances where `isCloudManagedInstance()` is true, every import
route answers 403 `code=cloud_managed` before auth and body work, and
the Import UI (sidebar entry, settings tab, org-chart button,
`/company/import` route) is hidden or redirected. Export remains fully
available. Self-hosted instances are unchanged.

**Reason and benefit**

Cloud-managed instances keep one consistent provisioning authority, and
users do not see an Import surface that dead-ends in a 403.

## What Changed

- `server/src/routes/companies.ts`: a router-level floor mounted at the
`/import` and `/:companyId/imports` prefixes. It covers the single-shot
upload, preview, job polling, chunked transfer
declare/part-upload/status/preview/apply, and the agent-safe per-company
import routes. It throws `forbidden(..., { code: "cloud_managed" })` on
cloud-managed instances, or `403 settings_operator_managed` when the
operator hides `company.import` — both before auth and body validation,
mirroring the company-creation floor.
- `packages/shared/src/settings-visibility.ts`: new
`HIDEABLE_COMPANY_PAGES` registry group — `company.members`,
`company.invites`, `company.secrets`, `company.export`, `company.import`
— with a `hidesCompanyPage` helper. The company General page stays
non-hideable (settings root). `company.import` floors its API; the other
keys are UI-visibility only, as documented in the registry, so
membership/invite/secret/export APIs stay live for agents.
- `ui/src/components/CloudManagedPageGate.tsx` (new): route gate that
redirects cloud-managed instances to `/company/settings`, modeled on
`HiddenSettingsPageGate`.
- `ui/src/App.tsx`: wraps the `company/import` route in
`CloudManagedPageGate`.
- `ui/src/components/CompanySettingsSidebar.tsx`,
`ui/src/components/access/CompanySettingsNav.tsx`,
`ui/src/pages/OrgChart.tsx`: hide the Import entry points when
`useCloudInstance()` reports a managed instance, and honor the new
`company.*` hidden-settings keys for every company page entry (sidebar
item, tab, org-chart buttons).
- `ui/src/App.tsx`: `HiddenSettingsPageGate` route gates for the members
(incl. the legacy access route), invites, secrets, export, and import
pages under their `company.*` keys.
- `docs/deploy/environment-variables.md`: documents the new keys and
their semantics; the CLI and board-operator guides note that import is
unavailable on cloud-managed instances.
- Tests: new `server/src/__tests__/company-import-cloud-floor.test.ts`
and `ui/src/components/CloudManagedPageGate.test.tsx`, registry cases in
`packages/shared/src/settings-visibility.test.ts`, plus cloud and
hidden-key cases in the sidebar, settings-nav, and org-chart suites.

## Verification

- TypeScript typechecks pass for every workspace package (`tsc` in
shared, server, ui; the runner's Rust leg needs a local cargo toolchain
and is covered by CI).
- `pnpm test` on this branch fails only in 9 files that also fail on a
clean `origin/master` checkout on the same machine
(environment-dependent suites: live-listener probes,
workspace/native-runtime spawns, skill materialization). Zero
branch-only failures against that baseline; every suite touched by this
change passes.
- `server/src/__tests__/company-import-cloud-floor.test.ts` asserts:
every import route answers 403 `cloud_managed` under the server-token
signal; the managed-config signal alone also floors; every import route
answers 403 `settings_operator_managed` when `company.import` is hidden;
hiding other company pages leaves import open; the floor applies before
auth and body validation; export stays open on cloud-managed instances;
self-hosted import preview and job polling still work.
- `packages/shared/src/settings-visibility.test.ts` covers the new
`company.*` keys and `hidesCompanyPage`.
- UI suites assert the Import tab, sidebar entry, and org-chart button
disappear on a cloud-managed instance while Export stays, that
`/company/import` redirects through the gate, and that the `company.*`
keys hide their sidebar entries and tabs.

## Risks

- Low risk for self-hosted installs: the floor is inert unless a cloud
signal (`PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN` or
`PAPERCLIP_MANAGED_CONFIG`) is present, and the self-hosted paths are
regression-tested.
- On cloud-managed instances this is a deliberate behavioral removal:
import (including agent-driven safe imports and resumable transfers)
stops working the moment an instance runs this build. In-flight chunked
transfers on such instances cannot be applied afterward; they answer
403.
- CLI import commands against a cloud-managed instance now fail with the
`cloud_managed` error; the message names the reason.
- The new `company.*` keys change nothing unless an operator sets them:
`PAPERCLIP_HIDDEN_SETTINGS` unset keeps behavior identical, and older
images ignore unknown keys by design. The four non-import company keys
hide UI only; their APIs stay live, which the registry documents
explicitly.

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — agentic
coding session with tool use (code search, editing, 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
2026-08-25 16:06:35 -07:00
0cedb45df3 build(deps-dev): bump typescript from 5.9.3 to 7.0.2 (#11880)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3
to 7.0.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/microsoft/TypeScript/releases">typescript's
releases</a>.</em></p>
<blockquote>
<h2>TypeScript 7.0.2</h2>
<p><a
href="https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/">https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/</a></p>
<p>This tag was originally released at: <a
href="https://github.com/microsoft/typescript-go/releases/tag/typescript%2Fv7.0.2">https://github.com/microsoft/typescript-go/releases/tag/typescript%2Fv7.0.2</a></p>
<h2>TypeScript 6.0.3</h2>
<p>For release notes, check out the <a
href="https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/">release
announcement blog post</a>.</p>
<ul>
<li><a
href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&amp;q=milestone%3A%22TypeScript+6.0.0%22">fixed
issues query for TypeScript 6.0.0 (Beta)</a>.</li>
<li><a
href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&amp;q=milestone%3A%22TypeScript+6.0.1%22">fixed
issues query for TypeScript 6.0.1 (RC)</a>.</li>
<li><a
href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&amp;q=milestone%3A%22TypeScript+6.0.2%22">fixed
issues query for TypeScript 6.0.2 (Stable)</a>.</li>
<li><a
href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&amp;q=milestone%3A%22TypeScript+6.0.3%22">fixed
issues query for TypeScript 6.0.3 (Stable)</a>.</li>
</ul>
<p>Downloads are available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/typescript">npm</a></li>
</ul>
<h2>TypeScript 6.0</h2>
<p>For release notes, check out the <a
href="https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/">release
announcement blog post</a>.</p>
<ul>
<li><a
href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&amp;q=milestone%3A%22TypeScript+6.0.0%22">fixed
issues query for TypeScript 6.0.0 (Beta)</a>.</li>
<li><a
href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&amp;q=milestone%3A%22TypeScript+6.0.1%22">fixed
issues query for TypeScript 6.0.1 (RC)</a>.</li>
<li><a
href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&amp;q=milestone%3A%22TypeScript+6.0.2%22">fixed
issues query for TypeScript 6.0.2 (Stable)</a>.</li>
</ul>
<p>Downloads are available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/typescript">npm</a></li>
</ul>
<h2>TypeScript 6.0.1 RC</h2>
<p>For release notes, check out the <a
href="https://devblogs.microsoft.com/typescript/announcing-typescript-6-0-rc/">release
announcement blog post</a>.</p>
<ul>
<li><a
href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&amp;q=milestone%3A%22TypeScript+6.0.0%22">fixed
issues query for TypeScript 6.0.0 (Beta)</a>.</li>
<li><a
href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&amp;q=milestone%3A%22TypeScript+6.0.1%22">fixed
issues query for TypeScript 6.0.1 (RC)</a>.</li>
</ul>
<p>Downloads are available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/typescript">npm</a></li>
</ul>
<h2>TypeScript 6.0 Beta</h2>
<p>For release notes, check out the <a
href="https://devblogs.microsoft.com/typescript/announcing-typescript-6-0-beta/">release
announcement</a>.</p>
<ul>
<li><a
href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&amp;q=milestone%3A%22TypeScript+6.0.0%22+is%3Aclosed+">fixed
issues query for Typescript 6.0.0 (Beta)</a>.</li>
</ul>
<p>Downloads are available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/typescript">npm</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/microsoft/TypeScript/commit/1e4744d68260a7cb91b62b12edc3f6a2187faaf1"><code>1e4744d</code></a>
Merge branch 'main' into ts7-release</li>
<li><a
href="https://github.com/microsoft/TypeScript/commit/a5a219c3b5da0db4fa0ecf6c0b1f588c9af9c669"><code>a5a219c</code></a><code>microsoft/typescript-go#4558</code></li>
<li><a
href="https://github.com/microsoft/TypeScript/commit/ecfe30dce91368d52c9a49b6095bb0b673a238f8"><code>ecfe30d</code></a>
Update status localization</li>
<li><a
href="https://github.com/microsoft/TypeScript/commit/5de25b5f8fec2ca35eadaed041f1f06d2e214895"><code>5de25b5</code></a>
Hide executable name in TypeScript status</li>
<li><a
href="https://github.com/microsoft/TypeScript/commit/d7ce74a75da2b80e8201506a1599c06549432b93"><code>d7ce74a</code></a>
Show bundled TypeScript version for packaged servers</li>
<li><a
href="https://github.com/microsoft/TypeScript/commit/29be66a607707f90d7a53103a4469bb3015a4d54"><code>29be66a</code></a>
Correct TS 7 release version to 7.0.2</li>
<li><a
href="https://github.com/microsoft/TypeScript/commit/ed2bd1bfa4aac5211ce4bc58fcd1313c7eddc8ff"><code>ed2bd1b</code></a>
Merge branch 'main' into ts7-release</li>
<li><a
href="https://github.com/microsoft/TypeScript/commit/887307575c58ea640dbeba3b4e8fdb6347cd3044"><code>8873075</code></a>
Bump the github-actions group across 1 directory with 3 updates
(microsoft/ty...</li>
<li><a
href="https://github.com/microsoft/TypeScript/commit/9427131ae2d4e230a90ee8a09daac4e75da3e311"><code>9427131</code></a>
Set up stable / nightly extension split, other prep
(microsoft/typescript-go#...</li>
<li><a
href="https://github.com/microsoft/TypeScript/commit/d4eaca5460a1f5f02a829e62706794b0a6fb903e"><code>d4eaca5</code></a><code>microsoft/typescript-go#4549</code></li>
<li>Additional commits viewable in <a
href="https://github.com/microsoft/TypeScript/compare/v5.9.3...v7.0.2">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~microsoft1es">microsoft1es</a>, a new
releaser for typescript since your current version.</p>
</details>
<br />

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Priya Raman <priya@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-25 14:49:05 -07:00
Nicky LeachandPaperclip 6880213de5 fix(adapter-utils): honor .gitignore for referenced-project staging (#12184)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Sandbox adapters stage project files before an agent starts.
> - Referenced projects ignored Git-ignored paths and copied large local
directories.
> - This behavior increased staging time and disk use, and it differed
from anchor workspaces.
> - This pull request resolves Git-ignored paths once and shares that
result across all referenced-project consumers.
> - The benefit is smaller, faster, and consistent project staging.

## Linked Issues or Issue Description

No public GitHub issue exists for this bug.

**What happened?**
Referenced-project staging copied Git-ignored paths, except for a fixed
list of heavy directory names. A large repository therefore used much
more time and disk space than the same repository in an anchor
workspace.

**Expected behavior**
Referenced-project staging should exclude the same Git-ignored paths
that the workspace staging path excludes.

**Steps to reproduce**
1. Create a referenced project with a large Git-ignored directory.
2. Start a sandbox or SSH run that stages the referenced project.
3. Observe that the ignored directory enters the staged content.

**Paperclip version or commit**
Commit `9964b034bbff24e700c8eccf5a8b1fc3daa44bf2`.

**Deployment mode**
Built from source.

## What Changed

- Resolve each referenced project's Git-ignored paths once before
staging.
- Carry the resolved paths as a required field on
`SandboxAdditionalSource`.
- Reuse the resolved paths in sandbox staging, SSH staging, and
content-signature code.
- Harden the read-only Git helper with a bounded process, a reduced
environment, and disabled system and global configuration.
- Fail closed on Git errors, timeouts, and invalid path relations.
- Escape tar glob metacharacters in ignore-derived exclude entries.
- Add and update unit tests for the resolver and its three consumers.

## Verification

- `pnpm vitest run --config packages/adapter-utils/vitest.config.ts`
passes 266 tests locally.
- `pnpm exec tsc --noEmit -p packages/adapter-utils/tsconfig.json`
passes locally.
- CI must pass on this pull request.
- Greptile must report 5/5 with no unresolved comments before merge.

## Risks

- A Git error or timeout now prevents staging for the affected
referenced project.
- The resolver uses a bounded read-only Git process and fails closed by
design.
- The change stays inside `packages/adapter-utils` and does not change
the database schema.

## Model Used

Claude Sonnet 5 (Anthropic) assisted the implementation with code
execution and tool use. The exact context window and reasoning mode are
not recorded.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-25 14:22:47 -07:00
Dotta 397de98193 feat(runner): add flagged Codex execution adapter (#12188)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Paperclip Runner now has protocol, provider, tool, package,
persistence, and hidden server boundaries.
> - The server still cannot select that path for a real agent heartbeat.
> - A new runtime must not change any existing direct adapter.
> - An experimental runtime must fail closed when its rollout flag is
off.
> - This pull request adds one guarded Codex vertical slice through
runnerd.
> - The benefit is a production-built runner path that users cannot
start by default.

## Linked Issues or Issue Description

Refs #11962

Refs #12111

Refs #12169

Refs #12176

**Subsystem affected**

Cross-cutting. The change affects the runner package, server
orchestration, shared settings, and adapter configuration UI.

**Problem or motivation**

The hidden PRP coordinator cannot execute a real heartbeat. The
application also needs an explicit rollout boundary before it can expose
the experimental runner. Existing direct adapters must keep their
current execution and finalization behavior.

**Proposed solution**

Add `paperclip_runner` as a Codex-only adapter behind the default-off
`enableNativeRunner` instance flag. Select the native runtime only for
that adapter. Persist the run binding before runnerd starts. Wait for
the durable PRP result and terminal event. Resume the real Codex
provider thread on later heartbeats. Keep persisted native runs readable
and recoverable after the flag changes.

**Alternatives considered**

The server could route `codex_local` through runnerd. That option would
change an existing adapter and weaken rollback safety. The server could
expose all providers now. That option would add unreviewed provider
behavior. The build could depend on a prebuilt runner binary. That
option would make source builds architecture-dependent and difficult to
verify.

**Roadmap alignment**

This work supports the shipped enforced-outcomes, governed-tool, and
self-healing-run milestones. It does not add a new roadmap surface. It
is the guarded execution step after the merged hidden runner boundaries.

**Additional context**

This is the next replacement for the closed large runner pull request.
Task-thread presentation remains a separate follow-up so this change can
preserve the current direct-adapter UI.

## What Changed

- Add `paperclip_runner` as an explicit Codex-only adapter.
- Add the default-off `enableNativeRunner` instance flag.
- Reject fresh create, hire, import, switch, and execution requests
while the flag is off.
- Allow edits to persisted runner agents while the flag is off.
- Recover an already persisted native run even after the flag is
disabled.
- Keep every built-in direct adapter on its existing runtime path.
- Persist an immutable native run binding and revisioned completion
contract before runnerd starts.
- Execute server to PRP to runnerd to Codex to server through the hidden
coordinator.
- Validate the durable result against the terminal event and exact
completion criteria before finalization.
- Preserve the Codex provider thread ID and use `thread/resume` on the
next heartbeat.
- Strip unsupported Codex configuration fields from the experimental
adapter.
- Build a target-native release runner binary from source and vendor it
into the server distribution.
- Install Rust only in the Docker build stage. Do not add a workflow or
lockfile change.
- Stop the runner process group on completion, cancellation, and forced
shutdown.

## Verification

- Run `pnpm --filter @paperclipai/paperclip-runner check:all`. All 69
TypeScript tests and 58 Rust tests pass. Protocol, conformance, replay,
formatting, and generated-file checks pass.
- Run the 12 focused adapter, settings, runtime-selection, coordinator,
direct-isolation, and real Codex integration test files. All 186 tests
pass.
- The real integration test uses PostgreSQL, HTTP, WebSocket, runnerd,
and a fake Codex app server. It proves one `thread/start` followed by
one `thread/resume`.
- Run `pnpm -r typecheck`.
- Run `pnpm build`.
- Run `pnpm check:token-gates`.
- Build the Docker `build` target from a clean context. Confirm that the
server distribution contains an executable `paperclip-runnerd` built
with Debian Rust 1.85.
- Start the server through the source-mode tsx entry point with the
package `dist` directory absent. Confirm the vendor shim resolves source
exports and the server boots.
- Run `pnpm test:run` twice. On this macOS host, 405 files pass and 1
file skips. Eight untouched workspace and loopback tests fail because
macOS resolves `/tmp` and `/var` through `/private` and because
PID-derived test ports exceed 65535. Linux CI must pass the full suite.
- Confirm that the diff contains 52 files. Confirm that it contains no
`.github` or `pnpm-lock.yaml` change.

## Risks

- The feature flag is off by default. A fresh native start fails with a
stable error while the flag is off.
- A persisted native run remains recoverable after the flag changes.
This prevents rollout changes from corrupting recorded work.
- Only local Codex execution is accepted. Other providers and remote
work modes fail closed.
- Existing direct adapters do not start runnerd, create native rows, use
native status arbitration, or enter native finalization.
- The runner receives its one-use bootstrap ticket through the child
environment. The server does not put the ticket in command arguments or
logs.
- The server validates the company, task, agent, run, runner, session,
completion contract, result, and terminal binding before it accepts
completion.
- The build compiles a target-native Rust binary. Cross-platform release
packaging remains a later concern. Source builds and Docker builds
compile for their current target.
- Docker needs enough build memory for the existing server TypeScript
compile. The Docker build stage sets a 4 GB V8 heap limit.

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

## Model Used

- OpenAI Codex with GPT-5. The exact deployment ID and context-window
size are not exposed. The model used agentic 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 applicable tests pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-25 16:03:41 -05:00
Devin Foley 79b464bf9d fix(server): surface skill materialization failures instead of dropping the skill (#12146)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Runtime skill listing materializes each company skill's files before
handing them to the agent's adapter
> - A materialization failure was swallowed with catch-to-null, and the
skill silently vanished from the runtime while the library still showed
it installed
> - Operators saw "installed", agents saw nothing, and nobody saw the
cause; on claude-local a missing desired skill could even crash the
prompt-bundle hasher
> - This pull request turns both failure paths into structured "missing"
entries with the real error and makes every adapter skip unmountable
entries explicitly
> - The benefit is that a broken skill shows up as broken, with its
cause, instead of not existing

## Linked Issues or Issue Description

**What happened?**

A company skill whose runtime files fail to materialize (deleted source,
missing stored SKILL.md copy, failed version snapshot) disappears from
`listRuntimeSkillEntries` with no trace. Agent skill snapshots report a
generic "not available" with no cause. On claude-local, a desired skill
whose source path does not exist reaches the prompt-bundle hasher, whose
`fs.lstat` throws and can fail the whole run.

**Expected behavior**

The skill appears with `sourceStatus: "missing"` and a `missingDetail`
carrying the underlying error, snapshots and the UI show it as broken,
and adapters skip it at mount time with a logged warning instead of
crashing or dangling-symlinking.

**Steps to reproduce**

Install a local-path skill referenced by an agent, delete its source
directory contents so the stored SKILL.md copy cannot be recovered, and
start a run: before this change the skill vanishes from the runtime set
silently; on claude-local a pinned-but-unmaterializable version can fail
bundle preparation.

## What Changed

- `server/src/services/company-skills.ts` `resolveRuntimeSkillSource`:
both `.catch(() => null)` sites (version snapshot, runtime
materialization) now return the structured `{status: "missing", source,
detail}` shape the deliberate missing branch already used, with the
underlying error message in `detail`.
- `packages/adapter-utils/src/server-utils.ts`:
`isPaperclipSkillSourceMissing` is exported with a doc comment.
- `packages/adapters/claude-local/src/server/execute.ts`: missing
desired skills are filtered out of the prompt bundle and each one logs a
`[paperclip] Warning` with its detail to the run output.
- `cursor-local`, `gemini-local`, `kimi-local`, `opencode-local`,
`pi-local` `execute.ts`: mount loops (and the cursor/gemini injection
calls) skip missing entries instead of symlinking a nonexistent path.

## Verification

- `cd server && npx vitest run
src/__tests__/company-skills-service.test.ts` — new test pins the
missing-with-cause entry for a failed materialization. Nine pre-existing
project-workspace tests in this file fail on my machine at clean
`master` too (environment-specific); their count is unchanged by this
PR.
- `cd server && npx vitest run
src/__tests__/heartbeat-runtime-skills.test.ts
src/__tests__/claude-local-skill-sync.test.ts
src/__tests__/cursor-local-skill-sync.test.ts
src/__tests__/cursor-local-skill-injection.test.ts
src/__tests__/gemini-local-skill-sync.test.ts` — 12 tests pass.
- `cd packages/adapters/claude-local && npx vitest run` — 244 passed, 1
skipped.
- `pnpm run typecheck` clean in server, adapter-utils, and all six
touched adapters.

## Risks

- Runtime skill entry lists grow by the previously dropped entries (now
flagged missing). All shipped consumers either intersect with desired
sets, already handle `sourceStatus: "missing"`, or now skip missing
entries at mount time. The snapshot layer already understood the missing
shape via the `materializeMissing: false` path, so downstream contracts
are unchanged.

## Model Used

- Claude Fable 5 (`claude-fable-5`, Anthropic) with extended thinking
and tool use, via Claude Code.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - Importing a company package as a new company takes the company name
from the package manifest
> - Repeat imports of the same package therefore create several
identically named companies, distinguishable only by issue prefix
> - Users cannot tell which import they are looking at, which feeds the
"my import disappeared" loop of importing again
> - This pull request suffixes manifest-derived names with " (2)", "
(3)", … on collision, while honoring explicitly typed names verbatim
> - The benefit is that every imported company has a recognizable name

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Naming of companies created by the company package import.

**Subsystem affected**

Server — company import (`server/src/services/company-portability.ts`).

**Current behavior**

The new-company branch uses `newCompanyName ?? manifest name ??
"Imported Company"` with no de-duplication. Only the issue prefix is
unique. Three imports of the same package yield three companies with the
same name.

**Proposed behavior**

When the name comes from the manifest (no explicit `newCompanyName`),
the import checks existing company names case-insensitively and appends
the first free " (N)" suffix. Explicit names remain honored verbatim.
Name exhaustion (thousands of collisions) falls back to the base name
rather than failing the import, since names carry no uniqueness
invariant.

**Breaking changes**

None. Only the default name of newly imported companies changes, and
only on collision.

## What Changed

- New exported pure helper `dedupeImportedCompanyName(baseName,
existingNames)`.
- The new-company branch resolves the name through it when no explicit
name was provided, reading existing names via `companyService.list()`.

## Verification

- `cd server && npx vitest run
src/__tests__/company-portability.test.ts` — 87 tests pass (new: pure
helper cases and two `importBundle` tests for the suffixed manifest name
and the honored explicit name).
- `cd server && npx vitest run
src/__tests__/company-portability-routes.test.ts
src/__tests__/company-portability-import-batching.test.ts` — 44 passed,
1 skipped (pre-existing skip).
- `cd server && pnpm run typecheck` — clean.

## Risks

- Low risk. The check-then-create has a theoretical race with a
concurrent import, but names have no unique constraint — the worst case
is today's behavior (a duplicate name). Issue-prefix uniqueness is
untouched.

## Model Used

- Claude Fable 5 (`claude-fable-5`, Anthropic) with extended thinking
and tool use, via Claude Code.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - Chunked company-import transfers are deduplicated by content: a
byte-identical zip that already finished an apply is rejected
> - The rejection said only "this exact package was already imported by
a completed transfer" without saying where that import went
> - Users who could not find the earlier import read the rejection as
data loss and kept retrying, or exported again and created duplicate
companies
> - This pull request makes the declaration response carry the company
the completed apply created, and both clients name it in the error
> - The benefit is that the dedupe rejection now points at the existing
import instead of implying it vanished

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The `alreadyCompleted` rejection when re-declaring a chunked
company-import transfer.

**Subsystem affected**

Shared transfer contract
(`packages/shared/src/company-import-transfer.ts`), transfer declaration
route (`server/src/routes/companies.ts`), web import page, CLI import
command.

**Current behavior**

`POST /api/companies/import/transfers` returns `alreadyCompleted: true`
with no pointer to the earlier import. Web and CLI raise "This exact
package was already imported by a completed transfer. Re-export the
package to import it again."

**Proposed behavior**

The response includes an optional `company` field (`{id, name,
issuePrefix} | null`) resolved from the completed run's company link.
Web and CLI raise a shared message: `… It created the company
"Paperclip" (PAPA) — open it from the company switcher. Re-export the
package to import it again.` A company that was deleted since (or a link
that was never written) degrades to `null` and the original message.

**Breaking changes**

None. The new response field is optional; old clients ignore it.

## What Changed

- `CompanyImportTransferCreated` gains optional `company`, plus a shared
`buildAlreadyImportedMessage` used by both clients.
- The declaration route's `alreadyCompleted` branch resolves the landed
company null-safely via `companyService.getById`.
- Web (`ui/src/pages/CompanyImport.tsx`) and CLI
(`cli/src/commands/client/company.ts`) raise the shared message.

## Verification

- `cd packages/shared && npx vitest run
src/company-import-transfer.test.ts` — 3 tests (named company, id
fallback, no-company original message).
- `cd server && npx vitest run
src/__tests__/company-import-transfer-routes.test.ts` — 24 tests; the
re-declaration test now asserts the company payload and the
deleted-company null path.
- `cd cli && npx vitest run
src/__tests__/company-import-transfer.test.ts` — 17 tests; new test pins
the named-company message.
- `cd ui && npx vitest run src/pages/CompanyImport.test.tsx` — 23 tests.
- `pnpm run typecheck` clean in shared, server, ui, cli.

## Risks

- Low risk. The lookup runs only on the `alreadyCompleted` branch and is
null-safe; the transfer run is already scoped to the requesting actor
(user + instance context in the actor key), so the response never names
a company the caller did not import.

## Model Used

- Claude Fable 5 (`claude-fable-5`, Anthropic) with extended thinking
and tool use, via Claude Code.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - Company import parks every imported agent as a safety default, and
issue assignment wakes are dropped for paused agents
> - The pause was recorded as the generic reason "system" and was almost
invisible: the chat-style task thread showed nothing, the legacy notice
had no action, and the new-task dialog gave no hint
> - Users assigned tasks in an imported company, nothing ran, and there
was no explanation — the imported company looked broken
> - This pull request records a dedicated "import" pause reason and
makes the paused state visible and fixable where the user is looking
> - The benefit is that a silent no-op becomes an explained state with a
one-click resume

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Working with a company whose agents arrived paused from a company
import.

**Subsystem affected**

Shared constants (`PAUSE_REASONS`), company import service
(`server/src/services/company-portability.ts`), task thread and new-task
dialog UI.

**Current behavior**

Imported agents get `pauseReason: "system"`, the same value
plugin-managed and built-in agent pauses use. Assigning an issue to a
paused agent silently drops the wake. The chat-style task thread renders
no paused notice; the legacy thread's notice says "It was paused by the
system." with no action and only renders when the composer is shown.

**Proposed behavior**

Import writes `pauseReason: "import"`. The paused-assignee notice
explains the import pause, offers an inline "Resume agent" button
(suppressed for budget pauses, which clear on their own), and renders
for read-only viewers. The chat-style task thread shows the same notice
above the composer. The new-task dialog warns when the selected assignee
is paused.

**Breaking changes**

None. `PAUSE_REASONS` is widened, not changed; the column already stores
free-text values in other paths, and every consumer is an equality check
with a manual fallback, so an older client shows the generic fallback
copy for the new value.

## What Changed

- `packages/shared/src/constants.ts`: `"import"` added to
`PAUSE_REASONS`.
- `server/src/services/company-portability.ts`: the import pause patch
writes `pauseReason: "import"`.
- `ui/src/components/IssueChatThread.tsx`: `IssueAssigneePausedNotice`
gains import copy, a Resume button, test ids, and is exported; it now
renders even when the composer is hidden. New `onResumeAssignee` /
`resumeAssigneePending` props.
- `ui/src/components/TaskChatThread.tsx`: renders the paused-assignee
notice above the composer dock (the chat-style thread previously had no
paused surface at all).
- `ui/src/pages/IssueDetail.tsx`: wires a resume mutation
(`agentsApi.resume`) through both thread variants and invalidates the
company agent list.
- `ui/src/components/NewIssueDialog.tsx`: inline note when the chosen
assignee is paused, with import-specific copy.

## Verification

- `cd server && npx vitest run
src/__tests__/company-portability.test.ts` — 82 tests pass (pause pin
updated to `"import"`).
- `cd ui && npx vitest run src/components/IssueChatThread.test.tsx
src/components/NewIssueDialog.test.tsx
src/components/TaskChatThread.test.tsx` — 120 tests pass (new: notice
copy per reason, resume click, budget suppression, active-agent null
render, dialog note).
- `pnpm run typecheck` in `packages/shared`, `server`, and `ui` — clean.

## Risks

- Low risk. The resume action calls the existing `POST
/agents/:id/resume` route with its existing guards. Existing rows keep
`"system"` and fall back to the current generic copy; only new imports
write `"import"`.

## Model Used

- Claude Fable 5 (`claude-fable-5`, Anthropic) with extended thinking
and tool use, via Claude Code.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - Each agent's runtime only receives skills listed in its own
desired-skill set; the company library alone does nothing for an agent
> - Every CEO creation path (first-run wizard hire, New Agent
first-agent flow, cloud onboarding seed) creates the CEO with an empty
desired-skill set
> - The default CEO instructions tell the agent to use the core
paperclip skills, so a fresh CEO contradicts its own instructions and
reports its toolkit as "not installed"
> - This pull request unions the core skill keys into every
skills-capable CEO hire/create and into the onboarding-seeded CEO's
adapter config
> - The benefit is that a new CEO can actually do what its instructions
describe, and stops telling users that installed skills do not exist

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Creating the first lead agent (role `ceo`) via hire, create, or the
cloud onboarding seed.

**Subsystem affected**

Server — agent hire/create routes (`server/src/routes/agents.ts`),
onboarding seed (`server/src/services/onboarding-seed.ts`), company
skills service constant (`server/src/services/company-skills.ts`).

**Current behavior**

A CEO created by the wizard, the New Agent page, or the onboarding seed
has no `paperclipSkillSync` block. Its runtime mounts zero skills. Its
default instructions (`server/src/onboarding-assets/ceo/AGENTS.md`,
`HEARTBEAT.md`) tell it to use `paperclip-create-agent`,
`para-memory-files`, and the paperclip coordination skill. The agent
then reports these skills as not installed.

**Proposed behavior**

When the new agent's role is `ceo` and its adapter supports skill sync,
the hire and create routes union the five bundled
`paperclipai/paperclip/*` skill keys into the requested desired-skill
set. The onboarding seed writes the same preference into the seeded
CEO's adapter config. Explicit requests win over defaults for the same
key. Non-CEO agents are unchanged. Any default stays removable through
`POST /agents/:id/skills/sync`.

**Breaking changes**

None. The default is additive, applies only to role `ceo` on
skills-capable adapters, and the bundled skills are guaranteed present
in every company library by `ensureSkillInventoryCurrent`.

## What Changed

- New exported constant `PAPERCLIP_CORE_SKILL_KEYS` in
`server/src/services/company-skills.ts` (the five bundled
`paperclipai/paperclip/*` keys).
- `defaultRoleSkillSelections` + `withDefaultRoleSkillSelections`
helpers in `server/src/routes/agents.ts`, applied in both the hire and
create routes before `resolveDesiredSkillAssignment(..., "add")`.
- `server/src/services/onboarding-seed.ts` builds the seeded CEO's
adapter config with `writePaperclipSkillSyncPreference` instead of `{}`
when the seeded adapter supports skills.

## Verification

- `cd server && npx vitest run
src/__tests__/agent-skills-routes.test.ts` — 32 tests pass (three new:
CEO default set, union with a requested skill, non-CEO untouched).
- `cd server && npx vitest run
src/__tests__/onboarding-seed-route.test.ts` — 14 tests pass (seeded CEO
adapter config assertion added).
- `cd server && npx vitest run
src/__tests__/agent-permissions-routes.test.ts` — 54 tests pass.
- `cd server && pnpm run typecheck` — clean.

## Risks

- Existing CEOs are not modified; only newly created ones get the
defaults. An operator who wants a minimal CEO can remove the skills
after creation with the skills sync (mode `remove`), and that removal
sticks. Adapters without skill support are skipped, so the change is
inert there.

## Model Used

- Claude Fable 5 (`claude-fable-5`, Anthropic) with extended thinking
and tool use, via Claude Code.

## Checklist

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

> - Paperclip routes plugin worker messages to agent sessions.
> - The login pseudo-terminal route opens after the host receives the
open reply.
> - `readline` can deliver later frames from the same pipe read before
that reply continuation runs.
> - The host dropped early output and exit frames.
> - The fix queues valid early frames, preserves arrival order, and
replays them after the route opens.
> - The route uses bounded memory and closes fail-closed when a bound
breaks.
> - The final tests also pin child issue ordering so the serialized
suite remains deterministic.

## Linked Issues or Issue Description

Fixes #12122

## What Changed

- Add a bounded queue for login pseudo-terminal output and exit frames
during route opening.
- Validate session ids, chunk types, and per-chunk limits before queue
insertion.
- Bound the queue by 10,000 frames and 8 MiB of characters.
- Charge retained worker session identifiers against the character
bound.
- Preserve arrival order and stop replay after the first valid exit.
- Drop repeated exits without changing the first exit position or code.
- Bound the repeat-exit lookup and clear queued state on all terminal
paths.
- Add regression tests and fixture support for coalesced frames,
ordering, limits, cleanup, and log safety.
- Pin issue numbers in the child-wake test so its expected child order
remains deterministic.

## Verification

- Build the plugin SDK with `pnpm --filter @paperclipai/plugin-sdk
build`.
- Run `npx vitest run
server/src/__tests__/plugin-worker-manager.test.ts` from the repository
root.
- Run `npx vitest run server/src/__tests__/issues-service.test.ts` from
the repository root.
- The focused plugin worker suite passes 66 of 66 tests at the prior
reviewed head.
- The issue service file passes 120 of 120 tests in two isolated runs at
the current head.
- Confirm that GitHub Actions passes all required checks.
- Confirm that Greptile reports 5/5 with no unresolved review threads.
- Storybook visual regression remains skipped because the PR has no
`storybook-visual` label.

## Risks

- The queue adds bounded memory use while the login pseudo-terminal
route opens.
- A queue limit breach closes the route and prevents unbounded
buffering.
- A hostile worker can fail only its own login route when it breaches a
bound.
- The first valid exit closes the route, so later records do not reach
the session.
- The child-wake test now uses distinct issue numbers to match the
service sort contract.

## Model Used

OpenAI Codex, GPT-5, extended reasoning, tool use, and code review
support. The runtime does not expose a separate context-window value.

## 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 that 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 linked the existing public issue with `Fixes: #12122`
- [x] I have not referenced internal Paperclip issues or links
- [x] My branch name describes the change and contains no internal
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 where needed
- [x] I have considered and documented risks above
- [x] All required Paperclip CI gates are green
- [x] Greptile is 5/5 with no unresolved review threads
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-25 13:42:13 -07:00
Dotta 9964b034bb feat(runner): add hidden server PRP coordinator (#12176)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Paperclip Runner needs a narrow server trust boundary before an
adapter can start it.
> - The package has durable runner transport, but the server does not
host or authorize that transport.
> - Native persistence exists, but no writer connects PRP events to
those records.
> - A direct adapter must not enter this path by accident.
> - This pull request adds a hidden, run-bound PRP server coordinator.
> - The benefit is a recoverable server boundary that remains
unavailable to normal execution.

## Linked Issues or Issue Description

Refs #11962

Refs #12129

Refs #12169

**Subsystem affected**

Cross-cutting. The change affects the runner package and server
orchestration.

**Problem or motivation**

The server cannot authenticate runnerd, commit PRP events before ACK,
authorize semantic tools, or enter native finalization from a durable
runner result. The application must have this hidden boundary before a
guarded adapter can use the runner.

**Proposed solution**

Add an authenticated PRP WebSocket authority and register it only for
one exact persisted native Codex run. Bind each connection and event to
the company, issue, agent, run, runner, session, turn, item, and
verified runner identity. Commit each event before its cumulative ACK.
Project only authorized same-task read tools. Rebuild the accepted
result and finalization record from durable result and terminal events.

**Alternatives considered**

The server could expose a broad runner API key or route semantic calls
through existing adapter endpoints. Those options grant too much
authority and weaken replay recovery. The server could also add the
user-facing adapter in this pull request. That option would mix rollout
selection with the transport trust boundary and make legacy
compatibility harder to review.

**Roadmap alignment**

This work supports the shipped enforced-outcomes, governed-tool, and
self-healing-run milestones. It does not add a new roadmap surface.

## What Changed

- Add the durable PRP server authority with one-use bootstrap tickets,
reconnect leases, encrypted frames, bounded state, cumulative ACKs, and
idempotent commands.
- Add `/api/runner/v1/connect/:runId`. Derive its `ws://` or `wss://`
URL from the configured Paperclip API URL.
- Register one authority only after the coordinator verifies the
complete native Codex run binding.
- Commit validated PRP events to `heartbeat_run_events` before ACK.
Reject source gaps and conflicting replays.
- Rebuild accepted results and finalization records from durable result
and terminal events. Enforce finalization owner leases and retry times.
- Project five same-task read operations. Recheck run, agent, task, and
company authority for each call.
- Keep the route hidden. No adapter selects this coordinator, and no
code starts runnerd.
- Vendor the compiled runner TypeScript runtime into the server package
while keeping the workspace package development-only for the server.
- Document the package, database writer, run-log payload, and credential
exclusions.

## Verification

- Run `pnpm --filter @paperclipai/paperclip-runner check:all`. All
TypeScript protocol checks and 69 Vitest tests pass, including
commit-before-ACK crash recovery. All 43 Rust unit tests and 13 Rust
integration tests pass. Conformance and replay parity pass.
- Run the focused server WebSocket, coordinator, package-build, and
startup-wiring suites. All 26 tests pass, including a clean-checkout
reproduction with the runner `dist` directory absent.
- Run `pnpm -r typecheck`.
- Run `pnpm test:run`.
- Run `pnpm build`.
- Confirm that the diff contains 19 files. Confirm that it contains no
workflow or `pnpm-lock.yaml` change.

## Risks

- The server installs the WebSocket route at startup. An unregistered or
malformed run path fails closed and creates no native record.
- Bootstrap tickets are one use. The private state directory uses mode
`0700`, and the state file uses mode `0600`. The file stores derived
authentication verifiers and never stores raw tickets or lease tokens.
- The journal has explicit frame, command, event-window, and file-size
bounds. A bound violation closes the runner connection or rejects the
command.
- A runner event reaches the database before its ACK. A crash between
event commit and ACK causes a byte-equivalent replay, not a second
logical effect.
- The coordinator accepts only an existing queued or running native
Codex row with exact company, task, agent, runner, session, and
completion-contract ownership.
- Existing direct adapters do not call this service. They keep their
current execution, transcript, result, and finalization paths.
- The server has no production dependency on the private runner package.
Its build copies the compiled runtime into `server/dist`; the workspace
link is development-only. This adds no external package and does not
change the lockfile.

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

## Model Used

- OpenAI Codex with GPT-5. The exact deployment ID and context-window
size are not exposed. The model used agentic 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-25 14:17:14 -05:00
dependabot[bot] 69e8585146 build(deps): bump better-auth from 1.6.28 to 1.7.0 (#11886)
Bumps
[better-auth](https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth)
from 1.6.28 to 1.7.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/better-auth/better-auth/releases">better-auth's
releases</a>.</em></p>
<blockquote>
<h2>v1.7.0</h2>
<p><strong>Blog post:</strong> <a
href="https://better-auth.com/blog/1-7">Better Auth 1.7</a></p>
<h2><code>better-auth</code></h2>
<h3>❗ Breaking Changes</h3>
<ul>
<li>Moved database joins out of <code>experimental</code> into the
stable <code>advanced.database.joins</code> option (<a
href="https://redirect.github.com/better-auth/better-auth/pull/10359">#10359</a>)
<blockquote>
<p><strong>Migration:</strong> Replace <code>experimental: { joins: true
}</code> with <code>advanced: { database: { joins: true } }</code>.
Drizzle and Prisma users should regenerate their schema (<code>npx
auth@latest generate</code>) so it includes the required relations.</p>
</blockquote>
</li>
<li>Scoped account identity by trusted issuer, keying accounts on
<code>(issuer, accountId)</code> (<a
href="https://redirect.github.com/better-auth/better-auth/pull/10403">#10403</a>)
<blockquote>
<p><strong>Migration:</strong> Accounts now require
<code>Account.issuer</code>. Read provider identity from
<code>accountInfo.account.accountId</code>, drop <code>mapping.id</code>
from SSO configs, and give the <code>microsoftEntraId</code> helper a
concrete tenant GUID. Apply the account-identity backfill in the 1.7
upgrade guide before deploying.</p>
</blockquote>
</li>
<li>Required captcha endpoint entries to match full auth paths, with
wildcard support (<a
href="https://redirect.github.com/better-auth/better-auth/pull/10004">#10004</a>)
<blockquote>
<p><strong>Migration:</strong> Replace partial paths such as
<code>/sign-in</code> with explicit wildcards like
<code>/sign-in/*</code> or <code>/sign-in/**</code>.</p>
</blockquote>
</li>
<li>Moved the MCP plugin into its own <code>@better-auth/mcp</code>
package built on the OAuth provider (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9992">#9992</a>)
<blockquote>
<p><strong>Migration:</strong> Install <code>@better-auth/mcp</code> and
<code>@better-auth/cimd</code>, add the now-required <code>jwt()</code>
plugin, and move options nested under <code>oidcConfig</code> to flat
<code>mcp({ ... })</code> options. Rename <code>withMcpAuth</code> to
<code>requireMcpAuth</code> and <code>mcpHandler</code> to
<code>createMcpProtectedRequestHandler</code>. Regenerate the schema
(<code>npx auth migrate</code>): <code>oauthApplication</code> becomes
<code>oauthClient</code>, plus new <code>oauthRefreshToken</code> and
<code>oauthClientAssertion</code> tables.</p>
</blockquote>
</li>
<li>Added OIDC back-channel logout so ending a session cuts off every
connected app's API access (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9304">#9304</a>)
<blockquote>
<p><strong>Migration:</strong> Introspecting an access token whose
session has ended now returns <code>{ active: false }</code>, and
<code>/oauth2/userinfo</code> rejects it. Clients opt into notifications
by registering <code>backchannel_logout_uri</code>. Run the schema
migration for the new <code>oauthClient</code> and
<code>oauthAccessToken</code> columns.</p>
</blockquote>
</li>
<li>Modeled OAuth protected resources explicitly, with per-resource
TTLs, scopes, claims, and signing pins (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9648">#9648</a>)
<blockquote>
<p><strong>Migration:</strong> <code>validAudiences</code> is removed:
move each resource identifier into <code>resources</code> and link
restricted clients through <code>oauthClientResource</code>.
<code>@better-auth/mcp</code> now requires an explicit
<code>resource</code>. Run <code>npx @better-auth/cli generate</code>
and apply the migration before deploying.</p>
</blockquote>
</li>
<li>Decoupled SCIM provisioning from the organization plugin (<a
href="https://redirect.github.com/better-auth/better-auth/pull/10390">#10390</a>)
<blockquote>
<p><strong>Migration:</strong> SCIM configuration, client APIs, database
schema, and the Group model are all replaced, and provisioning state
cannot migrate in place. Follow the SCIM cutover in the 1.7 upgrade
guide, including a full directory reprovision, before resuming
traffic.</p>
</blockquote>
</li>
<li>Added OTP-only two-factor enablement with a discriminated
<code>enableTwoFactor</code> response (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9057">#9057</a>)
<blockquote>
<p><strong>Migration:</strong> <code>enableTwoFactor</code> now returns
a <code>method</code> field (<code>&quot;otp&quot;</code> or
<code>&quot;totp&quot;</code>); narrow on it before reading
<code>totpURI</code> and <code>backupCodes</code>. Pass <code>method:
&quot;otp&quot;</code> for OTP enrollment, which requires
<code>otpOptions.sendOTP</code>.</p>
</blockquote>
</li>
<li>Resolved the auth origin from <code>Host</code> by default when
using a dynamic <code>baseURL</code> (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9134">#9134</a>)
<blockquote>
<p><strong>Migration:</strong> If your proxy exposes the public hostname
only through <code>x-forwarded-host</code>, set
<code>advanced.trustedProxyHeaders: true</code>. Deployments where the
proxy rewrites <code>Host</code> (nginx default, Vercel, Cloudflare,
Netlify) are unaffected.</p>
</blockquote>
</li>
<li>Added unique lookup indexes for the device authorization
<code>deviceCode</code> and <code>userCode</code> columns (<a
href="https://redirect.github.com/better-auth/better-auth/pull/10059">#10059</a>)
<blockquote>
<p><strong>Migration:</strong> Resolve duplicate code values before
applying the migration. MySQL and SQL Server installations must also
convert both columns to bounded strings and clean up values longer than
191 characters.</p>
</blockquote>
</li>
<li>Enforced S256 PKCE in the Electron sign-in flow and hardened
custom-scheme origin checks (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9645">#9645</a>)
<blockquote>
<p><strong>Migration:</strong> Upgrade the
<code>@better-auth/electron</code> client and server together and add
your app's scheme to <code>trustedOrigins</code>. The
<code>code_challenge_method</code> parameter and
<code>disableOriginOverride</code> option are removed, and host-bearing
custom-scheme entries now match that host exactly.</p>
</blockquote>
</li>
<li>Identified Microsoft Entra accounts by the stable <code>oid</code>
claim (<a
href="https://redirect.github.com/better-auth/better-auth/pull/10204">#10204</a>)
<blockquote>
<p><strong>Migration:</strong> Migrate existing Microsoft account rows
created from <code>sub</code> before upgrading. Tokens without a valid
<code>oid</code> are rejected.</p>
</blockquote>
</li>
<li>Required a Google client ID before Google One Tap verifies ID tokens
(<a
href="https://redirect.github.com/better-auth/better-auth/pull/10036">#10036</a>)
<blockquote>
<p><strong>Migration:</strong> Configure <code>oneTap({ clientId
})</code> or <code>socialProviders.google.clientId</code>.</p>
</blockquote>
</li>
<li>Removed the deprecated <code>oidcProvider</code> plugin (<a
href="https://redirect.github.com/better-auth/better-auth/pull/10031">#10031</a>)
<blockquote>
<p><strong>Migration:</strong> Move OIDC authorization-server
integrations to <code>@better-auth/oauth-provider</code>.</p>
</blockquote>
</li>
<li>Rewrote the generic OAuth plugin as a first-class social provider
with OAuth 2.1 defaults (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9069">#9069</a>)
<blockquote>
<p><strong>Migration:</strong> Replace <code>signIn.oauth2({ providerId
})</code> with <code>signIn.social({ provider })</code>,
<code>oauth2.link()</code> with <code>linkSocial()</code>, and drop
<code>genericOAuthClient()</code>. Callbacks move to
<code>/api/auth/callback/:id</code>, <code>pkce</code> now defaults to
<code>true</code>, and <code>issuer</code> and
<code>requireIssuerValidation</code> are removed in favor of OIDC
discovery.</p>
</blockquote>
</li>
<li>Separated OAuth device grant ownership into
<code>oauthDeviceAuthorization()</code> (<a
href="https://redirect.github.com/better-auth/better-auth/pull/10746">#10746</a>)
<blockquote>
<p><strong>Migration:</strong> The OAuth integration replaces the
optional <code>resource</code> column with <code>oauthClientId</code>
and <code>resources</code>, so regenerate and apply the schema. Let
pending device codes expire before upgrading from an earlier 1.7
prerelease.</p>
</blockquote>
</li>
<li>Verified provider <code>id_tokens</code> with a single shared
verifier (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9828">#9828</a>)
<blockquote>
<p><strong>Migration:</strong> Custom <code>UpstreamProvider</code>
implementations replace the removed <code>verifyIdToken</code> method
with an <code>idToken</code> config carrying a JWKS source, issuer, and
audience. PayPal client <code>id_token</code> sign-in now returns
<code>ID_TOKEN_NOT_SUPPORTED</code>; its redirect flow is unchanged.</p>
</blockquote>
</li>
</ul>
<h3>Features</h3>
<ul>
<li>Added <code>clientAssertion</code> support to the Microsoft Entra ID
social provider (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9898">#9898</a>)</li>
<li>Made the <code>Auth</code> instance directly fetchable (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9431">#9431</a>)</li>
<li>Added per-provider <code>requireEmailVerification</code> for social
sign-in (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9929">#9929</a>)</li>
<li>Added a <code>user.validateUserInfo</code> gate for rejecting an
identity before a user is created or linked (<a
href="https://redirect.github.com/better-auth/better-auth/pull/9864">#9864</a>)</li>
<li>Added <code>hydrateSession</code> so <code>useSession</code> returns
server-fetched data on the first render (<a
href="https://redirect.github.com/better-auth/better-auth/pull/8733">#8733</a>)</li>
<li>Added compound table indexes to plugin database schemas (<a
href="https://redirect.github.com/better-auth/better-auth/pull/10402">#10402</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/better-auth/better-auth/blob/main/packages/better-auth/CHANGELOG.md">better-auth's
changelog</a>.</em></p>
<blockquote>
<h2>1.7.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/better-auth/better-auth/pull/8733">#8733</a>
<a
href="https://github.com/better-auth/better-auth/commit/4e8e4c7fc5fb2723144cbf41c4a1bfa28de8d671"><code>4e8e4c7</code></a>
Thanks <a href="https://github.com/bytaesu"><code>@​bytaesu</code></a>!
- Add <code>hydrateSession</code> to seed the client with a
server-fetched session so <code>useSession</code> returns data on the
first render.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/better-auth/better-auth/pull/9930">#9930</a>
<a
href="https://github.com/better-auth/better-auth/commit/0cbaf81bed9dec4c56880ee78a532262386e1ec5"><code>0cbaf81</code></a>
Thanks <a
href="https://github.com/gustavovalverde"><code>@​gustavovalverde</code></a>!
- Anonymous account linking now works after social and generic OAuth
sign-in in Expo and other in-app browsers, where the OAuth callback
returns without the session cookie. <code>onLinkAccount</code> fires and
the anonymous user is migrated; before, it was silently skipped.</p>
<p>Plugins can now carry server-trusted data across an OAuth redirect
with the new <code>addOAuthServerContext</code> API, read back on the
callback via <code>getOAuthState().serverContext</code>. Unlike
<code>additionalData</code>, it cannot be set from the request body, so
it is the right place for values the server must trust.</p>
<p>For <code>@better-auth/oauth-provider</code>, the post-login
authorization query now travels through that server-only channel, so it
can no longer be injected through <code>additionalData</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/better-auth/better-auth/pull/10004">#10004</a>
<a
href="https://github.com/better-auth/better-auth/commit/b36c38f9842d3416689340552989449a32007819"><code>b36c38f</code></a>
Thanks <a href="https://github.com/bytaesu"><code>@​bytaesu</code></a>!
- The captcha plugin now requires endpoint entries to match full auth
paths unless they use wildcard patterns. This prevents requests like
<code>/sign-in//email</code> from bypassing captcha while preserving
trailing-slash matches like <code>/sign-in/email/</code>. To protect
multiple routes, replace partial paths like <code>/sign-in</code> with
explicit wildcards such as <code>/sign-in/*</code> or
<code>/sign-in/**</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/better-auth/better-auth/pull/10746">#10746</a>
<a
href="https://github.com/better-auth/better-auth/commit/6782647d7c2d248246f9ef3980e656725c29ce64"><code>6782647</code></a>
Thanks <a
href="https://github.com/gustavovalverde"><code>@​gustavovalverde</code></a>!
- OAuth device grants now use <code>oauthDeviceAuthorization()</code>
alongside <code>oauthProvider()</code> or <code>mcp()</code>. This
single integration replaces both the standalone
<code>deviceCodeGrant()</code> plugin and the shared-grant
configuration. Standalone Device Authorization no longer accepts or
stores RFC 8707 resources, and <code>onDeviceAuthRequest</code> receives
only <code>clientId</code> and <code>scope</code>. The OAuth integration
rejects resource indicators that are not absolute, fragment-free
URIs.</p>
<p>The OAuth integration replaces the optional <code>resource</code>
column with <code>oauthClientId</code> and <code>resources</code>.
Regenerate and apply the schema when using it. Before upgrading from an
earlier 1.7 prerelease, let pending OAuth device codes expire or delete
them because they cannot be exchanged through the new integration.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/better-auth/better-auth/pull/10402">#10402</a>
<a
href="https://github.com/better-auth/better-auth/commit/763a2671c5372d88c291881977c8a1c2e29034b1"><code>763a267</code></a>
Thanks <a
href="https://github.com/gustavovalverde"><code>@​gustavovalverde</code></a>!
- Plugin database schemas can now define named or generated table-level
indexes across multiple fields. SQL migrations and generated Drizzle or
Prisma schemas resolve configured table and column names consistently,
while the MongoDB adapter creates the same indexes before the first
index-enforcing write.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/better-auth/better-auth/pull/9766">#9766</a>
<a
href="https://github.com/better-auth/better-auth/commit/bf39cbf13f3b934f728cde72b1e7ebdc4c85f641"><code>bf39cbf</code></a>
Thanks <a
href="https://github.com/GautamBytes"><code>@​GautamBytes</code></a>! -
Add a server-only <code>auth.api.consumePhoneNumberOTP</code> API for
custom phone OTP flows that need to verify and consume a code without
creating or updating users or sessions.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/better-auth/better-auth/pull/10330">#10330</a>
<a
href="https://github.com/better-auth/better-auth/commit/081d3c379c720926295067d878c421b5e8684c78"><code>081d3c3</code></a>
Thanks <a
href="https://github.com/ping-maxwell"><code>@​ping-maxwell</code></a>!
- Allow the username plugin's separate <code>displayUsername</code>
field to be omitted by
setting <code>displayUsername: false</code> on both the server and
client plugins.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/better-auth/better-auth/pull/10059">#10059</a>
<a
href="https://github.com/better-auth/better-auth/commit/49b5cf650e1264ecc4c917ca193ea05c3b58a3b9"><code>49b5cf6</code></a>
Thanks <a
href="https://github.com/GautamBytes"><code>@​GautamBytes</code></a>! -
Device Authorization now creates unique database indexes for
<code>deviceCode</code> and <code>userCode</code>, so each generated
code must be unique in its column. Existing installations on every
adapter must resolve duplicate values before applying the migration.
MySQL and SQL Server installations must also convert both columns to
bounded strings and clean up values longer than 191 characters before
running it.</p>
<p>Generated codes are limited to 191 characters. Issuance makes up to 3
attempts to overcome unique-key collisions, then returns
<code>server_error</code> if it cannot create a unique
<code>deviceCode</code> and <code>userCode</code>. Default-generated
user codes accept case changes and readability separators during
verification, approval, and denial; custom codes outside the default
alphabet are matched exactly. The <code>/device</code> limiter allows 5
requests over a window equal to the configured code lifetime, while
<code>/device/token</code> polling keeps its separate interval
behavior.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/better-auth/better-auth/pull/9645">#9645</a>
<a
href="https://github.com/better-auth/better-auth/commit/e0140297a59ddb59cccbcb4ba46c513de8cb86a7"><code>e014029</code></a>
Thanks <a
href="https://github.com/ping-maxwell"><code>@​ping-maxwell</code></a>!
- Harden the Electron OAuth flow and tighten custom-scheme
trusted-origin matching.</p>
<p>The Electron sign-in flow now mandates PKCE S256. Plain PKCE is
rejected: the <code>code_challenge_method</code> parameter is gone and
every authorization code is verified by hashing the verifier with
SHA-256. The server no longer trusts an <code>electron-origin</code>
header to set the request Origin. The Electron client now sends a real
<code>Origin</code> (for example <code>myapp:/</code>), so upgrade the
<code>@better-auth/electron</code> client and server together and make
sure your app's scheme is in <code>trustedOrigins</code>. The unused
<code>disableOriginOverride</code> option is removed.</p>
<p>Custom-scheme entries in <code>trustedOrigins</code> now match by
scheme and authority instead of string prefix. A host-less entry such as
<code>myapp://</code> or <code>exp://</code> still trusts every host of
that scheme, but a host-bearing entry such as
<code>myapp://callback</code> matches that host exactly, so it is no
longer satisfied by <code>myapp://callback.attacker.tld</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/better-auth/better-auth/pull/9948">#9948</a>
<a
href="https://github.com/better-auth/better-auth/commit/3d04fababbf3efd4c46a4012f46ed9397715c2e3"><code>3d04fab</code></a>
Thanks <a href="https://github.com/yordis"><code>@​yordis</code></a>! -
feat(generic-oauth): add <code>refreshTokenParams</code> config to
forward extra params on token refresh</p>
<p>Multi-tenant OIDC providers (Zitadel multi-org, Auth0 with
<code>audience</code>) need to send extra body params on the refresh
call to rescope tokens without a full authorization redirect. The
generic-oauth plugin now accepts a <code>refreshTokenParams</code>
option (object or sync/async function) that is merged into the refresh
request body, with <code>grant_type</code> and
<code>refresh_token</code> protected from override. The function form
receives request metadata for the request that triggered the refresh, so
request-scoped data (headers, cookies) is available without out-of-band
state like AsyncLocalStorage.</p>
<p><code>UpstreamProvider.refreshAccessToken</code> now accepts an
optional second <code>ctx</code> argument; the change is backwards
compatible because existing implementations that take only
<code>refreshToken</code> remain valid. See <a
href="https://redirect.github.com/better-auth/better-auth/issues/7554">#7554</a>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/better-auth/better-auth/pull/9069">#9069</a>
<a
href="https://github.com/better-auth/better-auth/commit/c7d22539ec4f7322d9625ae2953d397c3863d097"><code>c7d2253</code></a>
Thanks <a
href="https://github.com/gustavovalverde"><code>@​gustavovalverde</code></a>!
- Rewrite the generic OAuth plugin as a first-class social provider with
OAuth 2.1 security defaults. Providers now use
<code>signIn.social</code> + <code>callback/:id</code> instead of
dedicated plugin endpoints, with PKCE required by default (OAuth 2.1),
RFC 9207 issuer validation, OIDC auto-discovery with <code>openid</code>
scope injection, and typed provider IDs.</p>
<p><strong>Breaking changes:</strong></p>
<ul>
<li><code>signIn.oauth2({ providerId })</code> replaced by
<code>signIn.social({ provider })</code></li>
<li><code>oauth2.link()</code> replaced by
<code>linkSocial()</code></li>
<li>Callback URL changed from <code>/api/auth/oauth2/callback/:id</code>
to <code>/api/auth/callback/:id</code></li>
<li><code>genericOAuthClient()</code> removed; generic OAuth providers
now use the standard social client APIs</li>
<li><code>pkce</code> defaults to <code>true</code> (was
<code>false</code>); set <code>pkce: false</code> for providers that
reject PKCE</li>
<li><code>authorizationUrlParams</code> and <code>tokenUrlParams</code>
only accept <code>Record&lt;string, string&gt;</code></li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/better-auth/better-auth/commit/ccd57c2dcb145a40a30c75ab3f6c89b94af08701"><code>ccd57c2</code></a>
docs(changelog): align v1.7 release notes with final behavior (<a
href="https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth/issues/10846">#10846</a>)</li>
<li><a
href="https://github.com/better-auth/better-auth/commit/f577ec5c766c4ccd0a677ad18ca5f4f17b245289"><code>f577ec5</code></a>
chore: exit pre-release mode for v1.7.0</li>
<li><a
href="https://github.com/better-auth/better-auth/commit/69258d16709b977e519aa00a323ae54aea2b6164"><code>69258d1</code></a>
chore: sync main to next</li>
<li><a
href="https://github.com/better-auth/better-auth/commit/e84ec5e76d30e10fd692e8084177360484d538c1"><code>e84ec5e</code></a>
chore: release v1.6.30 (<a
href="https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth/issues/10840">#10840</a>)</li>
<li><a
href="https://github.com/better-auth/better-auth/commit/bc93b27542cbdf74f6455e6f66a0b4292c247a12"><code>bc93b27</code></a>
chore: release v1.7.0-rc.6 (<a
href="https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth/issues/10772">#10772</a>)</li>
<li><a
href="https://github.com/better-auth/better-auth/commit/58c49eb97f04ff18aa823318a3856a013353fdc2"><code>58c49eb</code></a>
chore: release v1.6.29 (<a
href="https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth/issues/10809">#10809</a>)</li>
<li><a
href="https://github.com/better-auth/better-auth/commit/e6e1b4e8146a84d2a2c5fe2c497c81d03dfc2ad3"><code>e6e1b4e</code></a>
perf(db): replace sequential get-then-delete loop with parallel deletes
in de...</li>
<li><a
href="https://github.com/better-auth/better-auth/commit/80799e69314d4d13c875457d932545d54fbc7ada"><code>80799e6</code></a>
chore: sync main to next</li>
<li><a
href="https://github.com/better-auth/better-auth/commit/3e485bf730c62b4ef3df2e55198179c3d15b5a9f"><code>3e485bf</code></a>
docs(username): fix displayUsername release notes (<a
href="https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth/issues/10776">#10776</a>)</li>
<li><a
href="https://github.com/better-auth/better-auth/commit/65fc17c755c3e2c8c77d5b401d612737764c219d"><code>65fc17c</code></a>
fix(deps): align <code>drizzle-orm</code> peer range with
drizzle-adapter (<a
href="https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth/issues/10501">#10501</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/better-auth/better-auth/commits/v1.7.0/packages/better-auth">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-25 11:16:22 -07:00
c5382b36ba build(deps-dev): bump vite from 6.4.3 to 8.2.2 (#11887)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite)
from 6.4.3 to 8.2.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>plugin-legacy@8.2.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@8.2.2/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.2.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.2.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@8.2.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@8.2.1/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.2.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.2.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>create-vite@8.2.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/create-vite@8.2.0/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@8.2.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@8.2.0/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.2.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.2.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.2.0-beta.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.2.0-beta.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.1.5</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.1.4</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.1.3</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.3/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.1.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.1.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>create-vite@8.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/create-vite@8.1.0/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@8.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@8.1.0/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@8.1.0-beta.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@8.1.0-beta.0/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v8.2.1...v8.2.2">8.2.2</a>
(2026-08-20)<!-- raw HTML omitted --></h2>
<h3>Features</h3>
<ul>
<li><strong>deps:</strong> widen <code>@vitejs/devtools</code> peer
range to v0.5.0 (<a
href="https://redirect.github.com/vitejs/vite/issues/23302">#23302</a>)
(<a
href="https://github.com/vitejs/vite/commit/495d9ff5a7d843ca876a9e49799947a5deb704c7">495d9ff</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>bundled-dev:</strong> handle lazy request error (<a
href="https://redirect.github.com/vitejs/vite/issues/23291">#23291</a>)
(<a
href="https://github.com/vitejs/vite/commit/3ba026dade4af56df08815310d3458fa110f5c5c">3ba026d</a>)</li>
<li><strong>bundled-dev:</strong> hot update through circular imports
instead of reloading (<a
href="https://redirect.github.com/vitejs/vite/issues/23259">#23259</a>)
(<a
href="https://github.com/vitejs/vite/commit/3dbddefaafc091a879b06f9279296f776691e455">3dbddef</a>)</li>
<li><strong>config:</strong> resolve sourcemap paths against sourcemap
location (<a
href="https://redirect.github.com/vitejs/vite/issues/23239">#23239</a>)
(<a
href="https://github.com/vitejs/vite/commit/05a003e6a17a84d75f907ea0f1598bc39b8dce6c">05a003e</a>)</li>
<li><strong>css:</strong> don't pass empty targets to lightningcss (<a
href="https://redirect.github.com/vitejs/vite/issues/23295">#23295</a>)
(<a
href="https://github.com/vitejs/vite/commit/2804636ff608d105928009d274ffba7cfbe55340">2804636</a>)</li>
<li><strong>define:</strong> fix match escaped dots to support
$-prefixed define keys (<a
href="https://redirect.github.com/vitejs/vite/issues/23249">#23249</a>)
(<a
href="https://github.com/vitejs/vite/commit/dcf88bd2ad2b1a8845f9029587cc8c825e382d42">dcf88bd</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23217">#23217</a>)
(<a
href="https://github.com/vitejs/vite/commit/ba958bddfc9cabe302c6b34269dcf5c9634531e0">ba958bd</a>)</li>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23218">#23218</a>)
(<a
href="https://github.com/vitejs/vite/commit/83ecb2c8059e8ce946a7cc835d4c14ef78aef4fd">83ecb2c</a>)</li>
<li><strong>module-runner:</strong> exclude completed modules from
in-flight cycle detection (fix <a
href="https://redirect.github.com/vitejs/vite/issues/22999">#22999</a>)
(<a
href="https://redirect.github.com/vitejs/vite/issues/23009">#23009</a>)
(<a
href="https://github.com/vitejs/vite/commit/d9b10a98db1c293ee64300bd75d568b44c8ae931">d9b10a9</a>)</li>
<li><strong>optimizer:</strong> close custom extension analysis bundles
(<a
href="https://redirect.github.com/vitejs/vite/issues/23207">#23207</a>)
(<a
href="https://github.com/vitejs/vite/commit/8fb76752836f61224d3095b502fa237b478a06b2">8fb7675</a>)</li>
<li>reduce Windows 8.3-short-name detection false-positives (<a
href="https://redirect.github.com/vitejs/vite/issues/23066">#23066</a>)
(<a
href="https://github.com/vitejs/vite/commit/02cffa9e2d38d5d8f12e4043ee9d0f7abb1471e2">02cffa9</a>)</li>
<li>respect <code>resolve.preserveSymlinks</code> when resolving root
(fix <a
href="https://redirect.github.com/vitejs/vite/issues/23197">#23197</a>)
(<a
href="https://redirect.github.com/vitejs/vite/issues/23198">#23198</a>)
(<a
href="https://github.com/vitejs/vite/commit/8413052731836d4aaf3eb94a0f25788dd35d2888">8413052</a>)</li>
<li><strong>ssr:</strong> rewrite computed key of destructing parameter
(<a
href="https://redirect.github.com/vitejs/vite/issues/23307">#23307</a>)
(<a
href="https://github.com/vitejs/vite/commit/9db0b61d4c9c7caad7ea1d9670b637faf2bb6c93">9db0b61</a>)</li>
<li><strong>vite:</strong> update outdated upstream file links in
license comments (<a
href="https://redirect.github.com/vitejs/vite/issues/23285">#23285</a>)
(<a
href="https://github.com/vitejs/vite/commit/c0f2fc607ee97ee4499337b04826420c00654065">c0f2fc6</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li><strong>build:</strong> note cssTarget precedence (<a
href="https://redirect.github.com/vitejs/vite/issues/23200">#23200</a>)
(<a
href="https://github.com/vitejs/vite/commit/a20a35ec0685e374519864d0f41dd5f6e9ba0271">a20a35e</a>)</li>
</ul>
<h3>Miscellaneous Chores</h3>
<ul>
<li>fix ts errors in build test cases (<a
href="https://redirect.github.com/vitejs/vite/issues/23209">#23209</a>)
(<a
href="https://github.com/vitejs/vite/commit/a0cfcf72f8ef8bf0f2f11d553333b9bb31f1d316">a0cfcf7</a>)</li>
</ul>
<h3>Code Refactoring</h3>
<ul>
<li>use JSON import attributes instead of readFileSync in constants (<a
href="https://redirect.github.com/vitejs/vite/issues/23258">#23258</a>)
(<a
href="https://github.com/vitejs/vite/commit/1d9fa392a43229241f80630236f8552ce8f7cd0f">1d9fa39</a>)</li>
<li>use named regex constants over inline literals (<a
href="https://redirect.github.com/vitejs/vite/issues/22964">#22964</a>)
(<a
href="https://github.com/vitejs/vite/commit/5c1c6c609718303202832f706884192e1f1e9223">5c1c6c6</a>)</li>
</ul>
<h3>Tests</h3>
<ul>
<li><strong>define:</strong> close rolldown bundler after generate (<a
href="https://redirect.github.com/vitejs/vite/issues/23231">#23231</a>)
(<a
href="https://github.com/vitejs/vite/commit/b4d66fee14d970f45b8a6f3d7d6aee73ca9b88ab">b4d66fe</a>)</li>
<li><strong>module-runner:</strong> add TLA circular import case (<a
href="https://redirect.github.com/vitejs/vite/issues/23299">#23299</a>)
(<a
href="https://github.com/vitejs/vite/commit/4a261f242831bef92afd2f1aacfb81eab9dec371">4a261f2</a>)</li>
<li><strong>module-runner:</strong> simplify server-hmr tests (<a
href="https://redirect.github.com/vitejs/vite/issues/23300">#23300</a>)
(<a
href="https://github.com/vitejs/vite/commit/599b44b6600ec426e10cd556908d53b027b0c4fb">599b44b</a>)</li>
<li><strong>ssr:</strong> add destructing assignment case for
moduleRunnerTransform (<a
href="https://redirect.github.com/vitejs/vite/issues/23308">#23308</a>)
(<a
href="https://github.com/vitejs/vite/commit/cb77e2a93bad2a8ece00b4aa0ef507c092582c45">cb77e2a</a>)</li>
</ul>
<h3>Build System</h3>
<ul>
<li>use JSON import attributes instead of readFIleSync in rolldown
configs (<a
href="https://redirect.github.com/vitejs/vite/issues/23251">#23251</a>)
(<a
href="https://github.com/vitejs/vite/commit/d615bcdb23d96c1ca5ce1ee45e21d8d87381106f">d615bcd</a>)</li>
</ul>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v8.2.0...v8.2.1">8.2.1</a>
(2026-08-06)<!-- raw HTML omitted --></h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>build:</strong> make client chunkImportMap work with
<code>sharedPlugins: true</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/23184">#23184</a>)
(<a
href="https://github.com/vitejs/vite/commit/15f03073c915d6ffb9a1fda447ef66b02bf5cde8">15f0307</a>)</li>
<li><strong>bundled-dev:</strong> inject client script tag before chunk
scripts (<a
href="https://redirect.github.com/vitejs/vite/issues/23161">#23161</a>)
(<a
href="https://github.com/vitejs/vite/commit/eac0cc84aa2472a85a19ee84561c1ba71e381a55">eac0cc8</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite/commit/de1111ab0be00879b404e7ed3b2a80e264edddc1"><code>de1111a</code></a>
release: v8.2.2</li>
<li><a
href="https://github.com/vitejs/vite/commit/cb77e2a93bad2a8ece00b4aa0ef507c092582c45"><code>cb77e2a</code></a>
test(ssr): add destructing assignment case for moduleRunnerTransform (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23308">#23308</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/9db0b61d4c9c7caad7ea1d9670b637faf2bb6c93"><code>9db0b61</code></a>
fix(ssr): rewrite computed key of destructing parameter (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23307">#23307</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/8413052731836d4aaf3eb94a0f25788dd35d2888"><code>8413052</code></a>
fix: respect <code>resolve.preserveSymlinks</code> when resolving root
(fix <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23197">#23197</a>)
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23">#23</a>...</li>
<li><a
href="https://github.com/vitejs/vite/commit/05a003e6a17a84d75f907ea0f1598bc39b8dce6c"><code>05a003e</code></a>
fix(config): resolve sourcemap paths against sourcemap location (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23239">#23239</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/495d9ff5a7d843ca876a9e49799947a5deb704c7"><code>495d9ff</code></a>
feat(deps): widen <code>@vitejs/devtools</code> peer range to v0.5.0 (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23302">#23302</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/1d9fa392a43229241f80630236f8552ce8f7cd0f"><code>1d9fa39</code></a>
refactor: use JSON import attributes instead of readFileSync in
constants (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/2">#2</a>...</li>
<li><a
href="https://github.com/vitejs/vite/commit/2804636ff608d105928009d274ffba7cfbe55340"><code>2804636</code></a>
fix(css): don't pass empty targets to lightningcss (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23295">#23295</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/599b44b6600ec426e10cd556908d53b027b0c4fb"><code>599b44b</code></a>
test(module-runner): simplify server-hmr tests (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23300">#23300</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/4a261f242831bef92afd2f1aacfb81eab9dec371"><code>4a261f2</code></a>
test(module-runner): add TLA circular import case (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23299">#23299</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v8.2.2/packages/vite">compare
view</a></li>
</ul>
</details>
<br />

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Priya Raman <priya@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-25 11:08:59 -07:00
DottaandPaperclip 4d2af732ae feat(runner): add native persistence contracts (#12169)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent runs need durable records so Paperclip can explain results and
final status changes.
> - The current heartbeat tables support direct adapters, but they do
not model native runner evidence.
> - The runner transport and server coordinator must share a strict
finalization contract before they write production data.
> - This pull request adds that contract and its additive database
boundary.
> - It does not select the Paperclip Runner or change any existing
adapter execution path.
> - The benefit is a reviewable persistence layer that preserves all
current behavior and supports later guarded integration.

## Linked Issues or Issue Description

Refs #11962

Refs #12129

## What Changed

- Add native run result, finalization, completion, assessment, status
decision, and status effect tables.
- Add inert native metadata to heartbeat runs and events. Keep `legacy`
as the default runtime mode.
- Bind each evidence relationship to one company, issue, run, contract,
result, assessment, and decision with composite constraints.
- Add a strict `paperclip.native_finalization.v1` shared type and
validator.
- Preserve database functions, triggers, and the unique indexes required
by foreign keys in JavaScript backups.
- Add migration, backup, mixed-owner denial, validator, and
direct-adapter compatibility tests.
- Document the new records and their ownership rules.

## Verification

- Run `pnpm -r typecheck`.
- Run `pnpm build`.
- Run `pnpm db:generate`. The schema output and migration safety checks
remain current.
- Run
`PAPERCLIP_PSQL_PATH=/Applications/Postgres.app/Contents/Versions/latest/bin/psql
pnpm exec vitest run
packages/shared/src/validators/native-finalization.test.ts
packages/db/src/client.test.ts packages/db/src/backup-lib.test.ts
server/src/__tests__/heartbeat-workspace-busy.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts`. All 52
tests pass.
- The full local `pnpm test:run` run completed 4,688 tests. It found 30
existing macOS test-environment failures. A serial rerun with the
canonical `/private/tmp` path reduced those failures to six existing
listener-diagnostics and skill-browser cases. None of those suites use
files in this change.
- The full Linux GitHub Actions matrix passes. This includes all
general-server, serialized-server, workspace, browser, build, typecheck,
canary, and aggregate verification jobs.
- Greptile passes at 5/5. Contributor trust, Superagent, Socket, and
Snyk pass with no finding from this change.
- Storybook visual regression skips by path because this pull request
has no UI or Storybook change.
- Confirm that the diff contains 25 files. Confirm that it contains no
workflow or `pnpm-lock.yaml` changes.

## Risks

- The migration adds tables, columns, indexes, a function, a trigger,
and ownership constraints. It does not remove or rename existing data.
- Composite foreign keys reject mixed-company, mixed-issue, and
mixed-run evidence even when each ID exists.
- The status-version trigger runs only when an issue status changes.
Backup tests confirm that restore retains this trigger and its
dependencies.
- Native source identifiers are unique when present. Legacy event rows
remain unchanged.
- This change does not add a unique run sequence constraint. The later
native writer must allocate its sequence atomically before that
invariant can be safe.
- Existing adapters keep their current execution and finalization paths.
New heartbeat runs default to `legacy` mode.

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

## Model Used

- OpenAI Codex with GPT-5. The exact deployment ID and context-window
size are not exposed. The model used agentic 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-25 13:08:39 -05:00
Zannis Kalampoukisandzannis 5db8ce3c44 fix(docker): make tini PID 1 in the server image so adopted orphans are reaped (#12137)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agent runs execute inside the server container, and they spawn many
short-lived descendants: git, the adapter CLI, esbuild, sh
> - The server image sets `ENTRYPOINT ["docker-entrypoint.sh"]`, and
that entrypoint ends in `exec`, so node becomes PID 1
> - Node reaps only the children it spawned itself. It installs no
`SIGCHLD`/`waitpid` handler for orphans that the kernel re-parents onto
PID 1, so those orphans stay as zombies forever
> - Zombies accumulate monotonically. When the cgroup pid limit is
reached, every `fork()` in the container fails and the instance is dead
> - This pull request installs `tini` and makes it PID 1 in front of the
existing entrypoint, adds a behavioural test that proves reaping, and
adds a `pids_limit` backstop to both compose files
> - The benefit is that a long-running container no longer degrades into
total fork failure, and a future regression is caught by CI instead of
by an outage

Depends-on: none — this change is self-contained in the image build and
its tests, and it touches no other in-flight branch

## Linked Issues or Issue Description

No public GitHub issue exists for this defect. It was found on a live
long-running instance. Description follows the bug report template.

**What happened?**

The server container ran for 22 hours and reached 2039 of 2048 pids in
its cgroup. Of 1760 processes, 1731 were zombies, and all 1731 had PID 1
as their parent. PID 1 was `node --import
./server/node_modules/tsx/dist/loader.mjs server/dist/index.js`. Zombies
accrued at about 79 per hour and were never reaped. The oldest zombie
was 20.8 hours old against a container uptime of 22.0 hours, so nothing
had been reaped since boot. Once the pid limit was reached, `git` and
`gh` failed with `pthread_create failed: Resource temporarily
unavailable`.

**Expected behavior**

PID 1 reaps orphaned processes that the kernel re-parents onto it. The
pid count of a long-running container stays flat instead of growing
without bound.

**Steps to reproduce**

1. Start the server image without `docker run --init` and without `init:
true`.
2. Run agent work that spawns descendants which outlive their immediate
parent.
3. Read `/sys/fs/cgroup/pids.current` and count processes in `Z` state
over several hours.
4. The zombie count grows monotonically and every zombie has PPID 1.

**Relevant logs or output**

```
cgroup pids.current / pids.max : 2039 / 2048
total processes                : 1760
  zombies                      : 1731  (98.4%)
  parent of every zombie       : PID 1  (1731/1731)
PID 1 cmdline                  : node --import .../tsx/dist/loader.mjs server/dist/index.js
container uptime               : 22.0 h
oldest zombie                  : 20.8 h    median: 14.4 h
zombie names                   : git 717, claude 280, MainThread 167, sleep 141,
                                 esbuild 138, postgres 76, sh 65, sccache 50
```

**Additional context**

The fix pattern is already in this repository.
`docker/agent-runtime/Dockerfile.base` installs `tini` and sets
`ENTRYPOINT ["/usr/bin/tini", "--"]`. It was never applied to the server
image.

## What Changed

- `Dockerfile`: install `tini` in the `base` stage and set `ENTRYPOINT
["/usr/bin/tini", "--", "docker-entrypoint.sh"]`. The entrypoint stays
in the exec chain, so UID/GID remapping, `gosu`, and graceful shutdown
are unchanged.
- `scripts/assert-orphan-reaping.sh` (new): a behavioural probe. It
spawns a leader that forks a grandchild, exits the leader, and asserts
that the orphaned grandchild leaves `Z` state instead of persisting. It
fails closed if the grandchild is not re-parented onto PID 1, so a pass
cannot mean the check ran too early.
- `.github/workflows/docker.yml`: run that probe against the pushed
image after the publish step. The publish step is multi-arch with `push:
true`, so nothing is loaded into the runner daemon and the pushed tag is
the only thing to test. The cloud variant is `FROM production` and
inherits the same `ENTRYPOINT`.
- `scripts/docker-build-test.sh`: run the same probe against a local
build.
- `docker/docker-compose.yml` and
`docker/docker-compose.quickstart.yml`: add `pids_limit: 2048` as a
backstop, so a future leak dies visibly at its own ceiling instead of
starving the host of pids.
- `server/src/__tests__/container-init-reaping.test.ts` (new): 13
assertions that guard the configuration the probe depends on.

No per-orchestrator init lever was added. The image owning PID 1 covers
compose, plain `docker run`, the quadlet units, and the ECS task
definition in one place. Adding `init: true` in compose or
`initProcessEnabled` on the ECS task would nest a second init around
`tini`, and `tini` then warns on every boot that it is not PID 1. The
new test asserts the absence of both levers across all three manifests,
so the decision survives the next edit.

## Verification

| Check | Result |
|---|---|
| `scripts/assert-orphan-reaping.sh` against a real init | Grandchild
re-parented to PPID 1, then reaped. Exit 0. |
| Same probe forced against a genuine zombie | Reports `Z` and fails.
The failure branch is not vacuous. |
| Config guard against the pre-fix files | Exactly the 3 relevant
assertions turn red. |
| Config guard with `tini` removed from `apt-get` but the comments kept
| Red. It checks the install, not a mention of the name. |
| `cd server && npx vitest run
src/__tests__/container-init-reaping.test.ts` | 13 passed |
| `npx tsc --noEmit -p server` | Clean |
| `node scripts/check-docker-deps-stage.mjs` | PASS |
| `node --test scripts/release-verify-workflow.test.mjs` | 8 passed |

Not verified locally: no container runtime is available in the authoring
environment, so the probe has not run against a build of this image. The
new `docker.yml` step runs it against the pushed image on this PR.

## Risks

Low risk, but it is an image and entrypoint change, so it affects
deployments.

- `tini` adds one small package to the `base` stage.
`docker/agent-runtime/Dockerfile.base` already installs it from the same
Debian archive.
- Signal handling changes shape: `tini` receives `SIGTERM` and forwards
it to the entrypoint, which `exec`s node. `tini` forwards signals to its
direct child by default, and the exec chain keeps node as that child, so
graceful shutdown is preserved. A reviewer should confirm this on a real
stop.
- `pids_limit: 2048` is new for compose users. A deployment that
legitimately needs more than 2048 processes would now hit the ceiling.
The measured steady state on a busy instance was under 400.
- If a deployment already passes `--init` or `init: true`, `tini` runs
under another init and prints a warning that it is not PID 1. Reaping
still works because the outer init handles it. The compose files in this
repository do not set `init: true`.

## Model Used

Claude Opus 5 (`claude-opus-5`), extended thinking, with tool use and
code execution in an agent harness.

## 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 issues or links
- [x] My branch name describes the change and contains no internal
ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All 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: zannis <1011451+zannis@users.noreply.github.com>
2026-08-25 09:52:39 -07:00
dependabot[bot] 3a841e15d0 build(deps-dev): bump @types/supertest from 6.0.3 to 7.2.1 (#11878)
Bumps
[@types/supertest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/supertest)
from 6.0.3 to 7.2.1.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/supertest">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-25 09:46:49 -07:00
dependabot[bot] ab4c4941f2 build(deps): bump @aws-sdk/client-s3 from 3.1111.0 to 3.1115.0 (#11876)
Bumps
[@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3)
from 3.1111.0 to 3.1115.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/aws/aws-sdk-js-v3/releases">@​aws-sdk/client-s3's
releases</a>.</em></p>
<blockquote>
<h2>v3.1115.0</h2>
<h4>3.1115.0(2026-08-20)</h4>
<h5>Documentation Changes</h5>
<ul>
<li><strong>client-pricing-plan-manager:</strong> Documentation update
for the CreateSubscription API to correct the default value of the
approval mode parameter. The default value for paid subscriptions is
MANUAL, not IMMEDIATE as previously documented. The default value
remains IMMEDIATE for FREE tier subscriptions. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/50d16ae3f271fe02a775de35ff81f96c3fc3f3f5">50d16ae3</a>)</li>
</ul>
<h5>New Features</h5>
<ul>
<li><strong>client-sesv2:</strong> Amazon SES now supports per-message
tracking overrides. You can use the new ConfigurationOverrides parameter
in SendEmail and SendBulkEmail to enable or disable open and click
tracking for individual messages without changing your account-level or
configuration set settings. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/da56caa551406c67f4add96bcb7a98ac9ad9ec5d">da56caa5</a>)</li>
<li><strong>client-arc-region-switch:</strong> Adds support for Rds
switchover read replica for Oracle databases in Region switch plans (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/85ffb20a7857736b13916df32017903c6fd0b3e0">85ffb20a</a>)</li>
<li><strong>client-ec2:</strong> EC2 marks UEFI instance metadata field
as sensitive. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/c232746ad78da7961a997005c6102943c392c30c">c232746a</a>)</li>
<li><strong>client-direct-connect:</strong> This release adds custom
route prefix pool allocations for Direct Connect. You can set IPv4 and
IPv6 route prefix counts on private and transit virtual interfaces, and
view pool size and unallocated counts on connections and LAGs, plus
direct connect gateway attachment prefix allocation totals. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/a94fb9783b97be5c59ef543ea7448d0e09f6f048">a94fb978</a>)</li>
<li><strong>client-amplify:</strong> Increased the maximum allowed
length from 255 to 4,096 characters to support longer access tokens. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/f7f8ecd1b8b45ba69bb48d1ac61a5bafc2a2d672">f7f8ecd1</a>)</li>
<li><strong>client-batch:</strong> AWS Batch now supports a new compute
environment type that provides fully managed EC2 capacity with broader
compute flexibility than Fargate, including GPU instances, bare metal,
and specific instance type selection, without infrastructure management
overhead. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/9c559a7366166cbe4e81c2752eb1c26696a884a9">9c559a73</a>)</li>
<li><strong>client-sagemaker:</strong> Added IAM Identity Center (IdC)
support to CreatePartnerApp and UpdatePartnerApp APIs. Added Customer
Managed Key (CMK) support to CreateMlflowApp and DescribeMlflowApp. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/5548588739d30ba5b7ebf1c6a88fa5749adb15b0">55485887</a>)</li>
<li><strong>client-lambda:</strong> Adds support for full JSON
resource-based policies, enabling customers to create, retrieve, update,
and delete function resource policies as complete JSON documents. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/72573a2ad860a406a0fe742dfd40b50458b6153d">72573a2a</a>)</li>
<li><strong>client-cloudfront:</strong> Added SigV4a as a supported
signing protocol for Origin Access Control (OAC), enabling CloudFront to
sign requests to Amazon S3 Multi-Region Access Point (S3-MRAP) origins.
(<a
href="https://github.com/aws/aws-sdk-js-v3/commit/95476293d5fa70f266cb4aa4c54ecebce9c771ce">95476293</a>)</li>
</ul>
<hr />
<p>For list of updated packages, view
<strong>updated-packages.md</strong> in
<strong>assets-3.1115.0.zip</strong></p>
<h2>v3.1114.0</h2>
<h4>3.1114.0(2026-08-19)</h4>
<h5>New Features</h5>
<ul>
<li><strong>client-eks:</strong> Adds support for EKS cluster
certificate authorities (CA) (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/a1316eaec0734d880bdb975c082110f36d3d7180">a1316eae</a>)</li>
<li><strong>client-bedrock-agentcore-control:</strong> AgentCore Memory
now supports Flexible Namespaces (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/65c89d6d82897e07d0d671fbd0a3a0a44a93a9a2">65c89d6d</a>)</li>
<li><strong>client-batch:</strong> AWS Batch now supports managing
CloudWatch Container Insights on compute environments via
CreateComputeEnvironment and UpdateComputeEnvironment. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/f77fc37f101238d66a1849924da42be3ac50f4c5">f77fc37f</a>)</li>
<li><strong>client-redshift:</strong> Amazon Redshift enhanced System
Table retention that allows customers to store their system table data
directly in S3 Tables in customer's account instead of Redshift Managed
Storage (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/a46d1f96345459f8c606642be702b8a723d97d51">a46d1f96</a>)</li>
<li><strong>client-bedrock-agentcore:</strong> AgentCore Memory now
supports Flexible Namespaces and Non-Conversational Payloads in
CreateEvent API (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/a0d8fb6df9d13bd1f22f89c1a5defef183becaf4">a0d8fb6d</a>)</li>
<li><strong>client-medialive:</strong> AWS Elemental MediaLive now
supports video cropping and output positioning. Use cropRectangle and
outputPositionRectangle to position the encoded video within the output
frame, with the surrounding area filled with black. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/2bf1331a81cddad987b30380c685cc3b4f85f18b">2bf1331a</a>)</li>
<li><strong>client-account-access:</strong> Adds throttling exceptions
to operation outputs that were previously inconsistent with other
operations. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/1e39b38544c7b77246db936bc6313163f98718b9">1e39b385</a>)</li>
<li><strong>client-vpc-lattice:</strong> Amazon VPC Lattice now supports
modification of private DNS options on Service Network VPC Associations
(<a
href="https://github.com/aws/aws-sdk-js-v3/commit/92c89b2723c281f6fc8d2562ec86dbd3c9716bdf">92c89b27</a>)</li>
<li><strong>client-redshift-serverless:</strong> Amazon Redshift
Enhanced System Table Retention that allows customers to store their
system table data directly in S3 Tables in customer's account instead of
Redshift Managed Storage (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/73ad53c311578c41c4420d71835f63ff021b703a">73ad53c3</a>)</li>
<li><strong>lib-transfer-manager:</strong> add file based download api
and worker thread based download. (<a
href="https://redirect.github.com/aws/aws-sdk-js-v3/pull/8259">#8259</a>)
(<a
href="https://github.com/aws/aws-sdk-js-v3/commit/b2d60357c87e86ce7da8902c9bd243bcc3bb34b2">b2d60357</a>)</li>
</ul>
<hr />
<p>For list of updated packages, view
<strong>updated-packages.md</strong> in
<strong>assets-3.1114.0.zip</strong></p>
<h2>v3.1113.0</h2>
<h4>3.1113.0(2026-08-18)</h4>
<h5>Chores</h5>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md">@​aws-sdk/client-s3's
changelog</a>.</em></p>
<blockquote>
<h1><a
href="https://github.com/aws/aws-sdk-js-v3/compare/v3.1114.0...v3.1115.0">3.1115.0</a>
(2026-08-20)</h1>
<p><strong>Note:</strong> Version bump only for package
<code>@​aws-sdk/client-s3</code></p>
<h1><a
href="https://github.com/aws/aws-sdk-js-v3/compare/v3.1113.0...v3.1114.0">3.1114.0</a>
(2026-08-19)</h1>
<p><strong>Note:</strong> Version bump only for package
<code>@​aws-sdk/client-s3</code></p>
<h1><a
href="https://github.com/aws/aws-sdk-js-v3/compare/v3.1112.0...v3.1113.0">3.1113.0</a>
(2026-08-18)</h1>
<p><strong>Note:</strong> Version bump only for package
<code>@​aws-sdk/client-s3</code></p>
<h1><a
href="https://github.com/aws/aws-sdk-js-v3/compare/v3.1111.0...v3.1112.0">3.1112.0</a>
(2026-08-17)</h1>
<p><strong>Note:</strong> Version bump only for package
<code>@​aws-sdk/client-s3</code></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/efc86fc9c3f80861228ad7f1b2fc97084b7a1c20"><code>efc86fc</code></a>
Publish v3.1115.0</li>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/5318b44c47c47e50ee77d6d8044cf8a472d2d08f"><code>5318b44</code></a>
Publish v3.1114.0</li>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/73a06d2aeb7261977dbffd4f604a6dafc3c2d381"><code>73a06d2</code></a>
Publish v3.1113.0</li>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/cb4ae7624bd56b21e127496f9641912b1a5a8ce2"><code>cb4ae76</code></a>
Publish v3.1112.0</li>
<li>See full diff in <a
href="https://github.com/aws/aws-sdk-js-v3/commits/v3.1115.0/clients/client-s3">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-25 09:41:28 -07:00
Nicky LeachandPaperclip 445547c989 feat(duplex): run the Daytona sandbox callback bridge over Node HTTP/2 (#12120)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandbox providers carry agent work through controlled execution
channels
> - The Daytona callback bridge uses a bespoke line-framed protocol over
its duplex channel
> - The bespoke protocol adds framing work and does not use the Node
transport that already supports multiplexed streams
> - This pull request carries raw bytes across the channel, adds a Node
HTTP/2 bridge, and selects it for Daytona
> - The benefit is one authenticated, multiplexed callback session with
queue_v1 as the bounded fallback

## Linked Issues or Issue Description

**Subsystem affected**

The packages/plugins Daytona provider and the shared duplex execution
path.

**Problem or motivation**

The Daytona callback bridge uses a bespoke line-framed protocol over the
provider duplex channel. This adds protocol work and limits stream
handling.

**Proposed solution**

Carry raw bytes through the cross-layer channel. Add an authenticated
Node HTTP/2 host server and sandbox client gateway. Select http2_v1 for
Daytona and retain queue_v1 as the fallback.

**Alternatives considered**

Keep the current duplex_v1 protocol. This keeps the bespoke framing path
and does not provide one HTTP/2 session for callback streams.

**Roadmap alignment**

ROADMAP.md lists Daytona under cloud and sandbox agents. This change
improves the shipped Daytona provider path.

**Additional context**

The branch adds no dependency. Node 24 provides the http2 module. The
host token check and canonical path parser remain the single dispatch
path.

## What Changed

- Carry raw Uint8Array chunks through the adapter, plugin, worker,
runtime, and Daytona layers.
- Encode bytes as base64 only across the JSON-RPC hop, because JSON has
no binary type.
- Add the bounded host HTTP/2 server and the in-sandbox HTTP/2 client
gateway.
- Authenticate every stream with the per-run bridge token before route
work.
- Parse the path once and reuse the canonical result for route and
forwarding work.
- Select http2_v1 for Daytona and fall back once to queue_v1 when the
client preface is absent.
- Add transport, session, stream, and fallback telemetry.
- Mark HTTP/2 as the preferred transport and queue_v1 as the
soft-deprecated fallback.

## Verification

- `npx vitest run packages/adapter-utils/src` — 990 passed and 4
skipped.
- `npx vitest run
server/src/__tests__/plugin-worker-manager-duplex.test.ts` — 32 passed.
- `npx vitest run --config
packages/plugins/sandbox-providers/daytona/vitest.config.ts` — 220
passed and 6 skipped.
- `npx tsc --noEmit` in `packages/adapter-utils`, `packages/shared`,
`packages/plugins/sdk`, and `server` — clean.
- No `package.json` or `pnpm-lock.yaml` file changed.
- The live Daytona test skips when `DAYTONA_API_KEY` is absent.
- The root `npx tsc --noEmit` command has a pre-existing missing
`packages/adapters/droid-local` reference on this branch and on
`master`.

## Risks

- The transport change affects several duplex layers and could expose
byte-boundary errors.
- A missing HTTP/2 client preface falls back once to queue_v1 and
records `preface_missing`.
- The host token check and canonical path parser must remain on the
shared dispatch path.
- The live Daytona test needs `DAYTONA_API_KEY` and does not run in this
agent sandbox.

## Model Used

OpenAI GPT-5, tool-enabled coding agent with repository inspection,
GitHub CLI, and shell execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-25 07:35:39 -07:00
DottaandPaperclip ffff1fe6e3 feat(runner): define package API and verification boundary (#12129)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The runner package now has protocol, transport, provider, catalog,
and authorization foundations.
> - Its first upstream package boundary should expose only the
implemented runtime and test-helper surfaces.
> - Rust correctness belongs in the repository existing build
verification, without introducing a parallel release process.
> - Direct package creation must build the files declared by the package
manifest.
> - This pull request defines the minimal package API and verifies the
optimized runner binaries in the existing PR and release Build jobs.
> - The benefit is a production-ready runner package boundary with
minimal build-process change.

## Linked Issues or Issue Description

Refs #11962

This pull request replaces one bounded part of the archived large runner
change. It follows the package-local authorization change in #12126.

## What Changed

- Export only `@paperclipai/paperclip-runner` and
`@paperclipai/paperclip-runner/testing`.
- Keep Node-only fixture loading and semantic conformance helpers out of
the runtime root.
- Add a provider-neutral semantic conformance kit with stable JSON
comparison and fail-closed input checks.
- Keep deferred SDK, eval, browser, React, lab, and command surfaces
private.
- Pin the runner Rust toolchain to 1.97.1 with the minimal profile and
`rustfmt`.
- Run the Rust workspace tests in release mode.
- Launch the optimized `paperclip-runnerd` and fake-harness binaries in
process-level integration coverage.
- Add one `pnpm --filter @paperclipai/paperclip-runner check:all` step
to each existing PR and release Build job.
- Make the existing server `prepack` lifecycle run its existing build
after it prepares UI assets.
- Document that no production adapter starts runnerd yet.

This revision adds no standalone GitHub Actions job. It adds no server
runner dependency or runner vendoring. It adds no Docker bootstrap or
clean-consumer harness. It does not change `pnpm-lock.yaml`.

## Verification

- `pnpm --filter @paperclipai/paperclip-runner check:all`
  - 66 TypeScript tests
  - 8 protocol contract tests
  - 56 Rust unit and integration tests
- Release-mode integration coverage launches the optimized runnerd and
fake-harness binaries.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/server-package-build-script.test.ts` (2 tests)
- Clean `pnpm pack` from `server/` rebuilt the server and produced both
`package/dist/index.js` and `package/dist/index.d.ts`.
- `node --test scripts/__tests__/release-verify-workflow.test.mjs` (8
tests)
- `pnpm -r typecheck`
- `pnpm build`
- `pnpm check:token-gates`
- `git diff --check`
- No `pnpm-lock.yaml` diff.
- The diff changes 12 files.

## Risks

The runner adds Rust work to the existing Build jobs. These jobs can
take longer on a cold cache. The pinned toolchain makes contributor and
CI behavior reproducible. Cargo tests use `--release` to verify
optimized executables. The server prepack lifecycle now performs the
build that its published entry points require. This can make direct
server packing slower. This pull request does not wire runnerd into the
server. It does not select runnerd for any adapter. Existing application
execution and finalization paths remain unchanged.

## Model Used

OpenAI Codex with GPT-5. Agentic coding mode used repository tools, code
execution, and automated 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
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-25 09:31:48 -05:00
Nicky LeachandPaperclip b6854e61c7 refactor(adapter-utils): rename EffectiveSandboxCapabilities to EffectiveExecutionCapabilities (#12119)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The adapter utilities package defines shared types for agent
execution targets
> - The type name EffectiveSandboxCapabilities describes only one
transport
> - All execution target drivers return the same resolved capability
snapshot
> - This pull request gives the snapshot a general name and keeps the
old type as a deprecated alias
> - The benefit is clearer public vocabulary with source compatibility
for current consumers

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The exported capability snapshot type uses the name
`EffectiveSandboxCapabilities`, although local, SSH, sandbox, and plugin
drivers return it.

**Subsystem affected**

The change affects `packages/adapter-utils` and its server consumers.

**Current behavior**

The public type name points to the sandbox transport. The private parser
also uses the sandbox-only name.

**Proposed behavior**

Use `EffectiveExecutionCapabilities` for the public type and
`parseEffectiveExecutionCapabilities` for the private parser. Keep a
deprecated alias for the old public type.

**Reason and benefit**

The new name matches the established execution-target vocabulary. The
alias keeps existing type imports working during the migration.

**Breaking changes**

None. The runtime field, capability flags, parsed shape, and package
versions do not change.

**Additional context**

GitHub search found no duplicate or related open issue or pull request.

## What Changed

- Rename the exported interface to `EffectiveExecutionCapabilities`.
- Keep `EffectiveSandboxCapabilities` as a deprecated type alias.
- Rename the private parser and update its call site and references.
- Add a type-level test for the deprecated alias.

## Verification

- `npx tsc --noEmit -p packages/adapter-utils`
- `npx vitest run
packages/adapter-utils/src/execution-target-sandbox.test.ts`
- `npx vitest run
server/src/__tests__/environment-execution-target-capabilities.test.ts
server/src/__tests__/environment-execution-target-duplex.test.ts`
- The local checks passed with 133 adapter-utils tests and 31 server
tests.
- Reviewers can confirm that the runtime field and capability flags stay
unchanged.

## Risks

Low risk. The alias protects existing type imports. The change does not
alter runtime behavior or serialized data.

## Model Used

OpenAI Codex, GPT-5, tool use and code execution. The runtime does not
expose the context window size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-25 07:13:55 -07:00
Nicky LeachandPaperclip d1573244b5 refactor: disambiguate the Telemetry and Observability data paths (#12128)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip records first-party events, OpenTelemetry data, and local
run-log events
> - The code and documents used one term for these three data paths
> - This naming made the required review level unclear
> - This pull request names each data path in the module names,
documents, and code comments
> - The benefit is a clear review rule without a runtime change

## Linked Issues or Issue Description

**Issue type**

Unclear or confusing.

**Where is the issue?**

`packages/shared/src/telemetry/README.md`, `doc/observability.md`,
`doc/run-log-events.md`, and the duplex instrumentation modules.

**What's wrong?**

The repository used Telemetry for first-party events, OpenTelemetry
data, and local run-log events. This usage made the data path and review
level unclear.

**Suggested fix**

Use Telemetry only for Paperclip first-party events. Use Observability
for OpenTelemetry data. Use the run log for rows in
`heartbeat_run_events`.

Related public pull requests: #8476 and #9672.

## What Changed

- Rename the duplex instrumentation modules and identifiers from
`Telemetry` to `Observability`.
- Move the Observability and run-log contracts out of the Telemetry
README.
- Add `doc/observability.md` and `doc/run-log-events.md` as the
canonical documents.
- Add a file-path review rule to `AGENTS.md`.
- Correct the remaining code comments that name the wrong data path.
- Keep all event names, payloads, database records, spans, configuration
keys, environment variables, and runtime paths unchanged.

## Verification

- `npx vitest run packages/shared/src/telemetry/readme-contract.test.ts`
passes.
- `npx vitest run packages/adapter-utils/src/published-exports.test.ts`
passes.
- `npx vitest run
packages/adapter-utils/src/acpx-engine/startup-timing.test.ts` passes
with 42 tests.
- `pnpm --filter @paperclipai/adapter-utils typecheck` passes.
- `pnpm --filter server typecheck` passes.
- The old module name does not remain in TypeScript or JSON files,
except for the intentional publication guard.
- CI and Greptile checks remain pending after PR creation.

## Risks

- The old duplex module subpath no longer has a compatibility shim. The
board accepted this intentional hard break.
- The new duplex module subpath stays blocked from package publication.
- The change has no runtime effect. The main risk is an incorrect
document or module reference.

## Model Used

OpenAI GPT-5 Codex, exact model ID `gpt-5`, with tool use and code
review support.

## 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 with the documentation issue
fields
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-24 16:42:33 -07:00
Nicky LeachandPaperclip a14e51d592 refactor(environment): classify environment capabilities from static driver definitions (#12045)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Environment runtime drivers provide workspace, lease, and custom
image behavior
> - Runtime code used driver identity checks and several
capability-specific members
> - These checks spread capability rules across the runtime and made new
drivers harder to verify
> - This pull request adds one general capability classifier and one
static driver support table
> - The benefit is one fail-closed capability model that keeps current
behavior and supports future drivers

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Environment runtime capability checks for workspace realization, custom
images, lease capabilities, and duplex authorization.

**Subsystem affected**

Cross-cutting (multiple of the above)

**Current behavior**

The runtime selects several capability paths from driver identity and
separate capability members. Custom image gates also trust provider
declarations without checking every matching live worker method.

**Proposed behavior**

The runtime uses one general capability classifier and one static
support table. Custom image gates require both the provider declaration
and every matching live worker method. The public capability names
remain unchanged.

**Reason and benefit**

The change keeps capability rules in one place. It removes identity
conditions from runtime consumers and makes unsupported drivers fail
closed.

**Breaking changes**

None. The public names sandboxCapabilities, sandboxProviders, and
EffectiveSandboxCapabilities remain available.

## What Changed

- Add classifyEnvironmentCapabilities and static support definitions for
all four driver families.
- Add resolveCapabilities to every environment runtime driver.
- Move driver traits into environment-driver-traits.ts and migrate
runtime consumers.
- Require provider declarations and matching live worker methods for all
custom image gates.
- Migrate duplex authorization to the general resolver and remove the
dead sandbox-only member.
- Delete the unused resolveEffectiveSandboxCapabilities wrapper and
update its test.

## Verification

- pnpm --filter @paperclipai/server typecheck
- pnpm exec vitest run
server/src/__tests__/environment-capability-contract.test.ts
server/src/__tests__/environment-runtime.test.ts — 92 tests pass
- pnpm exec vitest run
server/src/__tests__/environment-driver-traits.test.ts
server/src/__tests__/general-capability-classifier.test.ts — 12 tests
pass
- pnpm exec vitest run
server/src/__tests__/environment-custom-images-service.test.ts
server/src/__tests__/environment-execution-target-capabilities.test.ts
server/src/__tests__/environment-execution-target-duplex.test.ts
server/src/__tests__/environment-execution-target-duplex-kill-switch.test.ts
— 66 tests pass

## Risks

The main risk is a capability gate that denies a valid driver or permits
an invalid driver. The static support matrix, live worker method checks,
and regression tests reduce this risk. No database, public API, or
published type name changes.

## Model Used

OpenAI Codex, GPT-5, with tool use and code execution. The deployment
does not provide a separate context-window value.

## 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 (for example, docs/... or
fix/...) and contains no internal Paperclip ticket id or
instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-23 21:24:59 -07:00
Devin Foley 633e102971 fix: verify issue-update writes instead of inferring success (#12051)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents report task state to the control plane with `PATCH
/api/issues/{id}` at the end of each heartbeat
> - On remote sandbox targets those writes cross a relay that can fail
at the connection level
> - An agent that pipes its status curl through `head` cannot see that
failure; the write is lost but the run reports success
> - The issue then stays `in_progress` with no disposition, and the
missing-disposition recovery must repair it
> - This pull request makes the issue-update helper verify every write,
and it teaches the shared skill to require verified writes
> - The benefit is that a lost status write becomes a visible, retried
failure instead of a silent success

## Linked Issues or Issue Description

No public issue exists for this defect. The description below follows
the bug report template.

**What happened?**

A sandboxed heartbeat run answered its issue in a comment. It then sent
`PATCH /api/issues/{id}` with `status: done` through `curl -sf ... |
head -c 400`. The relay dropped the connection. The `-f` flag suppressed
the error output, and the pipe replaced curl's exit code with the exit
code of `head`. The agent saw empty output and exit 0. It reported the
write as an "empty 2xx" success and exited. The issue stayed
`in_progress`, and the successful-run recovery had to close it in a
corrective run.

**Expected behavior**

A status write that does not reach the server must surface as a failure.
The helper script must retry transient failures. It must exit non-zero
when the write is unconfirmed. Skill guidance must forbid write patterns
that hide failures.

**Steps to reproduce**

1. Point `PAPERCLIP_API_URL` at an endpoint that drops connections
intermittently.
2. Finalize an issue with `curl -sf -X PATCH
"$PAPERCLIP_API_URL/api/issues/$ID" -d '{"status":"done"}' | head -c
400`.
3. Observe exit code 0 with empty output while the server never received
the PATCH.

## What Changed

- `scripts/paperclip-issue-update.sh` now captures `%{http_code}`,
retries a retryable failure (connection-level, 429, 5xx) once — two
attempts total, which matches the shared bounded-write-retry rule —
rejects an empty 2xx body, and confirms the response echoes the
requested status before it exits 0.
- Failure output states plainly that the write was NOT saved, so the
calling agent reports it accurately.
- `skills/paperclip/SKILL.md` Step 8 adds a required "Verify writes —
never infer them" rule: a successful PATCH always returns the updated
issue JSON, disposition writes must never run through `head`/`tail`
pipelines, and an unconfirmed write must be reported as FAILED.
- `server/src/__tests__/paperclip-skill-utils.test.ts` pins the new
skill rule; a new `paperclip-issue-update-helper.test.ts` exercises the
helper's behavior end-to-end.

## Verification

- `bash -n scripts/paperclip-issue-update.sh`
- `server/src/__tests__/paperclip-issue-update-helper.test.ts` runs the
helper end-to-end against a local HTTP server: confirmed-echo success
(exit 0), empty 2xx (exit 1), wrong echoed status (exit 1), 422 reject
(exit 1, exactly one request), 503 then success (two requests),
connection refused (two attempts, then exit 1 with a "NOT saved"
report).
- `npx vitest run
server/src/__tests__/paperclip-issue-update-helper.test.ts
server/src/__tests__/paperclip-skill-utils.test.ts
server/src/__tests__/cli-invocation-safety.test.ts` — 50 passed.

## Risks

- Low risk. The success-path output is unchanged (the updated issue
JSON).
- The helper now exits non-zero on unconfirmed writes. Callers that
previously missed silent failures now see explicit errors. That is the
intended behavior change.
- The single retry re-sends the PATCH after a retryable failure. If the
first request committed and only its response was lost, an attached
comment can post twice. The duplicate is visible and benign; the prior
behavior lost the write silently.

## Model Used

- Claude Fable 5 (Anthropic), model id `claude-fable-5`, extended
thinking enabled, agentic tool use via Claude Code (CLI harness), 200k
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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-23 16:56:51 -07:00
Devin Foley c62bb4b16b feat: environment delete with agent reassignment and consented sandbox destroy (#12053)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Environments define where agent runs execute: local, SSH, or
provider sandboxes
> - Operators can create and edit environments, but the UI has no way to
delete one
> - The server already exposes `DELETE /environments/:id` and a
delete-blast-radius preflight, but no UI consumes them, and a delete
blocked by reusable sandbox leases gives the operator no path forward
> - This pull request adds the delete flow to the environment
configuration page: a preflight-driven modal that reassigns dependent
agents, names the workspaces that hold blocking sandbox leases, and can
destroy those sandboxes with explicit consent
> - The benefit is that operators can retire stale environments from the
UI without database surgery, and dependent agents move to a chosen
replacement instead of silently falling back

## Linked Issues or Issue Description

Refs #8554
Refs #11124

**Subsystem affected**

Environments (server routes, environment runtime service, and the
environment settings UI).

**Problem or motivation**

The environment configuration page has no delete control. The server
delete endpoint exists, but nothing in the UI calls it. When reusable
sandbox leases block a delete, the 409 error names no owner, so the
operator cannot find the blocking workspace. Agents that use the
environment as their default lose it silently through the FK `on delete
set null`.

**Proposed solution**

Add a delete button with a confirmation modal on the environment edit
page. The modal reads the delete-blast-radius preflight. It offers a
dropdown to reassign dependent agents to another environment before the
delete. It lists each workspace that holds a blocking reusable sandbox
lease, with a link. When those leases are the only blocker, the confirm
button destroys the sandboxes inline
(`?destroyReusableSandboxLeases=true`) and then deletes. A failed
teardown falls back to `pending_cleanup` for the sweep, so no sandbox is
orphaned.

## What Changed

- `ui/src/pages/CompanyEnvironments.tsx`: delete button on the edit page
header, confirmation modal with agent reassignment select, lease-holder
list, impact notes, and a consent-labeled destroy-and-delete action
- `ui/src/api/environments.ts`: `deleteBlastRadius` and `remove` client
methods; `remove` takes an optional `destroyReusableSandboxLeases` flag
- `server/src/routes/environments.ts`: `DELETE /environments/:id`
accepts `?destroyReusableSandboxLeases=true`; it destroys the
environment's reusable sandbox leases first, but only when those leases
are the sole delete blocker, then re-checks the blast radius before it
deletes
- `server/src/services/environment-runtime.ts`: new
`destroyReusableSandboxLeasesForEnvironment` — destroys every reusable
sandbox lease an environment still owns while the environment config
(provider credentials) is still available
- `server/src/services/environments.ts`: the delete blast radius now
returns `reusableSandboxLeaseHolders` (lease id, workspace, issue) so
clients can name what blocks a delete
- `packages/shared/src/types/environment.ts`:
`EnvironmentDeleteReusableLeaseHolder` type on the blast radius
- Tests: route gating for the consent flag (destroy runs, mixed-blocker
rejection, surviving-lease rejection), runtime destroy scoped to an
environment, blast-radius holder join, and UI tests for the reassignment
flow, holder links, and the consent button

## Verification

- `npx vitest run server/src/__tests__/environment-routes.test.ts
server/src/__tests__/environment-service.test.ts
server/src/__tests__/environment-runtime.test.ts
ui/src/pages/CompanyEnvironments.test.tsx`
- Manual: open Settings → Environments → edit an environment. The trash
icon opens the modal. With agents on the environment, pick a
reassignment target and confirm; agents move and the environment
deletes. With reusable sandbox leases, the modal names the holding
workspaces and the confirm button reads "Destroy N sandboxes and
delete".

## Risks

- The consented path destroys provider sandboxes. It runs only when
reusable leases are the sole blocker, so a delete that would still be
rejected never destroys anything. A failed teardown routes to
`pending_cleanup` and the delete stays blocked until the sweep resolves
it.
- Agent reassignment issues one PATCH per agent from the client. A
mid-sequence failure leaves some agents reassigned; the reassignments
are valid on their own and the UI refreshes to the actual state.
- Hard blockers (managed local, instance default, pending cleanup) keep
the existing 409 behavior and disable the confirm button.

## Model Used

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

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - Plugins extend the server with sandbox providers, tools, and jobs; a
loader activates them at boot
> - When activation fails, the loader marks the plugin `error` and skips
it on every later boot
> - Activation failures are often environmental — missing package
dependencies, a stale build output, a module that moved under a pull —
and the fix lands on disk without any write to the plugin row
> - The plugin therefore stays dead forever, and every feature behind it
(sandbox destroys, cleanup sweeps, probes) silently stops working until
an operator flips the row by hand
> - This pull request makes `loadAll` retry errored plugins once per
boot: flip to `ready`, attempt activation, and re-record the error if
the attempt fails
> - The benefit is that a plugin recovers on the next boot after its
environment is fixed, with no manual database or lifecycle intervention

## Linked Issues or Issue Description

**What happened?**

Several sandbox-provider plugins sat in `error` status for weeks after a
transient activation failure (a module resolution error from an older
checkout state). The boot loader only loads plugins in `ready` status,
so it never retried them. Environments backed by those providers lost
sandbox destroys, cleanup sweeps, and probes with no visible signal
other than the stale `last_error`.

**Expected behavior**

A plugin whose activation failure has been fixed on disk recovers on the
next server boot. A plugin that still fails stays in `error` with a
fresh error message.

**Steps to reproduce**

1. Install a plugin whose worker cannot start (for example, delete one
of its dependencies), then boot the server. The plugin lands in `error`
status.
2. Restore the dependency.
3. Restart the server. Before this change, the plugin stays in `error`
forever. After this change, the boot retries it and the plugin
activates.

## What Changed

- `server/src/services/plugin-loader.ts`: `loadAll` also fetches plugins
in `error` status, flips each to `ready`, and activates it with the
normal batch. The flip runs before activation because the `error` status
only legally transitions to `ready` or `uninstalled`; a retry that
failed while still in `error` could not re-mark itself. A failed flip
logs a warning and never aborts the boot load. The stale comment at the
`markError` site now describes the retry.
- `server/src/__tests__/plugin-loader-error-retry.test.ts`: covers the
flip-then-retry flow, the failed-flip isolation, and the empty case.

## Verification

- `npx vitest run server/src/__tests__/plugin-loader-error-retry.test.ts
server/src/__tests__/bundled-plugins.test.ts
server/src/__tests__/plugin-lifecycle-restart.test.ts
server/src/__tests__/cloud-image-bundled-plugins.test.ts`
- Manual: mark an installed plugin's status to `error`, restart the
server, and observe the loader log line `retrying plugins that failed
activation on a previous boot` followed by a successful activation (or a
fresh `last_error` if the plugin is genuinely broken).

## Risks

- A genuinely broken plugin now costs one bounded activation attempt per
boot (the attempts run in parallel with the ready batch under
`Promise.allSettled`). It cannot crash-loop within a running process,
and it returns to `error` with a fresh message.
- The flip clears `last_error` before the attempt. If the process dies
between the flip and the activation, the row is `ready` with no error
text; the next boot simply loads it as a ready plugin.
- Operators who relied on `error` as a manual "keep this off" latch
should use the `disabled` status, which this change does not touch.

## Model Used

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

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip keeps agent lifecycle changes behind control-plane
authorization
> - Plugins can create agents in a paused state until an operator
activates them
> - An agent with a direct configuration grant could not resume these
agents
> - A paused plugin-managed agent also had no stable provenance in its
pause reason
> - This pull request adds one protected resume path and preserves every
other lifecycle gate
> - The benefit is safe recovery from plugin provisioning without a
broad permission change

## Linked Issues or Issue Description

Refs #8168. That pull request uses a role capability and also opens
clear-error. This change uses the current grant system and keeps
clear-error closed.

**What happened?**

A plugin can create a paused managed agent. An agent actor cannot resume
that agent, even when the actor has a direct `agents:configure` grant.
The paused agent can also have a null pause reason.

**Expected behavior**

An agent with a direct `agents:configure` grant can resume an accessible
paused agent. An agent without that grant cannot resume it.
Plugin-managed paused agents show stable plugin provenance. A completed
resume stays in effect after reconcile.

**Steps to reproduce**

1. Install a plugin that declares a managed agent with `status: paused`.
2. Give a same-company agent a direct `agents:configure` grant.
3. Call `POST /api/agents/{id}/resume` with the granted agent key.
4. On the base revision, observe a board-only authorization error.

**Paperclip version or commit**

`master` at `63df7ad2b3`.

**Deployment mode**

All deployment modes. This is a server authorization and reconcile
behavior.

## What Changed

- The resume route now uses the protected `agent_config:update` decision
with `requiresChangeGrant: true` for agent actors.
- The route keeps board access, tenant non-disclosure, and invalid
organization-chain protection.
- Resume activity now records the real user or agent actor, run, and API
key.
- Plugin-managed paused agents now receive a stable provenance reason
and pause time at creation.
- Reconcile backfills only a null reason on an agent that is still
declared and stored as paused.
- Reconcile preserves manual, budget, system, and other pause reasons.
It does not pause a resumed agent again.
- The implementation specification now records the narrow resume
exception.

## Verification

- `pnpm exec vitest run
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/plugin-managed-agents.test.ts` passed: 2 files and
26 tests.
- `pnpm --filter @paperclipai/server typecheck` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- GitHub CI passed all policy, typecheck, build, test, e2e, canary, and
security gates on commit `306edf469c`.
- Greptile reviewed all 5 changed files. Its check passed with 0
comments and 0 unresolved threads.
- The host uses Node 22.22.2. The repository requests Node 24.11 or
newer, so pnpm printed engine warnings.
- A broad `pnpm test:run` attempt did not complete its general-server
group. Runtime port fixtures failed because host port `52000` was
already bound. The isolated failing fixture reproduced the same port
conflict. The focused feature tests passed before and after the final
commit.

## Risks

The main risk is an unintended lifecycle permission increase. The change
limits agent access to resume only. It requires a protected
direct-change decision. It does not open pause, clear-error, terminate,
approval, or key-management routes. Tests cover denial, self-denial,
tenant isolation, organization-chain checks, and activity attribution.
There is no database migration.

> This change fixes a narrow gap in the completed plugin, approval, and
activity-log roadmap areas. It does not add a new roadmap feature.

## Model Used

OpenAI Codex `gpt-5.6-sol`, with xhigh reasoning, tool use, and code
execution. The runtime did 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-08-23 16:15:32 -05:00