Skip to content

fix(hooks): stop SessionEnd from mutating tracked STATE.md#17

Merged
lx-0 merged 2 commits intomainfrom
fix/state-md-no-runtime-mutations
May 2, 2026
Merged

fix(hooks): stop SessionEnd from mutating tracked STATE.md#17
lx-0 merged 2 commits intomainfrom
fix/state-md-no-runtime-mutations

Conversation

@lx-0
Copy link
Copy Markdown
Member

@lx-0 lx-0 commented May 2, 2026

Summary

The SessionEnd hook overwrote STATE.md's last_updated frontmatter on every session end via sed -i. STATE.md is a tracked source-of-truth Markdown, written deliberately by skills (init-project, plan-milestone, plan-task, summarize-task, reassess-roadmap, plan-ceo-review) as part of state transitions. A hook firing on every session-end has no business writing to it -- the result was a phantom diff in git status after every session, in every ytstack project.

The same hook already appends to journal/sessions.jsonl (gitignored, append-only), which carries the recency information without polluting the tracked tree.

Behavior change

SessionStart's "Last updated: X" line now reflects the last deliberate skill-driven state transition rather than the last process exit. That's the more useful semantic anyway -- "when was real work last done on this project" beats "when did Claude Code last close a tab".

Breakage audit

Searched the repo for references to last_updated outside the worktree mirrors:

Readers (1):

  • hooks/session-start:110 -- reads field for the "Last updated: X" line in injected context. Display only; no logic branches on the value.

Writers (kept, intentional):

  • skills/init-project/SKILL.md -- seeds the field
  • skills/plan-milestone/SKILL.md, plan-task, summarize-task, reassess-roadmap, plan-ceo-review -- bump on state transitions

Writer removed (this PR):

  • hooks/session-end -- the unsolicited mutation

No tests reference session-end or last_updated (grep across .sh/.bats/.py excluding vendor). Agent Teams orchestration hooks (post-tool-use-bash, task-completed, task-created, teammate-idle) don't touch the field.

Smoke test

$ TMPDIR_TEST=$(mktemp -d); mkdir -p "$TMPDIR_TEST/.ytstack"
$ printf '...frontmatter with last_updated: 2026-01-01...' > "$TMPDIR_TEST/.ytstack/STATE.md"
$ shasum "$TMPDIR_TEST/.ytstack/STATE.md"  # before
384169db9808b0622540c2ea880524db17c5d705
$ CLAUDE_PROJECT_DIR="$TMPDIR_TEST" CLAUDE_SESSION_ID=test-1 bash hooks/session-end
$ shasum "$TMPDIR_TEST/.ytstack/STATE.md"  # after
384169db9808b0622540c2ea880524db17c5d705   # ← unchanged
$ cat "$TMPDIR_TEST/.ytstack/journal/sessions.jsonl"
{"timestamp":"2026-05-02T16:59:39Z","event":"session-end","milestone":"M001","slice":"S01","task":"T01","session_id":"test-1"}

Reporter context

Surfaced from a downstream user whose engine-vault setup clones a repo containing .ytstack/ into a subdirectory: every Claude Code session in the vault was rewriting the engine's dev-tracking STATE.md. The leak (engine .ytstack/ in vault distributions) is a separate concern in the downstream repo; this PR fixes the upstream side that makes the leak visible (the unsolicited write).

Test plan

  • Smoke test: STATE.md sha unchanged before/after hook invocation
  • Journal entry written with correct frontmatter fields
  • Grep audit: no logic depends on last_updated being current
  • Reviewer: run a real Claude Code session in any ytstack project, verify git status is clean after session-end

🤖 Generated with Claude Code


Note

Low Risk
Low risk: removes an unsolicited sed write to tracked STATE.md in hooks/session-end, reducing noisy diffs; behavior change is limited to the last_updated field no longer updating on session exit.

Overview
Prevents hooks/session-end from updating STATE.md’s last_updated on every session end; it now only appends a session-end entry to journal/sessions.jsonl and clarifies this contract in comments.

Bumps plugin metadata version from 0.1.3 to 0.1.4 in .claude-plugin/plugin.json and .claude-plugin/marketplace.json.

Reviewed by Cursor Bugbot for commit cc2b5d8. Bugbot is set up for automated code reviews on this repo. Configure here.

lx-0 added 2 commits May 2, 2026 19:00
The SessionEnd hook overwrote STATE.md's `last_updated` frontmatter on every
session end via `sed -i`. STATE.md is a tracked source-of-truth Markdown,
written deliberately by skills (init-project, plan-milestone, plan-task,
summarize-task, reassess-roadmap, plan-ceo-review) as part of state
transitions. A hook firing on every session-end has no business writing to
it -- the result was a phantom diff in `git status` after every session, in
every ytstack project.

The same hook already appends to `journal/sessions.jsonl` (gitignored,
append-only), which carries the recency information without polluting the
tracked tree. SessionStart's "Last updated:" line now reflects the last
deliberate skill-driven state transition rather than the last process exit
-- which is the more useful semantic anyway.

Smoke-tested: STATE.md sha unchanged before/after hook invocation; journal
entry written correctly with milestone/slice/task/session_id.

Surfaced from a downstream user whose engine-vault setup clones a repo
containing `.ytstack/` into a subdirectory: every Claude Code session in
the vault was rewriting the engine's dev-tracking STATE.md.
Required for `/plugin update` to pick up the SessionEnd fix.
@lx-0 lx-0 merged commit 8020b43 into main May 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant