Skip to content

fix(logs): report real branch names in hook-output JSON; fix tips recipes wt rejects - #4279

Merged
max-sixty merged 4 commits into
mainfrom
nightly/clean-36224276749
Sep 26, 2026
Merged

max-sixty merged 4 commits into
mainfrom
nightly/clean-36224276749

Conversation

@worktrunk-bot

@worktrunk-bot worktrunk-bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Two recipes on the Tips & Patterns page fail when copied. This PR fixes both, adds a test that runs the database recipe straight from the page, and makes the hook-output JSON report real branch names so the log recipes can scope to the current branch.

  • Database per worktree: the set-vars step passed three KEY=VALUE pairs to one wt config state vars set, which accepts exactly one (error: unexpected argument 'port=…' found). Because the step failed, vars.container and vars.port were never set for the db step or pre-remove. The step now runs three vars set calls chained with &&. The recipe also stored db_url, and vars keys have rejected underscores since fix: reject underscore in vars keys (invalid git config variable name) #2285 (Git config variable names can't contain them). It's now db-url, in the vars get example too. fix: use underscores in db_url var key examples #1848 had moved this key from hyphens to underscores because Jinja parses vars.db-url as subtraction. That doesn't apply here: the recipe reads the key only with wt config state vars get db-url, never in a template.
  • Monitor hook logs: wt config state logs get --hook=user:post-start:server and the wtlog alias built on it fail with unexpected argument '--hook', because refactor(config-state): structured JSON for logs; drop --hook/--branch filters #2161 removed that flag. Its replacement, a jq filter over wt config state logs --format=json, couldn't scope to the current branch. The JSON branch field was the sanitized log directory (feature/x became feature-x-<hash>), so the help example used startswith and head -1 and tailed whichever worktree's server log was newest.
  • Hook-output JSON branch: the field now holds the real branch name. It maps each log directory back through sanitize_for_filename over the local branches, and is null when no local branch writes there: a deleted branch, a detached HEAD, or a directory two branches share. The recipes in the help and on the page match [.branch, .source, .hook_type, .name] exactly. The extending page's hook-log alias compared .branch with {{ branch | sanitize_hash }}, the on-disk form, and now passes {{ branch }} to jq with --arg, like its other values. The wtlog alias becomes a shell function taking the hook name.

test_doc_example_database_set_vars_step_runs pulls the set-vars step from the page's TOML block, runs it as a wt alias with wt on PATH, and checks the stored port. It fails on main with the argument error above. After the first fix it also caught the db_url rejection. test_logs_get_json_branch_is_unsanitized covers the branch mapping, including the deleted and shared-directory cases. I ran the new recipe by hand from a feature/x worktree next to an other/y worktree, and it tailed only the feature/x log.

The skills/ and plugins/ copies of the page are regenerated by test_docs_are_in_sync.

The "Database per worktree" set-vars step passed three pairs to one
`wt config state vars set`, which takes a single KEY=VALUE, and stored
`db_url`, a key vars validation rejects since #2285. Split it into three
calls chained with && and rename the key to `db-url`.

"Monitor hook logs" used `wt config state logs get --hook=...`, a flag
removed in #2161. Replace it with the jq filter over
`wt config state logs --format=json` that the command's own help shows.

Add a test that reads the set-vars step from the doc page and runs it
through a wt alias, so a recipe wt rejects fails the suite.
@worktrunk-bot worktrunk-bot added the nightly-cleanup Issues found by nightly code quality sweep label Sep 26, 2026

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The replacement hook-log recipe is no longer scoped to the current branch. The removed wt config state logs get --hook=user:post-start:server resolved the current branch, with --branch as the override. wt config state logs --format=json lists hook_output for every branch's subtree under .git/wt/logs/, sorted newest first. Take a repo where several worktrees each run a server post-start hook: tail -f / wtlog server then follows whichever worktree wrote its log most recently, not the one the user is in. That is the usual setup for the per-worktree dev-server pattern this page describes.

A plain branch filter is harder than it looks, because .branch is the sanitized directory name. A name that isn't already filename-safe gets a hash suffix: feature/x is stored as feature-x-<hash>. So select(.branch == "$(git branch --show-current)") misses those branches, and a startswith on a tr / - form can also match a sibling such as feature-x2. The wt config state logs --help example (src/cli/config.rs, "Path to one hook log (e.g. the post-start server hook for the current branch)") has the same gap and claims current-branch scoping that its jq doesn't do.

A maintainer should choose between two options: document the recipe as picking the newest matching log across all branches, or add a branch filter to the JSON output. The second could be an unsanitized branch value or a current-branch --branch option, which would restore what #2161 dropped. I haven't pushed a fix because this changes the output format.

Comment thread docs/src/content/docs/tips-patterns.md Resolved
`wt config state logs --format=json` reported `branch` as the sanitized
log directory, so `feature/x` read as `feature-x-<hash>` and no filter
could match the current branch. The hook-log recipes fell back to
`startswith` plus `head -1`, which picks the newest log from any
worktree.

Map each directory back through `sanitize_for_filename` over the local
branches. `branch` is now the exact name, or null when no local branch
writes there (deleted, detached, or shared by two branches). The help
and tips recipes filter on `.branch == $branch` and `.name == "server"`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
max-sixty added a commit to max-sixty/tend that referenced this pull request Sep 26, 2026
…s its findings (#1433)

#1203 moved the job of applying a bot-authored PR's findings into the
review run, and removed the `tend-mention` hand-off. The skill still
treated the review as the last step on a self-authored PR, though. The
**Self-authored PRs** paragraph in Submit said "Submit as `COMMENT` when
there are concerns", and it pointed at **Push fixes** only "after
earlier rounds". The rule to apply the findings sat in step 9, after
Monitor CI and thread resolution. On
[max-sixty/worktrunk#4279](max-sixty/worktrunk#4279),
the reviewer wrote that it would "post this as a comment since it's a
self-authored PR", posted, and ended. It named two options, one of them
a docs-only fix, and deferred both because the other needed a new flag.

The pre-flight now decides who the author is and emits `author: self |
bot | human` in place of `self_authored`. `self` is matched against the
bot login, because the bot's own account is a plain user to GitHub.
`bot` is GitHub's `is_bot` flag (Dependabot, renovate). The skill forks
on that field among the step-1 snapshot fields that decide how much of
the workflow runs. On `self` or `bot`, the run is the author: it posts
the review, then applies the findings in one push.

Three wording changes follow from the fork. The self-authored paragraph
now continues into **Push fixes** instead of ending at the `COMMENT`.
The two "offer to push a fix commit" lines now defer to **Push fixes**,
which already branches on the author. Step 9 adds one sentence: an
option that needs maintainer intent doesn't block one that doesn't.

This failure is uncommon. Of 20 recent worktrunk bot PRs with a
self-review, 16 got later commits, and the three I timed landed 17–36s
after the review, from the review run itself. Two cold A/B scenarios on
the worktrunk#4279 state pushed a fix in every run under both the old
and new text (7/7 each). So this change rests on the structural argument
rather than a measured effect.

> _This was written by Claude Code on behalf of max-sixty_

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Compare the fields as one array instead of chaining `and`. The
extending page's `hook-log` alias matched `.branch` against
`{{ branch | sanitize_hash }}`, the on-disk form; `branch` is now the
real name, so it compares `{{ branch }}` directly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@max-sixty max-sixty changed the title docs(tips): fix the database-vars and hook-log recipes that wt rejects fix(logs): report real branch names in hook-output JSON; fix tips recipes wt rejects Sep 26, 2026

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hook-log alias now splices {{ branch }} into a jq string literal inside a single-quoted shell program. Aliases render with POSIX shell escaping, so a branch name that needs quoting ends the single quote early. Git accepts a"b (git check-ref-format --branch 'a"b'), and that branch renders as ["'a"b'", $kind, $name], which is a shell syntax error. Before this change, sanitize_hash rewrote the ", so this is a regression. Pass the branch through --arg like the other values and leave the placeholders unquoted, so wt's escaping yields exactly one argument each. The fix is below, and I'll push it with the regenerated mirrors.

Comment thread docs/src/content/docs/extending.md Resolved
Splicing `{{ branch }}` into the single-quoted jq program broke on a
branch name that wt's shell escaping quotes, such as `a"b` or `it's`.
Each value now reaches jq as its own argument.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@max-sixty
max-sixty merged commit 17b100e into main Sep 26, 2026
38 checks passed
@max-sixty
max-sixty deleted the nightly/clean-36224276749 branch September 26, 2026 17:40
@max-sixty max-sixty mentioned this pull request Sep 27, 2026
max-sixty added a commit that referenced this pull request Sep 27, 2026
Bumps 0.79.0 → 0.80.0 and adds the `## 0.80.0` changelog section, cut
from `12efb5159`. The bump is minor because the library API breaks (see
Verification).

## Verification

- `cargo run -- hook pre-merge --yes` on the cut tip: 4985 passed.
- `nightly.yaml` on `12efb5159`, all jobs green including
feature-powerset:
https://github.com/max-sixty/worktrunk/actions/runs/36338233859
- `cargo semver-checks check-release -p worktrunk` reports five
major-level lints, the removals and signature changes listed in the
Internal entry.
- Changelog: repeated verification passes against source until one came
back clean. Fixed headlines name the condition a bug needed rather than
its worst case, and near-zero-reach fixes are folded into two roll-up
bullets, per #4295.
- Data-loss surface review over `v0.79.0..HEAD` with independent
finders. The one real finding, a nested stale detached worktree read as
the enclosing repo's rebase so `wt step prune` could unregister it, was
fixed in #4302. The review also found a squash regression in the backup
path, fixed in #4303. The remaining candidates were adjudicated
acceptable and signed off: #4228 unregistering stale worktrees whose
directory remains (files stay on disk; the branch is deleted only if
integrated), #4206 leaving a detached `HEAD` on Ctrl-C during a slow
commit hook, #4251 allowing `--no-squash` merges with untracked files,
#4219 dropping `--no-hooks` from the skill example, and #4279's `docker
stop` recipe. #4252 closes a submodule data-loss path.

> _This was written by Claude Code on behalf of max-sixty_

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

nightly-cleanup Issues found by nightly code quality sweep

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants