Files
PaperClipAI/docs/feedback-voting.md
T
Nicky LeachandPaperclip 5ca7b4c1fe fix(security): standardize paperclipai CLI guidance on safe npx path (#11343)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip provides CLI guidance to agents and operators through
documentation and runtime messages.
> - Content-bearing `pnpm paperclipai` examples send arguments through a
shell.
> - Shell evaluation can execute command substitutions in untrusted
argument content.
> - Runtime hostname guidance can also place request-derived content
inside a shell command.
> - This pull request uses `npx paperclipai` for content-bearing
guidance and uses a static hostname placeholder.
> - The benefit is safer copy-paste guidance for agents and operators.

## Linked Issues or Issue Description

**Issue type**
Incorrect information

**Where is the issue?**
CLI guidance in `doc/CLI.md`, `skills/paperclip/SKILL.md`,
documentation, and runtime-generated hints.

**What's wrong?**
Content-bearing `pnpm paperclipai` commands can pass argument text
through `/bin/sh`. Shell command substitution in an argument can execute
before the CLI receives the value.

**Suggested fix**
Use `npx paperclipai` for content-bearing commands. Use a static
`<host>` placeholder when runtime guidance displays the allowed-hostname
command.

## What Changed

- Replace content-bearing `pnpm paperclipai` examples with `npx
paperclipai` across the documentation and agent-facing guidance.
- Update runtime-generated CLI hints to use a static `<host>`
placeholder.
- Add safety notes to `doc/CLI.md` and `skills/paperclip/SKILL.md`.
- Add scans and regression tests for unsafe invocation and hostile
hostname headers.
- Keep fixed lifecycle commands and `pnpm --filter @paperclipai/*` build
commands unchanged.

## Verification

- Run `tsc --noEmit` for the changed server files.
- Run `cli-invocation-safety.test.ts`.
- Run `private-hostname-guard.test.ts`.
- Confirm that hostile hostname headers do not enter shown shell
commands.
- Confirm that the three commits contain the required Paperclip
co-author trailer.

## Risks

- This change updates documentation and diagnostic text across many
surfaces.
- Fixed lifecycle and setup commands remain unchanged.
- The tests fail if content-bearing `pnpm paperclipai` guidance returns.
- The change does not alter the CLI argument parser.

## Model Used

OpenAI Codex, GPT-5, tool use, code execution, and repository 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-13 16:43:21 -07:00

7.7 KiB

Feedback Voting — Local Data Guide

When you rate an agent's response with Helpful (thumbs up) or Needs work (thumbs down), Paperclip saves your vote locally alongside your running instance. This guide covers what gets stored, how to access it, and how to export it.

How voting works

  1. Click Helpful or Needs work on any agent comment or document revision.
  2. If you click Needs work, an optional text prompt appears: "What could have been better?" You can type a reason or dismiss it.
  3. A consent dialog asks whether to keep the vote local or share it. Your choice is remembered for future votes.

What gets stored

Each vote creates two local records:

Record What it contains
Vote Your vote (up/down), optional reason text, sharing preference, consent version, timestamp
Trace bundle Full context snapshot: the voted-on comment/revision text, issue title, agent info, your vote, and reason — everything needed to understand the feedback in isolation

All data lives in your local Paperclip database. Nothing leaves your machine unless you explicitly choose to share.

When a vote is marked for sharing, Paperclip immediately tries to upload the trace bundle through the Telemetry Backend. The upload is compressed in transit so full trace bundles stay under gateway size limits. If that immediate push fails, the trace is left in a retriable failed state for later flush attempts. The app server never uploads raw feedback trace bundles directly to object storage.

Viewing your votes

Quick report (terminal)

pnpm paperclipai feedback report

Shows a color-coded summary: vote counts, per-trace details with reasons, and export statuses.

# Installed CLI
paperclipai feedback report

# Point to a different server or company
pnpm exec paperclipai feedback report --api-base http://127.0.0.1:3000 --company-id <company-id>

# Include raw payload dumps in the report
pnpm paperclipai feedback report --payloads

API endpoints

All endpoints require board-user access (automatic in local dev).

List votes for an issue:

curl http://127.0.0.1:3102/api/issues/<issueId>/feedback-votes

List trace bundles for an issue (with full payloads):

curl 'http://127.0.0.1:3102/api/issues/<issueId>/feedback-traces?includePayload=true'

List all traces company-wide:

curl 'http://127.0.0.1:3102/api/companies/<companyId>/feedback-traces?includePayload=true'

Get a single trace envelope record:

curl http://127.0.0.1:3102/api/feedback-traces/<traceId>

Get the full export bundle for a trace:

curl http://127.0.0.1:3102/api/feedback-traces/<traceId>/bundle

Filtering

The trace endpoints accept query parameters:

Parameter Values Description
vote up, down Filter by vote direction
status local_only, pending, sent, failed Filter by export status
targetType issue_comment, issue_document_revision Filter by what was voted on
sharedOnly true Only show votes the user chose to share
includePayload true Include the full context snapshot
from / to ISO date Date range filter

Exporting your data

Export to files + zip

pnpm paperclipai feedback export

Creates a timestamped directory with:

feedback-export-20260331T120000Z/
  index.json                    # manifest with summary stats
  votes/
    PAP-123-a1b2c3d4.json      # vote metadata (one per vote)
  traces/
    PAP-123-e5f6g7h8.json      # Paperclip feedback envelope (one per trace)
  full-traces/
    PAP-123-e5f6g7h8/
      bundle.json              # full export manifest for the trace
      ...raw adapter files     # codex / claude / opencode session artifacts when available
feedback-export-20260331T120000Z.zip

Exports are full by default. traces/ keeps the Paperclip envelope, while full-traces/ contains the richer per-trace bundle plus any recoverable adapter-native files.

# Custom server and output directory
pnpm exec paperclipai feedback export --api-base http://127.0.0.1:3000 --company-id <company-id> --out ./my-export

Reading an exported trace

Open any file in traces/ to see:

{
  "id": "trace-uuid",
  "vote": "down",
  "issueIdentifier": "PAP-123",
  "issueTitle": "Fix login timeout",
  "targetType": "issue_comment",
  "targetSummary": {
    "label": "Comment",
    "excerpt": "The first 80 chars of the comment that was voted on..."
  },
  "payloadSnapshot": {
    "vote": {
      "value": "down",
      "reason": "Did not address the root cause"
    },
    "target": {
      "body": "Full text of the agent comment..."
    },
    "issue": {
      "identifier": "PAP-123",
      "title": "Fix login timeout"
    }
  }
}

Open full-traces/<issue>-<trace>/bundle.json to see the expanded export metadata, including capture notes, adapter type, integrity metadata, and the inventory of raw files written alongside it.

Each entry in bundle.json.files[] includes the actual captured file payload under contents, not just a pathname. For text artifacts this is stored as UTF-8 text; binary artifacts use base64 plus an encoding marker.

Built-in local adapters now export their native session artifacts more directly:

  • codex_local: adapter/codex/session.jsonl
  • claude_local: adapter/claude/session.jsonl, plus any adapter/claude/session/... sidecar files and adapter/claude/debug.txt when present
  • opencode_local: adapter/opencode/session.json, adapter/opencode/messages/*.json, and adapter/opencode/parts/<messageId>/*.json, with optional project.json, todo.json, and session-diff.json

Sharing preferences

The first time you vote, a consent dialog asks:

  • Keep local — vote is stored locally only (sharedWithLabs: false)
  • Share this vote — vote is marked for sharing (sharedWithLabs: true)

Your preference is saved per-company. You can change it any time via the feedback settings. Votes marked "keep local" are never queued for export.

Data lifecycle

Status Meaning
local_only Vote stored locally, not marked for sharing
pending Marked for sharing, saved locally, and waiting for the immediate upload attempt
sent Successfully transmitted
failed Transmission attempted but failed (for example the backend is unreachable or not configured); later flushes retry once a backend is available

Your local database always retains the full vote and trace data regardless of sharing status.

Remote sync

Votes you choose to share are sent to the Telemetry Backend immediately from the vote request. The server also keeps a background flush worker so failed traces can retry later. The Telemetry Backend validates the request, then persists the bundle into its configured object storage.

  • App server responsibility: build the bundle, POST it to Telemetry Backend, update trace status
  • Telemetry Backend responsibility: authenticate the request, validate payload shape, compress/store the bundle, return the final object key
  • Retry behavior: failed uploads move to failed with an error message in failureReason, and the worker retries them on later ticks
  • Default endpoint: when no feedback export backend URL is configured, Paperclip falls back to https://telemetry.paperclip.ing
  • Important nuance: the uploaded object is a snapshot of the full bundle at vote time. If you fetch a local bundle later and the underlying adapter session file has continued to grow, the local regenerated bundle may be larger than the already-uploaded snapshot for that same trace.

Exported objects use a deterministic key pattern so they are easy to inspect:

feedback-traces/<companyId>/YYYY/MM/DD/<exportId-or-traceId>.json