## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The CLI is one of the main operator and agent-facing control
surfaces.
> - CLI commands build API paths from dynamic company, issue, project,
agent, and other resource identifiers.
> - Dynamic path segments need to be encoded so reserved characters
cannot reshape the request URL.
> - Empty dynamic path segments should fail locally instead of creating
malformed API routes.
> - The shared `apiPath` helper already implements those safeguards, but
its behavior was not directly covered in the common CLI tests.
> - This pull request adds focused coverage for path segment encoding
and empty-segment rejection.
> - The benefit is stronger regression coverage around a small but
security-relevant CLI routing helper.
## Linked Issues or Issue Description
- Bug: `apiPath` is the shared CLI helper for constructing API paths
with dynamic identifiers, but the common CLI tests did not directly
assert that dynamic segments are URL-encoded or that empty segments are
rejected before a request is made.
## What Changed
- Imported `apiPath` into `cli/src/__tests__/common.test.ts`.
- Added coverage that verifies reserved characters in dynamic path
segments are encoded.
- Added coverage that verifies empty and undefined dynamic path segments
throw before producing a malformed path.
## Verification
- `./node_modules/.bin/vitest run cli/src/__tests__/common.test.ts
--config cli/vitest.config.ts` passed (10 tests).
- `git diff --check` passed.
## Risks
- Low risk. This is test-only coverage for existing helper behavior.
- If future code intentionally wants query-string construction through
this helper, it should use static template text for the query string and
keep dynamic values as path segments or use a dedicated query builder.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI GPT-5 via Codex, with code editing and local command execution.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: 馨冉 <xinxincui239@gmail.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents act on issues through the `paperclipai` CLI as well as the
HTTP API; the server gates agent-authenticated **mutations** of an
in-progress issue (checkout, release, interactions, PATCH, attachment
upload) behind the `X-Paperclip-Run-Id` header (`requireAgentRunId` /
`assertAgentIssueMutationAllowed`).
> - The CLI's HTTP client (`client/http.ts`) already supports sending
that header, but `resolveCommandContext` never populated `runId`, so
there was no way to provide it — every agent-authenticated mutation via
the CLI failed with `401 Agent run id required`.
> - Separately, `issue attachment:upload` hand-rolls its own multipart
`fetch` (bypassing the JSON client), so it never forwarded the run-id at
all, and its `inferContentTypeFromPath` couldn't produce `text/html` and
appended `; charset=utf-8` to `md`/`txt` — which fails the server's
exact-match content-type allowlist (`422 Unsupported attachment content
type`).
> - This PR lets the CLI send `X-Paperclip-Run-Id` from a new global
`--run-id` flag (falling back to `$PAPERCLIP_RUN_ID`), and fixes
`attachment:upload` to forward the run-id and emit server-allowed bare
MIME types.
> - The benefit is that an embodied agent can drive the full issue
lifecycle (checkout → work → disposition → upload deliverable) entirely
through the official CLI, instead of dropping to raw HTTP.
## Linked Issues or Issue Description
No issue exactly covers the CLI **send** side, so describing it here
(bug path). Related:
- `Refs #2063` — "Sub-agents cannot post comments on subtickets — Agent
run id required" (same error string; that report focuses on the server
gate, this PR fixes the CLI not sending the header for agent mutations).
- `Refs #1199` — injects `X-Paperclip-Run-Id` on the **http adapter's**
outbound request (server side). This PR is the complementary **CLI
client** side.
**Bug (per `bug_report.yml`):**
- **What happened:** Running agent-authenticated CLI mutations (`issue
checkout` / `issue update` on an in-progress issue / `issue
attachment:upload`) returns `401 Agent run id required`, even with
`--run-id`/`$PAPERCLIP_RUN_ID` set; `attachment:upload` of an
HTML/markdown deliverable additionally returns `422 Unsupported
attachment content type`.
- **Expected:** The CLI forwards the agent run-id so the server
authorizes the mutation, and uploads use a content-type the server
accepts.
- **Steps to reproduce:** As an agent token, `paperclipai issue checkout
<id> --agent-id <id>` then `paperclipai issue update <id> --status done`
(→ 401); `paperclipai issue attachment:upload <id> ./report.html` (→
401, then 422 once run-id is wired).
- **Deployment mode:** local_trusted (applies to all modes — server-side
gate is mode-independent).
## What Changed
- `cli/src/commands/client/common.ts`: resolve `runId` in
`resolveCommandContext` from a new global `--run-id` flag, falling back
to `$PAPERCLIP_RUN_ID`, so the existing HTTP client sends
`X-Paperclip-Run-Id`; thread `runId` into the attachment-upload path;
align `inferContentTypeFromPath` with the server's
`DEFAULT_ALLOWED_TYPES` (add
`html`/`htm`/`csv`/`zip`/`mp4`/`m4v`/`webm`/`mov`/`qt`, drop the `;
charset` suffix).
- `cli/src/commands/client/issue.ts`: pass `ctx.api.runId` into
`uploadAttachment` and send the `X-Paperclip-Run-Id` header on the
hand-rolled multipart request (matching what the JSON client injects
automatically).
- Tests: CLI asserts `attachment:upload` forwards `x-paperclip-run-id` +
the inferred bare MIME type, and that `inferContentTypeFromPath` covers
the allowed types; a server test locks the contract that an in-progress
checkout owner without a run-id is rejected `401` on attachment upload.
## Verification
```bash
# CLI tests (no DB)
node_modules/.bin/vitest run \
cli/src/__tests__/common.test.ts \
cli/src/__tests__/issue-subresources.test.ts
# → 2 files, 13 tests passed
# Server contract test (embedded postgres)
node_modules/.bin/vitest run \
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
# → 37 tests passed
```
Manual: with an agent token and a valid `$PAPERCLIP_RUN_ID`, `issue
checkout` / `issue update --status done` / `issue attachment:upload
./report.html` now succeed where they previously returned 401/422.
## Risks
Low. Additive only:
- `--run-id` is a new optional flag; behavior is unchanged when it (and
`$PAPERCLIP_RUN_ID`) are unset — the header is simply omitted as before.
- The content-type map only **widens** the allowed set to match the
server's existing allowlist and removes a suffix the server already
rejected, so no previously-accepted upload changes type.
- No schema/migration changes; no server behavior changes (the server
test only documents the existing gate).
## Model Used
Claude Opus 4.8 (1M context window), via Claude Code (tool use / agentic
file edits + local test execution). Extended reasoning 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots — N/A (CLI-only)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green — pending CI run on this PR
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
pending review
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, with the CLI
acting as a scriptable operator and agent interface to that control
plane.
> - The REST API surface has grown across companies, agents, issues,
routines, plugins, auth, workspaces, secrets, and operational inspection
commands.
> - The CLI had drifted from that API surface: some commands were
missing, some command shapes differed from docs/reference material, and
several edge cases only failed during end-to-end local-source testing.
> - The local development runbook requires these tests to be disposable
and isolated from a real `~/.paperclip`, `~/.codex`, or `~/.claude`
installation.
> - This pull request adds broad CLI/API parity coverage, fixes the
actionable bugs found during that pass, and records the reproducible
test log under `doc/logs`.
> - The benefit is a more complete, scriptable CLI surface with
regression coverage for the command families exercised by the parity
run.
## What Changed
- Added or expanded CLI command coverage for access/auth, companies,
agents, projects, goals, issues and subresources, routines, plugins,
workspaces, activity/run/cost/dashboard inspection, assets, skills,
secrets, tokens, prompt/wake flows, and local setup helpers.
- Fixed CLI/API parity bugs found during the run, including context
profile patching, issue interaction optional payloads, malformed
tree-hold errors, environment duplicate handling, configure
invalid-section exit codes, worktree pnpm invocation, token agent ID
resolution, plugin tool worker lookup, and routine webhook secret
cleanup.
- Added missing CLI wrappers and route coverage for health/access,
invite resolution URL forwarding, join status normalization, secret
lifecycle commands, LLM docs routes, available-skill isolation, positive
board-claim coverage, and interactive `connect` prompt-flow tests.
- Added a schema-backed `/api/openapi.json` route sufficient for CLI
parity and `paperclipai openapi --json` smoke coverage.
- Added `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` with the
detailed living test/bug log and renamed the log directory from
`doc/bugs` to `doc/logs`.
- Added `doc/plans/2026-05-23-cli-api-parity.md` and the OpenAPI parity
reference used during the pass.
OpenAPI note: this PR intentionally does not try to subsume
`feature/openapi-spec`. The OpenAPI implementation here is schema-backed
and better than the earlier route-inventory stub, but
`feature/openapi-spec` is the fuller/better OpenAPI branch because it
includes exact mounted-route coverage tests and additional current route
coverage. That branch should stay as its own PR and can supersede this
OpenAPI route implementation.
## Verification
Targeted automated checks run:
- `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts`
- `pnpm exec vitest run server/src/__tests__/board-claim.test.ts`
- `pnpm exec vitest run cli/src/__tests__/connect.test.ts`
- `pnpm exec vitest run cli/src/__tests__/agent-lifecycle.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts`
- `pnpm exec vitest run server/src/__tests__/routines-service.test.ts`
- `pnpm --dir cli typecheck`
- `pnpm --dir server typecheck`
Manual/local E2E verification:
- Ran the full disposable local-source CLI/API parity pass with isolated
`PAPERCLIP_HOME`, `PAPERCLIP_CONFIG`, `PAPERCLIP_CONTEXT`,
`PAPERCLIP_AUTH_STORE`, `CODEX_HOME`, and `CLAUDE_HOME` under
`tmp/cli-api-parity`.
- Verified `DATABASE_URL` and `DATABASE_MIGRATION_URL` stayed unset for
the scratch server.
- Verified live health and schema-backed OpenAPI responses on
non-default port `3197`.
- Revoked created board/agent tokens and cleaned up temporary plugins,
secrets, non-default environments, and project workspaces.
- See `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` for the full
command-by-command reproduction log.
Not run:
- Full `pnpm test`, `pnpm test:run`, or `pnpm build` were not run after
the entire branch because the branch is broad and the parity pass used
focused test/typecheck verification plus live isolated CLI reruns.
## Risks
- This is a broad PR and touches many CLI command modules, so review
surface is high. The changes are grouped around one theme, but a split
may be easier if maintainers prefer narrower PRs.
- The OpenAPI route in this PR is not the final/best OpenAPI
implementation. `feature/openapi-spec` has stronger exact-route coverage
and should remain the source for the dedicated OpenAPI PR.
- The living log is intentionally detailed and large. It is useful for
reproducibility but adds documentation weight.
- No UI changes are intended; screenshots are not applicable.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in Codex desktop. Exact served
model/context-window identifier was not exposed in the local app. Work
used shell/Git/GitHub CLI tooling, local source inspection, targeted
test execution, and live isolated Paperclip CLI/API smoke testing.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Devin Foley <devin@devinfoley.com>