Skip to main content

Scanning & Discovery

The scanner automatically discovers lessons from your session history. It runs in the background on every session startup and writes candidates to the database for your review.


How scanning works

Claude Code writes every session to a JSONL file in ~/.claude/projects/. Each line is a JSON object — a user message, an assistant response, a tool call, or a tool result.

The scanner reads these files and looks for structured tags and heuristic patterns across four tiers:


Background scan

On every session startup, session-start-scan.mjs fires and spawns lessons.mjs scan --auto as a detached background process. The parent process unrefs the child immediately so session startup is not delayed.

The scan:

  1. Reads saved byte offsets from data/scan-state.json to resume where it left off
  2. Processes only new bytes in each JSONL file (incremental)
  3. Writes new candidates to the database with status='candidate'
  4. Updates data/scan-state.json with new offsets

The background scan respects autoScanIntervalHours — if the last scan was recent, it exits early.

Running a scan manually

The background scan already runs automatically on session startup, so you rarely need to trigger one yourself. If you want to force a fresh scan before reviewing, run:

/lessons:review

/lessons:review runs the scanner and walks you through whatever it finds — see Interactive review below.

CLI equivalent

node scripts/lessons.mjs scan # incremental scan, interactive
node scripts/lessons.mjs scan --auto # non-interactive (background mode)
node scripts/lessons.mjs scan --full # reset offsets, re-scan everything
node scripts/lessons.mjs scan --dry-run # show what would be found, don't write

Tier 1 — Structured tags

When Claude makes and corrects a mistake during a session, it emits a #lesson tag:

#lesson
tool: Bash
trigger: git stash
problem: git stash only stashes tracked files — untracked files silently left behind
solution: Use `git stash -u` to include untracked files
tags: tool:git, severity:data-loss
#/lesson

The scanner greps for #lesson markers in assistant message lines, parses the block, and extracts a structured candidate with:

  • tool, trigger, problem, solution, tags from the tag
  • confidence scored based on how many optional fields are present
  • sessionId and messageId for provenance

Tier 1 candidates auto-promote during interactive scan if they pass validation:

  • problem and solution each ≥ 20 chars
  • No template placeholders
  • Jaccard similarity vs existing lessons < 0.5

If they fail validation, they appear in the review queue for manual adjustment.


Tier 2 — Heuristic detection

For sessions where Claude didn't emit #lesson tags, the heuristic detector applies a sliding window over the JSONL stream looking for:

  1. A tool result that contains error signals (non-zero exit, Error:, ECONNREFUSED, etc.)
  2. An assistant response that follows with a correction (contains instead, should, fix, use, etc.)

When both conditions match within a configurable window, the detector extracts a candidate with:

  • problem derived from the error message
  • solution derived from the correction
  • confidence in the 0.4–0.6 range (lower than Tier 1) — always requires human confirmation before it's promoted

Tier 2 candidates always require manual review before promotion. They're noisier and need a summary, trigger pattern, and confirmation that the mistake is real and reusable.


Tier 3 — Structural detection

Tier 3 captures insight announcements — structured patterns like "I see that...", "The issue is...", or explicit problem/solution headers in assistant responses. These are written to pending_semantic_windows in the DB and surfaced for manual review. They do not auto-promote.


Tier 4 — LLM deep scan

Tier 4 runs a full-session LLM analysis using the Anthropic API to extract candidates that none of the pattern-based tiers would catch. This tier requires ANTHROPIC_API_KEY to be set in the environment before Claude Code starts.

Tier 4 requires ANTHROPIC_API_KEY

The session-start-scan.mjs hook only launches the deep-scan background job if ANTHROPIC_API_KEY is set. Without it, Tiers 1–3 still run normally.

Setting up the API key

Two options — use whichever fits your setup:

Option A — environment variable (key available system-wide):

export ANTHROPIC_API_KEY=sk-ant-...

Add to your shell profile (~/.zshrc, ~/.bashrc) so it's set before Claude Code starts.

Option B — data/.api-key file (scoped to this plugin only, gitignored):

echo "sk-ant-..." > ~/lessons-learned/data/.api-key

The session-start hook reads this file and injects the key into the scan subprocess environment. It does not affect any other process. The file is listed in .gitignore and will not be committed.


Viewing candidates

/lessons:manage → "show pending candidates"
/lessons:review

CLI equivalent

node scripts/lessons.mjs scan aggregate # ranked JSON view of all candidates
node scripts/lessons.mjs list --status candidate # all candidates

scan aggregate (formerly scan candidates, now a deprecated alias for the same thing) ranks every candidate by sessionCount * projectCount * confidence — it doesn't filter by session count, so single-occurrence candidates still appear, just lower in the list.


Promoting candidates

/lessons:review is the recommended way to promote both Tier 1 and Tier 2 candidates — see Interactive review below. The CLI equivalents in this section exist for scripting and local development.

Tier 1 candidates

Auto-promoted during /lessons:review if they pass validation. Claude shows you a preview and you confirm or skip.

CLI equivalent

node scripts/lessons.mjs scan

Tier 2 candidates

Require manual review. /lessons:review prompts you for problem, solution, and trigger before promoting.

CLI equivalent

List candidates to find the id you want, then promote by ID — scan promote <index> has been removed since indexes are recomputed on every listing:

node scripts/lessons.mjs scan aggregate # list candidates (includes each one's id)
node scripts/lessons.mjs promote --ids <id> # promote that candidate

To adjust fields at promotion time, pass --patch:

node scripts/lessons.mjs promote --ids <id> --patch '{"priority": 8, "tags": ["tool:git"]}'

After promotion, the manifest is rebuilt automatically.

Interactive review

/lessons:review

Walks through all pending candidates (Tier 1 and Tier 2) one at a time with guided prompts. It's the recommended way to review a batch of candidates.


Scan state

The scanner tracks progress in data/scan-state.json:

{
"files": {
"/Users/alice/.claude/projects/abc123.jsonl": 184320
},
"lastFullScanAt": "2026-04-01T00:00:00Z"
}

Each file entry is the last byte offset read. The next scan resumes from that point, processing only new content. This makes incremental scans fast even on large session archives.

scan-state.json is managed by the scanner — don't edit it directly. To force a full re-scan:

node scripts/lessons.mjs scan --full

Configuring scan paths

By default, the scanner reads ~/.claude/projects/. If your session files are elsewhere:

{
"scanPaths": ["~/.claude/projects/", "/path/to/other/agent/sessions/"]
}

Edit data/config.json to add directories. Tilde expansion is supported.


Scan frequency

The autoScanIntervalHours setting (default: 24) prevents the background scan from running on every session startup. A manual scan always runs regardless of this setting.

To scan on every startup:

{
"autoScanIntervalHours": 0
}

To scan weekly:

{
"autoScanIntervalHours": 168
}