Skip to content

fix(#66)(#64): stale-handoff warning + budget onboard's compact-restart output - #74

Merged
jsirish merged 2 commits into
mainfrom
fix/onboard-stale-handoff-and-output-budget
Sep 1, 2026
Merged

fix(#66)(#64): stale-handoff warning + budget onboard's compact-restart output#74
jsirish merged 2 commits into
mainfrom
fix/onboard-stale-handoff-and-output-budget

Conversation

@jsirish

@jsirish jsirish commented Sep 1, 2026

Copy link
Copy Markdown
Member

Summary

Both fixes touch hooks/session-onboard.sh and came out of a competitive read of
adrrr/persistent-handoff (see the linked issues).

#66 - stale-handoff detection. Every handoff-file design shares one blind spot by
construction: the file only reflects what was written, so it can look current while
work has moved on without it (this project's own HANDOFF.md went stale 5+ weeks this
way). session-onboard.sh already reads the Last Updated line and already shells
out to git - comparing the two now warns when HANDOFF.md sits 14+ days behind the
branch's latest commit. Uses a portable civil-calendar day-count in awk rather than
date -d/date -j (GNU vs. BSD, and eventually Windows Git Bash per #67).

#64 - output-size budgeting. A realistic worst case (a 30-line near-max-width
buffer tail, a normal HANDOFF.md, ~20 untracked files) measured at 11,026 characters -
over the ~10,000-char cap a competing plugin claims Claude Code enforces on
SessionStart output (Claude Code's own docs specify no limit at all, so the exact
number is unverified, but an unbounded worst case is worth budgeting against
regardless). Fix: shrunk TL_COMPACT_TAIL_LINES/TL_COMPACT_TAIL_LINE_CHARS from
30/300 to 20/200, and moved the buffer-tail inline to render LAST in the script -
after live git state and the unconsumed-buffer scan, both small and bounded with no
fallback of their own. If truncation happens, it now eats into the one block that
already tells you where to find the rest (session-<id>.md), not into orientation
content that has no such pointer. Same fixture now measures 5,818 characters.

Test plan

  • sh tests/run.sh: 171/171 pass (167 baseline + 4 new: stale-flagged,
    not-flagged-same-day, not-repeated-on-compact, git-state-precedes-tail-inline;
    1 existing assertion updated for the new 20-line tail bound)
  • shellcheck -s sh hooks/*.sh tests/*.sh (CI's exact command): clean
  • local-ci.sh --strict: all 5 checks PASS
  • Reproduced the exact worst-case fixture from the original finding (30-line
    near-max-width buffer + 20 untracked files) before and after: 11,026 -> 5,818
    characters, with ### Live git state confirmed present and preceding the
    🧷 Context was just compacted block in the actual hook output
  • Verified the day-count awk function against a same-month gap, a year boundary,
    and a Feb 29 leap-year case before landing it

Closes #66
Closes #64

jsirish and others added 2 commits September 1, 2026 11:07
Every handoff-file design shares one blind spot by construction: the file
only reflects what was written, so it can look current while work has moved
on without it. This project's own HANDOFF.md went 5+ weeks stale this way
(nested-workspace gap). session-onboard.sh already reads HANDOFF.md's "Last
Updated" line and already shells out to git for live state - comparing the
two turns the silent failure into a warning.

Compares HANDOFF.md's Last-Updated date against the branch's latest commit
date via a portable civil-calendar day-count in awk (no `date -d`/`date -j`,
which differ between GNU and BSD date and would need separate handling for
Windows Git Bash per #67). Warns past a 14-day gap; silent when the gap is
smaller, absent, or negative (an in-flight, uncommitted-state handoff ahead
of the last commit is normal). Skipped on a `compact` re-fire, matching the
gitignore nudge's own reasoning.

fix(#64): budget session-onboard.sh's compact-restart output

A realistic (not pathological) compact-restart scenario - a 30-line
near-max-width buffer tail plus a normal HANDOFF.md and ~20 untracked files -
measured at 11,026 characters, over the undocumented ~10,000-character cap a
competing plugin (adrrr/persistent-handoff) claims Claude Code enforces on
SessionStart output. Claude Code's own docs specify no limit at all, so the
exact cutoff is unverified, but an unbounded worst case is worth budgeting
against regardless.

Two changes: TL_COMPACT_TAIL_LINES/TL_COMPACT_TAIL_LINE_CHARS shrunk from
30/300 to 20/200, and the buffer-tail inline (the one block with no fallback
pointer... wait, it has one - "full history is at <path>" - unlike live git
state, which has none) now renders LAST in the script instead of before the
unconsumed-buffer scan and live git state. If truncation happens at all, it
now eats into the block that already tells you where to find the rest, not
into small bounded orientation content with no such fallback.

Same worst-case fixture measured at 5,818 characters after both changes -
roughly half - with live git state confirmed intact and preceding the tail
inline.

Adds 5 new assertions to tests/run.sh (167 -> 171 passing) and updates one
existing assertion for the new tail bound (30 -> 20 lines).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXL2ffVDsAp4P7387Nr18G
…rbage

/review-pr on PR #74 diffed the days_from_civil awk function against
Python's calendar for every date from 1899-01-01 to 2100-12-31 (73,779
dates, zero mismatches) - the arithmetic itself was right - but found the
regex extraction only validated that hf_date/commit_date were digit-shaped,
not that they were real calendar dates. A hand-edited "**Last Updated:**
2026-00-00" computed a confident wrong answer ("275 day(s) before") instead
of the "say nothing when unparseable" this check already promises elsewhere.
days() now range-checks month/day/leap-February itself and sets a `bad` flag
rather than returning a number for garbage input.

Also (same review): `head -20` on `git status -s` bounds live git state's
LINE COUNT but not each line's WIDTH - one modified tracked file's path can
be arbitrarily long regardless of `head`, and a 20-modified-file monorepo
fixture with ~8-segment paths measured over 2KB with nothing bounding it.
Since issue #64 moved this block to render first specifically because it
was "small and bounded", it needed the same per-line truncation discipline
the buffer-tail inline already has. Added TL_GIT_STATUS_LINE_CHARS=200 with
the same awk truncation pattern.

Also updates CHANGELOG.md (this PR and the already-merged #73 docs/demo/
Related-work PR both landed without an entry) and adds one line to
README.md's capture-point table describing the new stale-handoff warning.

3 new test cases (171 -> 174 passing).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXL2ffVDsAp4P7387Nr18G
@jsirish
jsirish merged commit 9587711 into main Sep 1, 2026
2 checks passed
@jsirish
jsirish deleted the fix/onboard-stale-handoff-and-output-budget branch September 1, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant