skill-feedback
Append-only self-assessment log for Claude Code skills. The agent rates its own use of a skill; the rating lands in .agent-plus/skill-feedback/<skill>.jsonl next to the project, never on a network. Skill authors run report to aggregate, or submit to bundle into a GitHub issue body for the skill's source repo.
The binary lives at ${CLAUDE_SKILL_DIR}/../../bin/skill-feedback; the plugin auto-adds bin/ to PATH, so call it as skill-feedback.
When to reach for this
The default trigger is implicit: every time you finish using a skill — yours or another agent-plus plugin's — emit one log entry before stopping. Treat it like a CHANGELOG line, not a separate task. One call, one line, done.
# Successful use of hermes-remote, no friction
skill-feedback log hermes-remote --rating 5 --outcome success
# Partial: the skill mostly fit, but you had to fall back to raw curl for one bit
skill-feedback log hermes-remote --rating 3 --outcome partial \
--friction "no streaming chat support; fell back to curl + SSE"
# Failure: the skill didn't fit the request at all
skill-feedback log github-remote --rating 1 --outcome failure \
--friction "user wanted to merge a PR; pr merge isn't wrapped"
Also trigger on explicit user requests:
- "show me feedback on the hermes skill" →
skill-feedback show hermes-remote --since 7d - "report on all skill usage" →
skill-feedback report --since 30d --pretty - "submit the feedback to the upstream repo" →
skill-feedback submit hermes-remote --no-dry-run
What to put in the fields
| Field | Required | Guidance |
|---|---|---|
<skill> |
yes | The exact name: value from the skill's SKILL.md (e.g. hermes-remote). |
--rating 1..5 |
yes | 5 = skill fit perfectly, 1 = skill was actively a wrong fit. Be honest — over-positive ratings waste the author's time. |
--outcome success|partial|failure |
yes | Was the user's actual request resolved using this skill? |
--friction "..." |
optional | One short label, e.g. "no streaming", "missing --json on list", "wrong default timeout". Aggregates by exact match — keep it short and reusable across sessions. |
--note "..." |
optional | Up to 1000 chars of free text. Capped + regex-scrubbed for token patterns before write. |
Don't over-share. No transcripts, no user prompts, no arguments containing PII or secrets. If in doubt, leave --note off and rely on --friction alone.
Headline commands
skill-feedback log <skill> --rating 1-5 --outcome success|partial|failure \
[--friction "<short label>"] [--note "<longer text>"] \
[--tool-version <ver>]
skill-feedback show <skill> [--since 7d] [--limit 50]
skill-feedback report [--skill <name>] [--since 30d] [--pretty]
skill-feedback submit <skill> [--since 30d] [--repo owner/name] [--no-dry-run]
skill-feedback path [--skill <name>]
All commands emit JSON to stdout. Use --pretty for indentation. Every payload carries a top-level tool: {name, version} field for self-diagnosis (pattern #6 in the agent-plus README).
Storage
JSONL, one entry per line, append-only:
{"ts":"2026-04-26T21:30:00Z","skill":"hermes-remote","rating":5,
"outcome":"success","session_id":"...","tool_version":"0.4.1","schema":1}
Storage root is resolved in this order (highest first):
SKILL_FEEDBACK_DIRenv var (absolute path)<git-toplevel>/.agent-plus/skill-feedback/if cwd is in a git repo<cwd>/.agent-plus/skill-feedback/if a project-local.agent-plus/exists~/.agent-plus/skill-feedback/(last-resort fallback)
Run skill-feedback path to print the resolved root, or skill-feedback path --skill X for the per-skill jsonl path.
Submit flow (opt-in)
submit bundles recent entries into a markdown body and (with --no-dry-run) opens a GitHub issue against the skill's source repo. Repo URL is read from the skill's plugin.json#repository field, or you can pass --repo owner/name explicitly.
- Default is
--dry-run. It prints the title + body so the user can review before anything leaves the machine. --no-dry-runrequires eitherghon PATH (usesgh issue create) or falls back to writing the body to<root>/<skill>.submit.mdfor manual paste. No raw GitHub API calls — auth is borrowed fromgh.- Free-text fields are regex-scrubbed before write, but the regex only catches token shapes — not PII, customer names, internal hostnames, or contextual leaks. You (the agent) are the final gate before anything reaches a public tracker; see "Privacy review before submit" below. The user owns the decision to publish.
Privacy review before submit
Regex scrubbing (in log) is the floor, not the ceiling. It catches ghp_…, AKIA…, sk-ant-…, etc., but it cannot recognise PII or context-sensitive content. Before running submit --no-dry-run, you (the agent already executing this skill in the user's Claude Code session) must do a privacy review pass on the assembled body. No extra API call, no SDK — your existing context is what does the work.
Workflow:
- Always run
submit --dry-runfirst (it's the default). Read thebodyfield in the resulting JSON. The result also exposesagent_review_required: trueplus anagent_review_checklistarray as a built-in reminder. - Scan
bodyagainst this checklist for content the regex cannot pattern-match:- PII: real names, email addresses, phone numbers, physical addresses
- Customer / employer / client / project names that aren't public
- Internal hostnames, private URLs, file paths revealing identity (e.g.
/Users/<name>/work/<client>/...) - Ticket IDs, repo names, or Slack channel names that aren't public
- Error messages or stack traces that quote internal data
- Anything you wouldn't paste into a public issue on an open-source repo
- If anything in step 2 is present: ABORT. Do not run
--no-dry-run. Tell the user exactly what you found and let them decide:- Edit
<root>/<skill>.jsonlby hand and retry - Use
--sinceto scope a window that excludes the sensitive entries - Skip the submit
- Edit
- Only run
--no-dry-runafter step 2 returns clean.
This is documentation, not enforcement — there is no flag to require the review and no flag to bypass it. You're trusted to do this correctly because you have the user's context and the regex doesn't.
Privacy + safety contract
- Local-first. No network call on
log,show,report, orpath. Onlysubmit --no-dry-runcan reach a network, and only viagh. - Length-cap + secret-scrub. Free-text inputs are capped at 1000 chars and regex-stripped of GitHub PATs (
ghp_…,github_pat_…,gho_/ghu_/ghs_/ghr_…), AWS access keys (AKIA…), Anthropic (sk-ant-…), Langfuse (pk-lf-…/sk-lf-…), Stripe (sk_live_/sk_test_/rk_/pk_…), generic OpenAI-stylesk-…, Supabase (sbp_…), Sentry (sntrys_…), Google API keys (AIza…), Slack (xoxb-/xoxa-/xoxp-/xoxr-/xoxs-…), Discord bot tokens, JWTs (eyJ…),Bearer …, andAuthorization: …patterns before write. Not exhaustive — don't paste secret-shaped strings into--noteto begin with. - No transcript ingestion. The CLI never reads
~/.claude/projects/...or any session log. Only what the agent passes on the command line is stored. - Skill name is whitelisted.
[A-Za-z0-9._-]+only — prevents path traversal and keeps the JSONL filename predictable.
Design rules (agent-plus patterns)
- Aggregate server-side.
reportdoes the math locally — outcomes histogram, rating average, top friction strings — agent gets one blob, not 200 raw entries. - Stay in your lane. No dashboard, no SaaS, no SDK. JSONL on disk;
ghfor the optional issue write. Ifghisn't there, write the body to disk and tell the user where. --jsonis the default. No human-prose output paths; pipe intojq.- Strip values the agent shouldn't see. Same secret-pattern set as
github-remote's_scrub_text(). - Self-diagnosing output. Every payload has
tool: {name, version}from the manifest.
When NOT to use this — fall back to the underlying CLI
- You want to view or edit a single entry.
skill-feedbackis append-only by design. Usecat,jq, or open the.jsonlfile in an editor. - You want to delete entries. Not exposed by the CLI. Edit the
.jsonldirectly, orrmthe file. (No PII should be in there in the first place — see scrub rules above.) - You want richer analytics. Pipe
report --pretty(or raw.jsonl) intojq/ DuckDB / a notebook. The CLI deliberately ships only the aggregations agents need at the terminal. - You want to file a bug, not a feedback report. Open an issue manually.
submitis for batched feedback, not single-issue triage.
What it doesn't do
Deliberately out of scope for v1:
- No retroactive transcript scraping. The agent has to log explicitly.
- No edit / delete commands — the file is plain
.jsonl, edit it yourself if needed. - No SaaS upload.
submitonly opens a GitHub issue (and only with--no-dry-run). - No automatic re-submission or de-duplication; once an issue is filed, those entries are not marked "submitted" — the user can purge or rotate
.agent-plus/skill-feedback/<skill>.jsonlthemselves.
Example: log → report → submit cycle
# 1. After each skill use the agent logs one line
skill-feedback log linear-remote --rating 4 --outcome success \
--friction "no --from-csv on bulk create"
# 2. The skill author runs report a week later
skill-feedback report --skill linear-remote --since 7d --pretty
# 3. If the signal is strong, bundle and file an issue upstream
skill-feedback submit linear-remote --since 30d # dry-run preview
skill-feedback submit linear-remote --since 30d --no-dry-run # actually file