Files
superpowers/skills/diagnosing-superpowers/references/claude-code-sessions.md
T
Jesse Vincent affa7fa4e2 feat: add diagnosing-superpowers skill
Evidence-based diagnosis of superpowers sessions: intake with the human
partner, safe transcript reading for Claude Code and Codex (discovery
procedure for other harnesses), seven analyst subagents, a report with
path:line evidence and a bounded superpowers-involvement line, scrubbed
export bundles, approval-gated GitHub issue search/draft, and
similar-session search. Includes spec, plan, structure test, and README
and docs index lines.

Developed RED-GREEN-REFACTOR per writing-skills: 46 scored scenario runs
across five SKILL.md versions, all twelve scenarios clean against the
final version, micro-tests control 5/5 to skill 0/5 on both
baseline-failing prohibitions, and one end-to-end run. Eval records are
kept by the maintainer outside the repo.

Claude-Session: https://claude.ai/code/session_01DyaGKhTXvHNs2JgPhDktz7
2026-08-31 10:03:57 -07:00

7.4 KiB

Claude Code session store

Verified against: Claude Code 2.1.247 (transcript version field), macOS. When a field below is missing from the file in front of you, trust the file and say so in coverage notes.

Where

  • Main transcript: ~/.claude/projects/<cwd-slug>/<sessionId>.jsonl, where <cwd-slug> is the working directory with every / replaced by - (e.g. /tmp/work-tmp-work).
  • Subagent transcripts: ~/.claude/projects/<cwd-slug>/<sessionId>/subagents/agent-<agentId>.jsonl, each with a sibling agent-<agentId>.meta.json (agentType, description, toolUseId, spawnDepth, optional model).
  • Plugin registry: ~/.claude/plugins/installed_plugins.json — per plugin: installPath, version, installedAt, lastUpdated, gitCommitSha.
  • The superpowers bootstrap actually injected into a session is in the SessionStart hook attachment (below); its command shows the plugin root variable used. A dev checkout loaded with --plugin-dir will not be in the registry, so report both the registry entry and the hook evidence.

Which file is the current session

The most recently modified .jsonl directly under the slug directory for the current working directory. Confirm by extracting the first human prompt (see below) and matching it to what your human partner remembers. If two files are close in mtime, show both first prompts and ask.

Line types

Every line is one JSON object. type values seen: user, assistant, attachment, system, plus session-level records (permission-mode, mode, bridge-session, last-prompt, ai-title, atis-latch, pr-link, queue-operation, relocated, worktree-state).

Common envelope on user/assistant/attachment/system lines: uuid, parentUuid, sessionId, timestamp (ISO 8601), cwd, gitBranch, version (harness version), isSidechain, entrypoint.

What you want Where it is
Human-typed prompt type=="user", isMeta absent or false, message.content is a string or a list whose first block is type:"text". Lines whose first block is tool_result are tool results, not prompts. <system-reminder> text inside a prompt is injected, not typed. Text beginning with <task-notification>, <command-name>, <local-command-stdout>, <system-reminder>, or This session is being continued from a previous conversation is harness-injected too, even though isMeta is absent on those lines — exclude them or your human-turn count will be several times too high.
Human-typed prompt queued mid-turn type=="attachment", attachment.type=="queued_command", attachment.origin.kind=="human", text in attachment.prompt. These are typed while a turn is running and never appear as standalone user lines, so they are missing from the list above. Add them to the timeline.
Assistant text / tool calls type=="assistant", message.content[] blocks of type:"text" or type:"tool_use" (id, name, input).
Tool result type=="user", message.content[0].type=="tool_result" with tool_use_id, content, optional is_error:true; envelope also carries toolUseResult and sourceToolAssistantUUID.
Model message.model on assistant lines.
Tokens message.usage on assistant lines: input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens.
Skill invocation tool_use block with name:"Skill" and input.skill (e.g. superpowers:brainstorming); the tool result line has toolUseResult.commandName.
Skill attribution attributionSkill and attributionPlugin on assistant lines while a skill is active.
Subagent dispatch tool_use with name:"Agent" (input.description, input.subagent_type, input.prompt); the subagent's own file is matched by toolUseId in its .meta.json. Subagent lines have isSidechain:true and agentId.
Hook output type=="attachment", attachment.type hook_success/hook_failure, attachment.hookName (e.g. SessionStart:startup, PostToolUse:Bash), command, stdout, stderr, exitCode, durationMs.
Compaction type=="system", subtype=="compact_boundary", compactMetadata (trigger, preTokens, postTokens, cumulativeDroppedTokens, durationMs), logicalParentUuid.
Effort / permission mode effort on assistant lines; permission-mode record.

Safe extraction

Lines can exceed a megabyte. Never print a whole line. Check size first:

F=~/.claude/projects/<slug>/<id>.jsonl
wc -lc "$F"
awk '{ if (length($0) > 100000) print NR, length($0) }' "$F"   # long lines

With jq (preferred):

jq -r '.type' "$F" | sort | uniq -c                                    # line-type census
jq -r 'select(.type=="user" and .isMeta!=true and ((.message.content|type)=="string" or .message.content[0].type=="text"))
       | select((.message.content|if type=="string" then . else (.[0].text // "") end)
                | test("^(<task-notification>|<command-name>|<local-command-stdout>|<system-reminder>|This session is being continued)") | not)
       | "\(input_line_number)\t\(.timestamp)\t\((.message.content|if type=="string" then . else .[0].text end)[0:160])"' "$F"   # human prompts
jq -r 'select(.type=="attachment" and .attachment.type=="queued_command" and .attachment.origin.kind=="human")
       | "\(input_line_number)\t\(.timestamp)\t\(.attachment.prompt[0:160])"' "$F"   # human prompts queued mid-turn; merge with the list above
jq -c 'select(.type=="assistant") | .message.content[]? | select(.type=="tool_use")
       | {name, id, input: (.input|tostring|.[0:120])}' "$F"           # tool calls
jq -r 'select(.type=="assistant") | .message.content[]? | select(.type=="tool_use" and .name=="Skill") | .input.skill' "$F"   # skill invocations
jq -c 'select(.type=="assistant") | {ts:.timestamp, model:.message.model, skill:.attributionSkill,
       u:(.message.usage|{input_tokens,output_tokens,cache_read_input_tokens,cache_creation_input_tokens})}' "$F"   # per-message usage
jq -c 'select(.subtype=="compact_boundary") | {line:input_line_number, ts:.timestamp,
       m:(.compactMetadata|{trigger,preTokens,postTokens,cumulativeDroppedTokens,durationMs})}' "$F"   # compactions (full compactMetadata also has UUID lists; keep this trimmed)
jq -c 'select(.type=="attachment" and (.attachment.type|startswith("hook"))) | {line:input_line_number, hook:.attachment.hookName, exit:.attachment.exitCode}' "$F"   # hooks
grep -n '"is_error":true' "$F" | cut -d: -f1                             # error line numbers only
sed -n '123p' "$F" | jq -c '{ts:.timestamp, first:((.message.content // "") as $c
       | ($c | if type=="array" then ($c[0] // "") else $c end) | tostring | .[0:400])}'   # one line, trimmed (content is sometimes a bare string, sometimes absent)

Without jq, the same with python3 (one line per record, print only what you asked for):

python3 -c 'import json,sys
for n,l in enumerate(open(sys.argv[1]),1):
    o=json.loads(l)
    if o.get("type")=="assistant":
        for b in o["message"].get("content",[]):
            if b.get("type")=="tool_use": print(n, b["name"], str(b.get("input"))[:120])' "$F"

Subagents

List ~/.claude/projects/<slug>/<id>/subagents/. For each agent-*.meta.json print agentType, description, model; the matching .jsonl is that subagent's transcript and follows the same line format. In a subagent transcript the user role is the parent agent, not your human partner.