Generated report
Dev Log: 2026-05-28
There's a particular flavor of bug that only shows up after you ship. You cut the release, you run timbers ack on the next commit, and... the hook refuses. The ack file is staged but not committed. The error message says "failed to commit ack file" and nothing else. You stare at it.
That was v0.22.3 landing on my own machine. The culprit, once I dug in, was embarrassingly mundane: a stale dev binary sitting in mise's GOBIN was first on PATH, shadowing the fresh ~/.local/bin install. The hook was happily running a pre-v0.22.0 timbers that didn't know what an ack was, while my shell — different lookup order — kept reporting the new version. Two timbers, one repo, silent disagreement.
So doctor learned a new trick: walk PATH, dedupe by resolved path, and warn when the first timbers reports a different version than a shadowed sibling. The compare is on the version token only, not the full --version string, because two builds of the same version with different commit suffixes shouldn't false-warn. And WriteAck's failure path now explains what actually happened — staged but uncommitted, here's the upgrade path, here's git commit to recover — instead of the one-line shrug it used to give.
The broader theme this week was making the ledger explain itself before it traps you. A handful of small surfaces, all in the same key.
The .timbersignore file existed, but you had to know it existed. An agent working in the repo had to research whether author-matching was a thing, and could easily write author:dependabot[bot] — which looks right and silently matches nothing, because [bot] parses as a character class. So: pending now hints at .timbersignore when commits stack up, --explain classifies every commit including skipped ones, help timbersignore documents the three rule types with a canonical bot recipe, and doctor lints for the [..] footgun. The trap became a one-liner.
While in there, I added msg: rules alongside author: and path: — a commit-subject glob. The trigger was concrete: release changelog commits touch CHANGELOG.md and the version-badge source in site/. Path-skipping the badge file would silently hide real landing-page edits later. A subject glob (msg:chore: changelog for v*) is the precise tool. The refactor underneath was nicer than expected — filterByRules now reuses the same classifyByIdentity path the debug trace uses, so skip semantics live in one place.
The log --anchor flag had been quietly lying. Its name promises "use this anchor," but at zero detected pending commits it refused anyway — an off-first-parent gap that a downstream project flagged a while back. The fix honors the promise: when pending is empty and --anchor is set explicitly, fall back to a single-commit range from anchor^..anchor. The refusal message, when it does fire, now names --range as the explicit escape hatch instead of leaving you to guess. pending got the matching treatment: at count zero with an off-first-parent anchor, it says so, instead of conflating clean with computed from a sideways anchor.
Then there was the ack-for-rebase question. Someone filed a thoughtful five-proposal feature request asking timbers to auto-relink entries across rebases via content matching. I sat with it. The honest read: timbers has no content matching today — docSet is literal SHAs — so the requested feature is built-from-scratch, and the naive form (byte-identical diffs, reflog lookup) is unsound. Rebases shift context lines. Reflog is absent on fresh clones and CI. The sound version needs patch-id stored at log time, which is a schema change.
Meanwhile, ack already satisfies the underlying requirement: a structured record that counts as documented. So the cheapest correct move was documenting that path — a new RebaseRelinkGuidance section wired into both the prime workflow and the MCP composer, sharper ack hints with a copy-pasteable rebased; content in <entry-id> form, and a protocol test to lock it. Gall's Law: ship the doc, graduate to a typed ack --to <entry> only if the free-text link starts biting, build the patch-id machinery only with real volume evidence.
The pattern across all of this: the system already had the lever. It just wasn't visible.
The release pipeline got a small but satisfying tightening too. just release now polls the GH release endpoint after tagging and, once assets appear, runs install-release to pull the published binary down as a normal consumer would. That sanity-checks the whole pipeline — CI build, GH release, install script — and keeps the local binary current without a manual follow-up. And the site-example regeneration step, which had once aborted an entire version release because a parallel claude -p flaked, is now non-fatal. A transient LLM hiccup shouldn't block a tag.
One bigger move in the background: beads migrated from server-mode Dolt to embedded, matching the canonical model already validated in downstream use. The repo had drifted into a broken hybrid — server Dolt couldn't persist under the sandbox, a stale refs/dolt/data from three weeks ago coexisted with steering docs claiming there was no remote. JSONL was the only current source of truth, so rebuild-from-JSONL was both safe and the only coherent path. Fresh-clone bootstrap round-trip verified. Not glamorous; necessary.
If there's a single thread tying the week together, it's surfacing the lever before someone falls into the trap. A stale shadowed binary, a no-op glob, a flag that doesn't honor its own name, a rebase that orphans an entry — each one had a fix in the system already, or a fix that fit naturally. The work was making the system say so.
It feels like solid ground. Not finished — the patch-id question is still out there if free-text rebase links start hurting, and the binary-shadowing check only catches PATH-order divergence, not the dozen other ways two timbers can disagree. But the surfaces that bit me this week now explain themselves to the next person, including future-me.
Written with AI assistance from session devlog entries.