Appearance
Claude Code Done Marker
A convention for marking Claude Code sessions as "done" with a slash command, so tooling can identify completed sessions by scanning JSONL transcripts.
The problem
Claude Code sessions live as JSONL files under ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl. From the outside, there's no reliable way to know whether a session is finished or merely abandoned mid-task. Saying "thanks" at the end works for humans but is ambiguous for automated detection — "thanks" can appear mid-conversation, and natural-language matching is fragile.
A slash command produces an unambiguous, structurally precise marker.
The convention
The user types /done when finished. This invokes a user-level slash command at ~/.claude/commands/done.md (works in every project) that acknowledges with one line and stops. The command body is intentionally minimal — the detection marker comes from the harness itself, not the command output.
Reference implementation:
markdown
---
description: >
Mark this Claude Code session as complete. Leaves a sentinel in the
JSONL transcript so tooling can detect finished sessions. ONLY runs when
the user explicitly types /done — never auto-invoke from "thanks",
apparent task completion, or any other natural-language cue.
---
The user invoked `/done` to mark this session as complete.
Respond with exactly one line:
✅ Session marked done.
Then stop. Do NOT summarize the conversation, run tools, ask follow-up
questions, or suggest next steps. The user is ending the session — honor
that.A user-level slash command (in ~/.claude/commands/) is preferable to a skill for this case because it's a single file, lives in the semantically correct location for user-typed commands, and doesn't need supporting files like skills do.
How the marker works
When any slash command is invoked, the Claude Code harness writes a user message to the session JSONL whose message.content field starts with the literal string:
<command-name>/done</command-name>The full prefix is:
<command-name>/done</command-name>
<command-message>done</command-message>
<command-args></command-args>This prefix is always at the beginning of the content field — never indented, never inside a code block, never quoted. That structural anchoring lets us distinguish real invocations from textual mentions.
Detecting completed sessions
Naive grep (over-matches)
bash
grep -l '<command-name>/done</command-name>' \
~/.claude/projects/**/*.jsonlThis catches the marker but also matches any message body that quotes the string (for example, this very documentation file would match if it ever landed in a transcript).
Precise grep (recommended)
Anchor the match to the start of the JSON content field:
bash
grep -l '"content":"<command-name>/done</command-name>' \
~/.claude/projects/**/*.jsonlBecause the harness always inserts the marker at the very beginning of the content string, the opening "content":" quote acts as a structural anchor that no body mention can satisfy.
jq alternative
For programmatic use, jq makes the intent explicit:
bash
jq -r '
select(
.type == "user"
and (.message.content | type == "string")
and (.message.content | startswith("<command-name>/done</command-name>"))
)
| .sessionId
' ~/.claude/projects/<project>/<session>.jsonlListing every done session for a project
bash
for f in ~/.claude/projects/<project>/*.jsonl; do
if grep -q '"content":"<command-name>/done</command-name>' "$f"; then
echo "$(basename "$f" .jsonl)"
fi
doneWhy "thanks" isn't enough
A /done slash command beats trailing gratitude because:
- Structural, not semantic. Detection is a single
grep. No NLP, no model call, no false positives from mid-conversation "thanks". - User-explicit. The marker only appears when the user deliberately types
/done. No guessing whether the conversation drifted into pleasantries. - Harness-generated. The sentinel is added by Claude Code itself when the slash command fires — the command body doesn't have to produce it. That means even if the model misbehaves or crashes mid-response, the marker is already in the JSONL.
Caveats
- One per session. The marker is additive, not authoritative. A session can be marked done, then resumed and continued. The marker records "the user said done at this point", not "this session can never grow".
- In-flight transcripts. JSONL writes are flushed periodically. A marker invoked seconds ago may not yet be on disk.
- No marker = unknown, not abandoned. Many sessions simply end without
/done. Absence is not a signal.