Skip to content

Journal: slice/task always "none" (STATE.md frontmatter drift) + session_id always "unknown" in session-end hook #21

Description

@lx-0

Summary

In a long-running project, journal/sessions.jsonl records "slice":"none" and "task":"none" on nearly every session-end entry, even while real slices/tasks were active. session_id is also always "unknown". The SessionEnd hook is faithful — the bug is upstream: the lifecycle skills don't keep active_slice/active_task in STATE.md frontmatter in sync, and the hook reads session_id from the wrong source.

Evidence (real project, ~12 entries)

{"timestamp":"2026-05-30T23:08:27Z","event":"session-end","milestone":"M004","slice":"none","task":"none","session_id":"unknown"}
{"timestamp":"2026-05-31T00:41:28Z","event":"session-end","milestone":"M005","slice":"none","task":"none","session_id":"unknown"}

milestone tracks correctly; slice/task are stuck at none.

Root cause #1 — STATE.md frontmatter drift (primary)

hooks/session-end reads three frontmatter fields verbatim:

get_field() { sed -n "s/^$1: *//p" "$STATE_FILE" 2>/dev/null | head -1; }
CURRENT_MILESTONE=$(get_field current_milestone)
ACTIVE_SLICE=$(get_field active_slice)
ACTIVE_TASK=$(get_field active_task)

The hook is correct. But git shows the skills only reliably advance current_milestoneactive_slice/active_task are left at the literal string none:

Commit (era) current_milestone active_slice active_task
init none none none
M002 M002 S05 none
M005 M005 none none
M006 (now) M006 S01 T01

So at M005 a slice was clearly active, but the frontmatter said none, and the journal dutifully recorded none. The :-none fallback in the hook hides nothing here — the field literally contained none.

Fix: plan-task, slice-milestone, and summarize-task must write back active_slice/active_task to STATE.md frontmatter as part of their state transitions (set on entry, clear to none only when genuinely idle). This is the same SSOT discipline already applied to current_milestone.

Root cause #2 — session_id always "unknown" (secondary)

--arg session_id "${CLAUDE_SESSION_ID:-unknown}"

CLAUDE_SESSION_ID is not part of the hook environment. Claude Code delivers session_id (and cwd, hook_event_name, etc.) as a JSON payload on stdin, not as an env var. The hook should parse stdin, e.g.:

INPUT=$(cat)
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // "unknown"')

As written, every entry will read unknown.

Suggested acceptance

  • A session ended mid-task records the actual S##/T## in sessions.jsonl.
  • session_id reflects the real Claude Code session id.
  • Skills that change slice/task state update the STATE.md frontmatter in the same step.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions