The stale anchor bug was one of those embarrassing gaps where the system claimed to be self-healing but quietly wasn’t. timbers pending, timbers prime, and the MCP path all handled ErrStaleAnchor gracefully — if a squash merge made the anchor commit unreachable, they’d accept the fallback commits from GetPendingCommits and carry on. But timbers log and timbers batch log? They treated it as fatal. The exact commands you’d reach for after seeing the “you have pending commits” nudge would just… blow up. Accepting the fallback and warning instead of dying was a small fix scattered across a handful of files, but the test proving it works is worth more than the fix itself.
That bug surfaced while working on something bigger: making the onboarding experience not feel like a trap. Fresh repos — or repos that existed long before timbers showed up — have no entries. GetPendingCommits dutifully returns every commit in history as “pending,” which is technically correct and completely useless. Nobody wants to write a devlog entry covering six months of pre-timbers work.
The original plan was to make
GetPendingCommitsreturn empty when there are no entries. Clean, obvious. Broke everything.
timbers log, timbers batch log, MCP — they all need those commits to create the first entry. So the fix moved to the display layer instead. pending.go and doctor_checks.go now check latest==nil and surface a friendly “you’re starting fresh” message with a catchup tip, while storage behavior stays completely unchanged underneath. The right abstraction boundary, found the hard way.
The largest chunk of work this cycle was the post-commit git hook. The motivation is specific: git hook stdout is visible to AI agents. Other hook mechanisms (like Claude Code’s PostToolUse) aren’t reliably surfaced. A post-commit hook printing a one-line reminder to run timbers log is the most dependable nudge mechanism available. timbers hook run post-commit prints the reminder, timbers init --hooks installs it, and timbers doctor checks for it. The --fix flag on doctor auto-installs it too, so the whole chain is self-repairing.
Review caught a few things in the hook code — a slice capacity off by one (5→6, the kind of thing that’s fine until it isn’t), a missing IsRepo guard, and zero test coverage on the new setup functions. All addressed with proper unit and integration tests for Generate, Check, Install, and the init --hooks flow.
With the hook in place, it made sense to wire health checking into timbers prime — the command agents run at session start. runQuickHealthCheck now verifies the post-commit hook exists and the agent environment is configured, outputting a Health section with a doctor --fix hint when something’s off. Agents see the problem and the fix before they write a single line of code. That’s the whole point: surface the issue where the user already is, not in some status command they’ll never think to run.
Generated with AI assistance.