Skip to content

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/**/*.jsonl

This 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).

Anchor the match to the start of the JSON content field:

bash
grep -l '"content":"<command-name>/done</command-name>' \
  ~/.claude/projects/**/*.jsonl

Because 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>.jsonl

Listing 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
done

Why "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.