refactor(perf): canonicalize the benchmark / wt-perf system#3403
Merged
Conversation
Design proposal (review-only, per design/ convention). Inventories the five perf-tooling layers and proposes seven consolidations: delete the cache-check subcommand (subsumed by logs profile --format=json), move the timeline renderer into worktrunk::trace, add by_context to Profile and retire the trace_processor SQL section, share one warm/cold bench runner, prune the bench matrix (~15-20 min/day of CI), move the wt-perf CLI tests in-package, and unify the setup config parser. Emission, chrome.rs, the separate wt-perf package, and fixture shapes stay as is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements design/simplify-bench-perf.md (items 1-7): 1. Delete wt-perf cache-check — its output is the .cache field of wt config state logs profile --format=json. 2. Move the timeline renderer into worktrunk::trace, sharing command_label/render_table/fmt_dur with the profile so the two views can't drift (drops wt-perf's tabwriter and insta deps; timeline durations now render in the profile's fixed-point format). 3. Add by_context to Profile (BY CONTEXT table + JSON array): per-worktree subprocess totals, the one analysis that previously required trace_processor SQL. benches/CLAUDE.md's SQL section becomes a pointer to logs profile + Perfetto; the running-tend statusline recipe now uses logs profile too. 4. One warm/cold bench runner (wt_perf::bench_wt) owns the BatchSize::PerIteration rationale once; five inline copies deleted. All runs now assert child exit status (list.rs previously ignored it). 5. Prune the matrix: real_repo keeps only the 8-worktree variants, cow_copy is deleted (shadow serial impl validating a settled choice), and the remove_e2e/first_output arm (duplicate of first_output/remove) is deleted. Ends three gist series; saves ~15-20 min/day of bench CI. 6. Move the wt-perf CLI tests in-package (native CARGO_BIN_EXE_wt-perf), deleting the dummy builds.rs and dropping wt-perf from the nextest setup script. 7. parse_config returns a SetupConfig enum covering mixed-W-B; shared init_bench_repo plumbing between the two fixture builders. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From two review passes over the branch: - CacheReport shrinks to its same-context fields. The six dropped fields (total_commands, total_time, contexts, duplicated_commands, extra_calls, unique_commands) duplicated Profile fields or tallied cross-context repeats — expected fan-out noise the text report never showed. Their only external reader was wt-perf cache-check, deleted earlier on this branch. - src/trace/mod.rs docstring cut over to the module map + logs profile pointer; the trace_processor SQL examples it carried were retired from benches/CLAUDE.md earlier on this branch for the same reason. - Chrome args drop duration_ms (redundant with the native dur field). - render_table pads by display width (unicode-width, already a direct dep), replacing the ASCII-only invariant that the new BY CONTEXT table violated; CJK alignment test added. - Timeline instant-event row gains coverage (the one render path no test exercised); stale /main suffix fixed in benches/CLAUDE.md's invalidate example. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Main's diagnostic-section addition calls the per-render cache analysis 'a separate tool', pointing at the wt-perf cache-check recipe — which this branch already cut over to wt config state logs profile. Reword: it's the same report against a statusline capture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per the design/ convention, proposals guide implementation PRs but don't land on main. The doc remains in this branch's history (b22aced) and its content is implemented by the commits above. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # tests/helpers/wt-perf/src/lib.rs # tests/helpers/wt-perf/src/main.rs
worktrunk-bot
left a comment
Collaborator
There was a problem hiding this comment.
Reviewing as a draft — flagging anything that looks worth a quick fix. Mark ready for a full review.
The refactor is clean and internally consistent — the by_context aggregation, the moved traced/wall timeline computation, the SetupConfig enum, and the bench_wt warm/cold strategy all preserve the prior behavior, and I found no correctness issues. Two documentation-accuracy nits:
benches/CLAUDE.md(expected-numbers table, theremove_e2e/first_outputrow) — this row (~86 ms, "single-target validation up to first output") documents the bench variant this PR deletes frombenches/remove.rs. It's outside this diff's hunks so I can't attach an inline suggestion; either drop the row or repoint it at the survivingfirst_output/removevariant intime_to_first_output.rs.
Inline suggestion below for the other one.
The profile module spec referenced a nonexistent `wt diagnose` command (the consumer is the -vv diagnostic report), and the expected-numbers table still documented the deleted remove_e2e/first_output variant — repointed at the surviving first_output/remove. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
max-sixty
marked this pull request as ready for review
July 10, 2026 09:54
worktrunk-bot
approved these changes
Jul 10, 2026
Merged
max-sixty
added a commit
that referenced
this pull request
Jul 10, 2026
Release v0.67.0: version bump and changelog. Highlights since v0.66.0: experimental `wt remove --reap` (#3396), `wt switch -x` opening the picker (#3394), the termimad panic containment in the picker's PR-comments preview (#3408), the `wt config show` SIGTTOU fix (#3327), the `GIT_*` discovery-var scrub for `wt step for-each` and the `--execute` fallback (#3400), and the `wt list` prompt-reserve fix (#3409). Seven commits landed on `main` after the initial cut; `origin/main` is merged back in and the two user-facing ones (#3394, #3411) plus the BY CONTEXT profile table (#3403) are folded into the changelog. Pre-release validation: local pre-merge gate green on the merged tree (4389 tests); nightly cross-platform suite green on the initial cut ([run 29086956095](https://github.com/max-sixty/worktrunk/actions/runs/29086956095)), with the seven post-cut commits each validated by their own PR CI; `cargo semver-checks` reports no breaking changes; data-loss surface review of the cumulative diff (including the drift commits) found one new destructive capability (`--reap`, explicit opt-in, adjudicated acceptable). > _This was written by Claude Code on behalf of max_
Skogix
added a commit
to skogai/worktrunk
that referenced
this pull request
Jul 11, 2026
commit 550d6641b6e1888b8a0712b637308ee00639e3ff
Author: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
Date: Sat Jul 11 03:04:10 2026 -0700
skills(running-tend): drop case-specific example from root-cause section (#3421)
Follow-up to #3414: drops the example paragraph citing PR #3390 / issue
#3389 from the "Weigh the root-cause fix" section. Skills state the
recurrence pattern generically; per-incident references require
institutional memory the reader lacks and age into noise. The rule
paragraph stands alone; the case remains documented in #3414 and the
#3390 discussion.
> _This was written by Claude Code on behalf of max_
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
commit 69d6afb1fe7cd441278c02e57941208c3425b3f8
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Fri Jul 10 20:37:21 2026 -0700
fix: restore Claude plugin hooks to conventional hooks/hooks.json (#3418)
## Problem
Since [#3382](https://github.com/max-sixty/worktrunk/pull/3382)
("Claude-scope the shared hooks payload"), the Claude plugin's hooks
stopped loading in Claude Code. Skills and commands still load, so the
plugin looks healthy — but `/hooks` shows none of worktrunk's handlers
and the 🤖/💬 activity markers never update. It fails silently.
Root cause (confirmed by the reporter on Claude Code 2.1.207): Claude
Code discovers plugin hooks by **convention** at `hooks/hooks.json`. It
does **not** honor `plugin.json`'s string-path `"hooks":
"./hooks/claude-hooks.json"` override for plugin loads. The #3382 rename
moved the file off the only path Claude's loader actually reads.
The rename was intended to stop Codex's convention discovery from
surfacing Claude's events
([#3362](https://github.com/max-sixty/worktrunk/issues/3362)). But the
Codex manifest already defined its hooks **inline** (`Some(Inline)`
branch of `resolve_manifest_hooks`), which overrides Codex's convention
discovery — and that inline definition **predates** the rename (verified
against `2677f05^`). So the rename was always redundant for the Codex
scoping, while it broke Claude's discovery.
## Solution
Restore the file to the conventional
`plugins/worktrunk/hooks/hooks.json` and point `plugin.json`'s `hooks`
back at it. `plugin.json` reverts byte-for-byte to its pre-#3382 state.
This does not resurface the #3362 collision: the Codex manifest's inline
`hooks` object keeps Codex off the shared `hooks/hooks.json` regardless
of the filename. The two toolchains now coexist on one file — Claude
discovers it, Codex ignores it via the inline override. The
`test_plugin_layout_is_consolidated` assertions covering the Codex
inline manifest are unchanged and still pass.
Also updates `plugins/worktrunk/CLAUDE.md` (layout diagram + Known
Limitations) and drops the now-reverted #3382 CHANGELOG entry, replacing
it with a Fixed entry (both were in the unreleased 0.67.0, so the net
change is nil).
## Testing
Reproduction is captured in `test_plugin_layout_is_consolidated`
(`tests/integration_tests/config_show.rs`), whose invariant was inverted
from "the Claude file must be Claude-scoped" to "the Claude file must
sit at the conventional `hooks/hooks.json` that Claude Code's loader
discovers." Against pre-fix production it fails (`left:
"./hooks/claude-hooks.json"`); after the rename it passes.
- `test_plugin_layout_is_consolidated` ✅
- `test_claude_hook_commands_parse_in_all_shells` ✅ (reads the renamed
file; shell-integration-tests)
- Full `integration_tests::config_show` module: 136 passed ✅
- `test_docs_are_in_sync` ✅
**Not verified end-to-end from CI** (per running-tend "Don't ship fixes
you can't verify"): the bot cannot drive a live Claude Code or Codex
session to observe the hooks firing. The fix rests on the reporter's
confirmed symptom + repro (symlinking `hooks/hooks.json` →
`claude-hooks.json` immediately restores the hooks) and on Claude Code's
[documented convention
path](https://code.claude.com/docs/en/plugins-reference.md)
(`hooks/hooks.json` in plugin root). The Codex-stays-clean claim rests
on the existing code analysis in CLAUDE.md and the inline manifest
predating the rename, not a live Codex run.
---
Closes #3417 — automated triage
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
commit f2508771d28aa98140c6c56add8769243d5c0a3d
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Fri Jul 10 16:26:20 2026 -0700
skills(running-tend): weigh root-cause fix over lossy config workaround (#3414)
Adds a triage rule to `running-tend` capturing a maintainer-flagged bad
case from the daily review-runs analysis.
## The bad case
In [#3390](https://github.com/max-sixty/worktrunk/pull/3390) (triage of
[#3389](https://github.com/max-sixty/worktrunk/issues/3389)), triage of
the `⚑` `branch_worktree_mismatch` flag shipped a **docs-only** PR
recommending users reconfigure `worktree-path = "{{ branch | basename
}}"`. On [#3390 the maintainer pushed
back](https://github.com/max-sixty/worktrunk/pull/3390#issuecomment-4928586504)
— "why do we drop the prefix?" — then [explicitly asked to log it as a
bad
case](https://github.com/max-sixty/worktrunk/pull/3390#issuecomment-4928926873)
and closed the PR.
Two problems the rule targets:
- **Lossy workaround presented as *the* fix.** `basename` collapses
`alice/foo` and `bob/foo` to `foo`, colliding on one directory, whereas
the default `{{ branch | sanitize }}` keeps them distinct (`alice-foo` /
`bob-foo`). The collision downside only surfaced after the maintainer
challenged it.
- **Jumped to a docs workaround over the root-cause code fix.** The
proportionate fix was teaching the path-match check to tolerate a
dropped namespace prefix, not asking every affected user to adopt a
lossy template. The "docs-only, no risk" framing masked a bad
recommendation — low code risk ≠ good guidance.
## The rule
A new `### Weigh the root-cause fix before shipping a config/docs
workaround` subsection under Issue Triage, placed right after
"Suggesting Aliases for Niche Feature Requests" (which encourages
config-based deflection) as a tempering counterpoint: check whether a
configurable workaround is lossy/foot-gunny, weigh a proportionate
root-cause code fix first, and surface any recommended config's
downsides up front.
## Gate assessment
- **Gate 1 (confidence)**: Critical — directly flagged by @max-sixty
("log this as a bad case"). 1 occurrence is sufficient at Critical.
- **Gate 2 (magnitude)**: Targeted one-subsection addition; evidence bar
met by the Critical maintainer flag.
Already recorded in the [review-runs-tracking evidence
log](https://github.com/max-sixty/worktrunk/issues/3349#issuecomment-4923383823).
Kept as a separate atomic PR from the open cargo-PATH skill note
([#3361](https://github.com/max-sixty/worktrunk/pull/3361)) — different
concern, non-overlapping section.
<sub>Run
[29081037464](https://github.com/max-sixty/worktrunk/actions/runs/29081037464)
· daily review-runs</sub>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
commit abf9155546c84e65a09118f4b82e7a7c545a8d66
Author: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
Date: Fri Jul 10 06:10:26 2026 -0700
Release v0.67.0 (#3416)
Release v0.67.0: version bump and changelog.
Highlights since v0.66.0: experimental `wt remove --reap` (#3396), `wt
switch -x` opening the picker (#3394), the termimad panic containment in
the picker's PR-comments preview (#3408), the `wt config show` SIGTTOU
fix (#3327), the `GIT_*` discovery-var scrub for `wt step for-each` and
the `--execute` fallback (#3400), and the `wt list` prompt-reserve fix
(#3409).
Seven commits landed on `main` after the initial cut; `origin/main` is
merged back in and the two user-facing ones (#3394, #3411) plus the BY
CONTEXT profile table (#3403) are folded into the changelog.
Pre-release validation: local pre-merge gate green on the merged tree
(4389 tests); nightly cross-platform suite green on the initial cut
([run
29086956095](https://github.com/max-sixty/worktrunk/actions/runs/29086956095)),
with the seven post-cut commits each validated by their own PR CI;
`cargo semver-checks` reports no breaking changes; data-loss surface
review of the cumulative diff (including the drift commits) found one
new destructive capability (`--reap`, explicit opt-in, adjudicated
acceptable).
> _This was written by Claude Code on behalf of max_
commit bd504f09982f4bababfcb33d649490312fad6819
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Fri Jul 10 04:30:14 2026 -0700
feat(switch): run --execute against the picked worktree (#3394)
commit 4f6f6d8dc28efce94f97d06a6767631e2b5dce14
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Fri Jul 10 04:27:29 2026 -0700
fix(test): tolerate the prune race in picker alt-x branch assertions (#3412)
commit dac0f80608b909b6b0d254b793e046267d193e60
Author: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
Date: Fri Jul 10 03:56:40 2026 -0700
feat(config): wt config update pins [list] json-schema while unset (#3411)
Wires the `[list] json-schema` nag into the standard config-update
machinery, so the warning comes with the standard one-command fix.
`wt config update` now pins `json-schema = 1` (the behavior-preserving
choice) when the key is unset, via a new `PendingDefault` variant in
`DEPRECATION_RULES`: applied on the update pass only — the load path
must not pin, or an in-memory `Some(1)` would silence the nag — and
scoped to user config through a `ConfigFileKind` enum threaded through
detection in place of the old string labels. Two guards keep the pin
honest: it stays inert when the system config layer already defines the
key (a user-file pin would override a system-level `= 2` and flip
resolved output), and the nag's hint offers `wt config update` only when
the same detection update runs would actually write the pin — a missing,
unreadable, or malformed user config falls back to naming the manual
setting.
The detection-equals-migration invariant holds with the warning
relocated: the pin's warning fires at the JSON-emitting surface
(`resolve_json_schema`) exactly when update would change the file, while
config load stays quiet (`DeprecationKind::is_pending_default` filters
it, and pin-only configs skip the warning-dedup machinery entirely), so
`wt switch` users never see it. `wt config show` renders the pending
pin's diff — including for empty config files — but keeps its TOML dump:
a pin is additive, unlike a deprecation diff that supersedes the dump.
This departs from the plan reviewed in `design/list-json-v2.md` (#3357),
which deferred the `wt config update` integration to the default flip.
Deliberate tradeoff: users who run update during the window land pinned
on schema 1 and will see the `= 1` deprecation round after the flip; in
exchange, the warning ships with its fixer.
**Testing:** unit tests pin the rule's iff (unset ⟺ update changes the
file), kind scoping (System/Project inert, load pass inert), the
PendingDefault-rule/kind coupling, and placement in existing or implicit
`[list]` sections; integration tests cover both hint variants, the
update flow end-to-end (pin applied, second run clean), `--print`, and
the system-config deferral. The invariant battery runs with an explicit
pin appended so each case exercises only its own rule. Full pre-merge
gate green (4386 tests) plus `--features shell-integration-tests`
clippy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
> _This was written by Claude Code on behalf of max_
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
commit 18d2bef84b989b4186d8bb1b2a9398d62d22a00e
Author: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
Date: Fri Jul 10 03:44:18 2026 -0700
refactor(perf): canonicalize the benchmark / wt-perf system (#3403)
Implements the design proposal from this branch's first commit
(b22aced85, `design/simplify-bench-perf.md` — reviewable there; dropped
from the merge candidate per the `design/` convention). The perf
tooling's emission side was already canonical; this consolidates the
consumption side around one split: capture/harness work in `wt-perf`,
analysis in `worktrunk::trace` with `wt config state logs profile` as
its one CLI surface.
The seven design items:
1. **`wt-perf cache-check` deleted** — its output was the `cache` field
of `logs profile --format=json`. The tend statusline recipe now uses
`logs profile` (its old pipe also fed human-format stderr into a JSON
parser, so it's now correct as well as canonical).
2. **Timeline renderer moved into `worktrunk::trace`**
(`src/trace/timeline.rs`), sharing
`command_label`/`render_table`/`fmt_dur` with the profile so the two
views can't drift (they already had); drops wt-perf's `tabwriter` and
`insta` deps. Durations now render in the profile's fixed-point format.
3. **`Profile.by_context`** (BY CONTEXT table + JSON array):
per-worktree subprocess totals, the one analysis that previously
required trace_processor SQL. The SQL sections in `benches/CLAUDE.md`
and the `trace` module docs are replaced by a `logs profile` pointer;
Perfetto stays for visual critical-path work.
4. **One warm/cold bench runner** (`wt_perf::bench_wt`) owns the
`BatchSize::PerIteration` rationale once, replacing five inline copies.
All runs now assert child exit status (`benches/list.rs` previously
ignored it).
5. **Bench matrix pruned**: `real_repo` keeps only the 8-worktree
variants (scaling shape stays on synthetic `worktree_scaling`),
`cow_copy` deleted (benchmarked a hand-written serial copy against
production rayon — a settled choice), `remove_e2e/first_output` deleted
(duplicate of `first_output/remove`). Ends three gist series with this
PR as the single discontinuity point; saves roughly 15–20 min of the
~80-min daily bench run.
6. **wt-perf CLI tests moved in-package** (native
`CARGO_BIN_EXE_wt-perf`): the dummy `builds.rs` is gone and the nextest
setup script builds only `mock-stub`. Note `cargo test --test
integration` no longer runs these four tests; full-workspace runs still
do.
7. **`parse_config` returns a `SetupConfig` enum** covering `mixed-W-B`;
the two fixture builders share `init_bench_repo`.
**Merge with main (#3401):** the new prune benches and fixtures landed
mid-flight in exactly these files; the resolution routes them through
the consolidated API — `prune-M-U` is a `SetupConfig::Prune` variant,
the pair parsing is one shared `parse_pair`, and `prune-real` keeps its
own cache-managed path (no `--path`, self-repairing). `benches/prune.rs`
keeps its custom `iter_batched` arms deliberately: every variant asserts
the candidate count from stdout, which `bench_wt`'s shape doesn't
express — the exact carve-out the cache-handling docs describe.
Two review passes (correctness + subtraction) then folded in:
`CacheReport` trimmed to its same-context fields (the dropped six
duplicated `Profile` fields or counted cross-context noise the text
report never showed), `render_table` pads by display width (replacing an
ASCII-only invariant the new BY CONTEXT table violated), the timeline's
instant-event row gained test coverage, and Chrome args dropped the
redundant `duration_ms`.
**Decisions surfaced, not taken** (from the review passes): (a) `wt-perf
setup` could become `--persist`-only, dropping the interactive
Enter-to-cleanup path — a UX preference; (b) whether the daily benchmark
cron's gist time series justifies its remaining ~60 min/day is a
cost/value call. Also deferred: measuring whether `skeleton/cold` adds
signal over `skeleton/warm` before cutting it.
Gate green throughout (4374 tests); `clippy --features
shell-integration-tests` clean; `setup`/`timeline` (warm, cold,
chrome)/`logs profile` (text + JSON)/`trace` all driven end-to-end on a
scratch `mixed-4-8` repo.
> _This was written by Claude Code on behalf of max_
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
commit fbf380b4471a580256f8da251de0aec9309828e3
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri Jul 10 03:43:43 2026 -0700
chore: bump taiki-e/install-action from 2.82.11 to 2.83.0 (#3405)
Bumps
[taiki-e/install-action](https://github.com/taiki-e/install-action) from
2.82.11 to 2.83.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/releases">taiki-e/install-action's
releases</a>.</em></p>
<blockquote>
<h2>2.83.0</h2>
<ul>
<li>
<p>Support <code>cargo-about</code>. (<a
href="https://github.com/taiki-e/install-action/pull/1924">#1924</a>,
thanks <a
href="https://github.com/ruffsl"><code>@ruffsl</code></a>)</p>
</li>
<li>
<p>Update <code>uv@latest</code> to 0.11.28.</p>
</li>
<li>
<p>Update <code>martin@latest</code> to 1.12.0.</p>
</li>
<li>
<p>Update <code>kingfisher@latest</code> to 1.106.0.</p>
</li>
<li>
<p>Update <code>biome@latest</code> to 2.5.3.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md">taiki-e/install-action's
changelog</a>.</em></p>
<blockquote>
<h2>[2.83.0] - 2026-07-09</h2>
<ul>
<li>
<p>Support <code>cargo-about</code>. (<a
href="https://github.com/taiki-e/install-action/pull/1924">#1924</a>,
thanks <a
href="https://github.com/ruffsl"><code>@ruffsl</code></a>)</p>
</li>
<li>
<p>Update <code>uv@latest</code> to 0.11.28.</p>
</li>
<li>
<p>Update <code>martin@latest</code> to 1.12.0.</p>
</li>
<li>
<p>Update <code>kingfisher@latest</code> to 1.106.0.</p>
</li>
<li>
<p>Update <code>biome@latest</code> to 2.5.3.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/taiki-e/install-action/commit/c7eb1735f09259a5035e8e5d44b1406b1cddc0fb"><code>c7eb173</code></a>
Release 2.83.0</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/84d4171800adc3ccfe039969324217bde4ca0f25"><code>84d4171</code></a>
Update changelog</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/21e5d859bea44c4a9c990a48a63fed2203e01d68"><code>21e5d85</code></a>
Support cargo-about (<a
href="https://github.com/taiki-e/install-action/issues/1924">#1924</a>)</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/786ded99bea7500a913f1f19b18bfcfdf782fb97"><code>786ded9</code></a>
Update <code>uv@latest</code> to 0.11.28</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/1c1bbcf779fa11c2ef8cf0bda69bba43465230e6"><code>1c1bbcf</code></a>
Update rclone manifest</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/4e5eb09101d68b0b5f52740d1abc011ae003ac6f"><code>4e5eb09</code></a>
Update <code>martin@latest</code> to 1.12.0</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/dc84fc5f1a7ad878e27656eb3ed173e393c62867"><code>dc84fc5</code></a>
Update <code>kingfisher@latest</code> to 1.106.0</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/cf10e546be4ee50afcfd94a851bcd9eb64cba30d"><code>cf10e54</code></a>
Update <code>biome@latest</code> to 2.5.3</li>
<li>See full diff in <a
href="https://github.com/taiki-e/install-action/compare/v2.82.11...v2.83.0">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
commit 9283179d1176a56985d38f1a3c92a05e6015c68b
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri Jul 10 03:43:38 2026 -0700
chore: bump max-sixty/tend from 0.1.9 to 0.1.10 (#3404)
Bumps [max-sixty/tend](https://github.com/max-sixty/tend) from 0.1.9 to
0.1.10.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/max-sixty/tend/releases">max-sixty/tend's
releases</a>.</em></p>
<blockquote>
<h2>0.1.10</h2>
<h3>Improved</h3>
<ul>
<li><strong>The system prompt gains an explicit priority
ordering.</strong> A new <code>Priorities</code> section in the shared
system prompt, loaded by every harness, ranks (1) being pro-social, (2)
making the project excellent, and (3) helping individual users — so when
an individual's workaround and the durable project-level fix pull apart,
the bot foregrounds the durable fix. The triage skill adds a matching
"apply the project lens" step before replying. (<a
href="https://github.com/max-sixty/tend/pull/758">#758</a>)</li>
<li><strong>The <code>tend-outage</code> failure reporter renders every
entry as one table.</strong> Follow-up failures on an open outage issue
now append the same one-row <code>When | Run | Trigger</code> table the
issue body uses, instead of a bespoke one-liner, so a single outage
issue reads uniformly. (<a
href="https://github.com/max-sixty/tend/pull/748">#748</a>)</li>
<li><strong>Both Claude harnesses update to claude-code
2.1.201.</strong> (<a
href="https://github.com/max-sixty/tend/pull/755">#755</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li><strong>Bot commits and PRs are attributed solely to the tend
bot.</strong> Both Claude harness actions now set <code>attribution:
{commit: "", pr: ""}</code> in the agent's settings,
emptying Claude Code's auto-added <code>Co-Authored-By</code> commit
trailer and "Generated with Claude Code" PR footer, and the
triage, ci-fix, and review skill templates drop their hard-coded
<code>Co-Authored-By</code> lines. (<a
href="https://github.com/max-sixty/tend/pull/760">#760</a>)</li>
<li><strong><code>tend-mention</code> no longer runs a no-op session on
the bot's own APPROVED review.</strong> The bot's empty-body approval is
terminal, but its <code>pull_request_review</code> event passed the
engagement check (which counts that very review) and spun up a session
with nothing to do. The verify job now skips when the review is the
bot's own approval with an empty body — a bot review requesting changes,
commenting, or approving with body text still fires. (<a
href="https://github.com/max-sixty/tend/pull/749">#749</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/max-sixty/tend/blob/main/CHANGELOG.md">max-sixty/tend's
changelog</a>.</em></p>
<blockquote>
<h2>0.1.10</h2>
<h3>Improved</h3>
<ul>
<li><strong>The system prompt gains an explicit priority
ordering.</strong> A new <code>Priorities</code> section in the shared
system prompt, loaded by every harness, ranks (1) being pro-social, (2)
making the project excellent, and (3) helping individual users — so when
an individual's workaround and the durable project-level fix pull apart,
the bot foregrounds the durable fix. The triage skill adds a matching
"apply the project lens" step before replying. (<a
href="https://github.com/max-sixty/tend/pull/758">#758</a>)</li>
<li><strong>The <code>tend-outage</code> failure reporter renders every
entry as one table.</strong> Follow-up failures on an open outage issue
now append the same one-row <code>When | Run | Trigger</code> table the
issue body uses, instead of a bespoke one-liner, so a single outage
issue reads uniformly. (<a
href="https://github.com/max-sixty/tend/pull/748">#748</a>)</li>
<li><strong>Both Claude harnesses update to claude-code
2.1.201.</strong> (<a
href="https://github.com/max-sixty/tend/pull/755">#755</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li><strong>Bot commits and PRs are attributed solely to the tend
bot.</strong> Both Claude harness actions now set <code>attribution:
{commit: "", pr: ""}</code> in the agent's settings,
emptying Claude Code's auto-added <code>Co-Authored-By</code> commit
trailer and "Generated with Claude Code" PR footer, and the
triage, ci-fix, and review skill templates drop their hard-coded
<code>Co-Authored-By</code> lines. (<a
href="https://github.com/max-sixty/tend/pull/760">#760</a>)</li>
<li><strong><code>tend-mention</code> no longer runs a no-op session on
the bot's own APPROVED review.</strong> The bot's empty-body approval is
terminal, but its <code>pull_request_review</code> event passed the
engagement check (which counts that very review) and spun up a session
with nothing to do. The verify job now skips when the review is the
bot's own approval with an empty body — a bot review requesting changes,
commenting, or approving with body text still fires. (<a
href="https://github.com/max-sixty/tend/pull/749">#749</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/max-sixty/tend/commit/bdfbdd40ac9455db7082b8abca040225503e933e"><code>bdfbdd4</code></a>
chore: release 0.1.10 (<a
href="https://github.com/max-sixty/tend/issues/763">#763</a>)</li>
<li><a
href="https://github.com/max-sixty/tend/commit/6e58d2ef510bd51898cbb14dec0ffd09fd5dc28d"><code>6e58d2e</code></a>
fix: attribute bot commits and PRs solely to the tend bot (<a
href="https://github.com/max-sixty/tend/issues/760">#760</a>)</li>
<li><a
href="https://github.com/max-sixty/tend/commit/75240861f0ac25ac54713b77395f36fb3dcb4f1b"><code>7524086</code></a>
prompt: make project excellence an explicit priority (<a
href="https://github.com/max-sixty/tend/issues/758">#758</a>)</li>
<li><a
href="https://github.com/max-sixty/tend/commit/1a505d4649980668d5082440d3465a5c2aca19ff"><code>1a505d4</code></a>
chore(review-reviewers): bump action pin to claude-interactive@0.1.9 (<a
href="https://github.com/max-sixty/tend/issues/754">#754</a>)</li>
<li><a
href="https://github.com/max-sixty/tend/commit/668dce805f035f4b3b14ddb15922f92dbd5b6144"><code>668dce8</code></a>
chore: bump claude_version to 2.1.201 (<a
href="https://github.com/max-sixty/tend/issues/755">#755</a>)</li>
<li><a
href="https://github.com/max-sixty/tend/commit/891df36b1d7ec493733644e616dde6946c639763"><code>891df36</code></a>
fix: skip no-op tend-mention session on bot's own APPROVED review (<a
href="https://github.com/max-sixty/tend/issues/749">#749</a>)</li>
<li><a
href="https://github.com/max-sixty/tend/commit/8bcb1537181c78c252d463e2b0fb8541e61f00af"><code>8bcb153</code></a>
refactor(report-failure): render every outage entry as one table (<a
href="https://github.com/max-sixty/tend/issues/748">#748</a>)</li>
<li><a
href="https://github.com/max-sixty/tend/commit/a28e618205c3f740c9a3b2c9f6a09cea00439156"><code>a28e618</code></a>
chore: regenerate workflows with tend 0.1.9 (<a
href="https://github.com/max-sixty/tend/issues/746">#746</a>)</li>
<li>See full diff in <a
href="https://github.com/max-sixty/tend/compare/0.1.9...0.1.10">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
commit 103e9a70abd418b34969358a8bafceb8c5e2e741
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Fri Jul 10 03:43:30 2026 -0700
chore: update tend workflows (0.1.9 → 0.1.10) (#3413)
Automated nightly regeneration of tend's workflow files, picking up tend
0.1.10.
**tend version:** 0.1.9 → 0.1.10
**Notable changes:**
- Skip a no-op `tend-mention` session when the only trigger is the bot's
own empty-body *approved* review — the review is terminal, so there's
nothing to act on (a bot `CHANGES_REQUESTED`/`COMMENTED` review, or an
approval carrying nits in its body, still fires). This is the
substantive body change in this regen (`tend-mention.yaml`)
(max-sixty/tend#749).
- Attribute bot commits and PRs solely to the tend bot account
(max-sixty/tend#760).
- `report-failure` now renders every outage entry as a single table in
`tend-outage` issues (max-sixty/tend#748).
- Prompt update: make project excellence an explicit priority for the
bot (max-sixty/tend#758).
Compare: https://github.com/max-sixty/tend/compare/0.1.9...0.1.10
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
commit 26f66585228c9aa627fcf6d82a1073c059c1935f
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Fri Jul 10 03:37:13 2026 -0700
fix: contain termimad panic on ragged tables in PR comments (#3408)
## Problem
Running `wt switch` (the interactive picker) crashes with:
```
thread '<unnamed>' panicked at termimad-0.34.1/src/tbl.rs:176:30:
index out of bounds: the len is 7 but the index is 7
Rayon: detected unexpected panic; aborting
```
The panic originates in the `termimad` crate (latest version, 0.34.1 —
no upstream fix available), reached via the picker's PR-comments preview
pane: `picker::prs::render_comment_blocks` →
`md_help::render_table_with_termimad` → `MadSkin::text`.
**Root cause (upstream bug):** termimad's column fitter,
`Table::fix_columns`, panics on a *ragged* table — one where some row
has more cells than the header — when it's rendered at a width too
narrow to fit the widest row. In that case the fitter's `TblFit::new`
fails and it takes an error branch that **skips** padding the short rows
to the column count, then the alignment loop indexes past them
(`cells[ic]` for `ic in 0..nbcols`). Equal-column tables never hit this
at any width; only ragged ones do.
PR/MR comment markdown is untrusted and can contain such a table.
Because the comments pane renders on a **rayon worker**, the escaped
panic aborts the entire process rather than just failing one render.
## Solution
Contain the upstream panic at the single termimad call site
(`render_table_with_termimad` in `src/md_help.rs`) with
`std::panic::catch_unwind`, falling back to the plain preprocessed table
lines so the pane still shows the table's text. This keeps the panic
from escaping to the rayon worker and taking down `wt switch`, and
guards the whole class of termimad table panics rather than one specific
input.
## Testing
Added `test_render_table_ragged_narrow_does_not_panic` in
`src/md_help.rs`, which renders a ragged table (`| Key | Value |` header
over a six-column data row) at a narrow width. Verified it panics at
`tbl.rs:176:30` without the fix (same location as the report) and passes
with it. Full `md_help` suite (26 tests) and `cargo clippy` are green.
---
Closes #3407 — automated triage
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
commit 57b303fda253dc80340133807f6d8833f6b47ec0
Author: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
Date: Thu Jul 9 23:29:40 2026 -0700
fix(nix): provide lsof to the flake's test sandbox (#3410)
The nix-flake job has failed on main since #3396:
`test_remove_reap_kills_process` discovers its child via `lsof -d cwd`,
but the flake's `worktrunk-tests` check didn't provide `lsof`, so
`processes_under` fails to spawn it, returns an empty list forever, and
the test's 5s discovery poll panics with `child <pid> never discovered`.
Failing main run:
https://github.com/max-sixty/worktrunk/actions/runs/29066326142.
Three changes:
- `flake.nix`: add `pkgs.lsof` to the test check's inputs, the same
pattern as `git`/`python3`/`procps`. `ps` (procps) already works in the
sandbox, so /proc-based process inspection is available there and the
test should be genuinely exercised rather than excluded.
- `.github/workflows/nightly.yaml`: add `flake.nix`, `flake.lock`, and
`nix/**` to the trigger paths (push and PR gate). A flake change
couldn't previously trigger the nix-flake job that validates it; this PR
relies on the fixed trigger for its own validation.
- `tests/integration_tests/remove.rs`: point the discovery-timeout panic
message at lsof, so the next environment missing it doesn't need
re-diagnosing.
Validation: nix isn't available locally, so the proof is this PR's own
nightly run going green on the nix-flake job.
> _This was written by Claude Code on behalf of max_
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
commit 0b413ae3b21bb4976474c1b28e9db94dcd268017
Author: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
Date: Thu Jul 9 22:35:47 2026 -0700
fix(list): reserve prompt rows so the table doesn't jump at exit (#3409)
`wt list`'s progressive table sits stable on screen through the loading
phase, then jumps up at an unpredictable moment: the command exits and
the shell prints its multi-line prompt, and the terminal scrolls to make
room. Every other command scrolls too, but there the output scroll and
the prompt scroll merge into one Enter-time event; the progressive
table's delay between paint and exit is what makes the jump read as a
jerk.
The renderer now emits two blank rows below the footer with the skeleton
and moves the cursor back over them. The prompt renders into those
pre-scrolled rows, so the settled table doesn't move at exit; the scroll
happens at Enter-time with the skeleton paint, where scroll is expected.
The exact prompt height is unknowable, but the failure modes are
asymmetric, so a constant works: extra reserved rows are consumed
invisibly by whatever prints next (verified in tmux: a 1-line prompt
leaves scrollback byte-identical to before), while a prompt taller than
3 rows scrolls by just the difference. Two rows absorb fish's default
(1), starship's default (2), and tide/powerlevel10k (typically 3).
Details:
- The viewport budget goes from `h − 4` to `h − 6` so a bottom-pinned
skeleton plus reserve can't scroll its own header off, which would break
the `MoveUp` redraw math. Short terminals show two fewer skeleton rows;
the overflow finalize still prints the full table at the end.
- The overflow finalize path re-emits the reserve after its reprint.
- The resting cursor position (line after the footer) is unchanged, so
the in-place update code needed no changes.
- The PTY test harness now exposes the final cursor position; the
overflow test asserts the cursor rests two reserved rows above the
bottom, and the fast-command test pins the resting position (catching an
emit-without-`MoveUp` mismatch).
Verified end-to-end in tmux (fish, 3-row prompt, full screen, 11
worktrees): the header row stays fixed across run, exit, and prompt,
where the released binary jumps two rows. The error path (post-table
error blocks printed after finalize) consumes the reserve and keeps
today's behavior, which is fine: new content appearing at completion
legitimately grows the screen.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
> _This was written by Claude Code on behalf of max_
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
commit 958b3082de22ad9f518eec7aab78bd9e4508b7f5
Author: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
Date: Thu Jul 9 22:08:33 2026 -0700
fix(env): scrub GIT_* discovery vars from for-each and --execute fallback (#3400)
#3374 scrubbed inherited `GIT_*` discovery vars from user-hook spawns,
but two more spawn sites relocate a user command into a wt-chosen
worktree and had the same bug: `wt step for-each` and the `--execute`
payload when wt executes it directly (no shell integration — including
every `!wt`-alias invocation, which bypasses the wrapper). Git resolves
`GIT_DIR`/`GIT_WORK_TREE` before walking up from the cwd, so the
inherited value silently overrode the worktree wt placed the command in:
`git w step for-each -- git rev-parse --show-toplevel` from a linked
worktree printed the invoking worktree's path for every iteration while
the headers named each worktree. The linked-worktree alias case is the
common trigger — git exports an absolute `GIT_DIR` pinned to the
invoking worktree's private gitdir there (verified on git 2.54; from a
main-worktree root it exports none).
The rule is now stated once, on `scrub_git_discovery_env_vars`: **scrub
exactly when wt chose the child's cwd** (hooks, for-each, the
`--execute` fallback); keep when the command runs in the user's own
context (aliases, `commit.generation`, and wt's internal plumbing, which
keeps the absolutize-and-forward behavior from #1914). A CLAUDE.md rule
points there, mirroring the `CommandTrace` any-new-spawn-site
convention.
Testing: regression tests for both new scrub sites (each validated by
reverting the fix and watching it fail) plus an alias pass-through test
pinning the keep side. The `--execute` test is cross-platform, so
Windows CI drives the non-unix `spawn` variant and unix the `exec`
variant; the for-each test follows the file's unix `sh -c` precedent.
Ref #3373 (already closed by #3374; this completes the sweep).
> _This was written by Claude Code on behalf of max-sixty_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
commit 5e58827a2858972080d47840b649ab70852bfaf7
Author: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
Date: Thu Jul 9 21:35:28 2026 -0700
bench(prune): rust-scale prune fixture, criterion benches, and removal trace spans (#3401)
Makes `wt step prune` / `wt remove` staging performance measurable and
reproducible. Users report prune taking many seconds while the synthetic
benches scan in ~150 ms; this adds the observability and fixtures that
reproduce the gap and attribute it. No `wt` runtime behavior changes —
the `src/` diff is ~29 lines of trace spans.
## What a reviewer needs
- **Trace spans** (`src/commands/step/prune.rs`,
`src/commands/process.rs`, `src/git/fsmonitor.rs`): `prune-gather` /
`prune-scan` / `prune-check:<ref>` / `prune-remove:<label>` plus
`internal-sweep` around `wt remove`'s end-of-command janitor. A
`prune-remove` span starts before the write-lock acquisition, so it
reads as "how long this removal stalled the run" (documented in
benches/CLAUDE.md).
- **Fixtures** (`tests/helpers/wt-perf/src/lib.rs`, the bulk of the
diff): `prune-M-U` — M squash-merged candidate pairs
(content-integrated, the post-PR-squash shape) against a
two-sided-diverged backdrop forked across history; `prune-real[-M-U]` —
the same shape on a rust-lang/rust clone, default 12+24 → 36 linked
worktrees. The real fixture is cache-managed under `target/bench-repos/`
(~15 GiB, minutes to build): `ensure_prune_real_repo` classifies it
Intact/Consumed/Broken on reuse, re-creates candidates consumed by a
live prune in ~1 min (round derived from the surviving squash commits,
no sidecar state), and heals invalidated worktree indexes.
- **Benchmarks** (`benches/prune.rs`): criterion groups `prune_e2e`
(dry-run cold/warm + live with per-iteration candidate re-creation) and
`prune_real_repo` (warm dry-run only). The real group is gated behind a
new `real-repo-benches` cargo feature so the nightly benchmarks workflow
— plain `cargo bench` on a hosted runner — never builds a fixture bigger
than the runner's disk and the actions cache cap.
## Measured (M-series Mac, in benches/CLAUDE.md)
Live prune on the rust-scale fixture: **~12 s wall** — a 5.4 s stat-cold
scan (36 `git status` at ~4.5 s each, absorbed by the rayon pool) plus
24 removals serialized under the scan write lock (~0.5–1.7 s per
worktree candidate). Warm re-scan: 0.25–0.8 s. This reproduces the
reported "prune takes seconds" experience and points optimization at
status cost and removal overlap, not the integration probes.
Rider fix picked up en route:
`docs.anthropic.com/en/docs/build-with-claude/claude-code` now 404s
(failing lychee on every push, including on main), so the llm-commits
installation link and the `wt config plugins` install-hint now point at
`code.claude.com/docs/en/setup`.
Also fixes a latent fixture bug: `history_spread_shas` divided by its
hardcoded 5000-commit cap, so on short synthetic histories every
"history-spread" fork silently collapsed to the tip.
## Testing
wt-perf unit tests cover the fixture lifecycle (state classification,
derived repair rounds, squash content-integration); every
`ensure_prune_real_repo` path (build, cached, repair, index-heal,
foreign cwd) was exercised end-to-end locally, as were both criterion
groups and the one-shot timelines. An integration test asserts the `wt
remove -vv` trace surfaces the fsmonitor sweep (span plus daemon count),
pinning the sweep's observability contract. The rust-scale numbers are
I/O-bound and ambient-load-sensitive — documented as shape, not
thresholds.
> _This was written by Claude Code on behalf of max_
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
commit c8de0d872a90f318d98e9303a0d44fed3d9358aa
Author: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
Date: Thu Jul 9 20:12:03 2026 -0700
ci: move lychee link check to the nightly workflow (#3402)
Moves the lychee link check out of PR CI into the `nightly` workflow.
Link health depends on external sites (429s, bot-blocking, link rot),
which made `lint` the flakiest PR check, and breakage isn't correlated
with the PR that trips it: the most recent main run's lint job went red
on the docs.anthropic.com restructure (fixed in #3399) with no code
change involved.
The hook stays defined once in `.pre-commit-config.yaml`, moved to the
`manual` stage, so the PR `lint` job, local `pre-commit run
--all-files`, and the `wt merge` gate all skip it (the wt.toml Windows
conditional existed only to skip lychee and collapses). The new
`link-check` nightly job runs that stage with the pinned lychee and is
wired into `create-issue-on-nightly-failure`, so breakage lands in the
nightly-failure issue for tend instead of blocking unrelated PRs.
Running through pre-commit rather than `git ls-files | xargs lychee`
keeps file selection at one definition: pre-commit's `types: [file]`
filter excludes the 16 tracked `.md`/`.txt` symlinks, which would
otherwise false-positive on relative links resolved against the
symlink's directory.
The trade: a PR that introduces a genuinely wrong URL now merges green
and is caught up to a day later, asynchronously.
Verified locally that the default stage no longer selects the hook and
that `pre-commit run lychee-system --all-files --hook-stage manual`
reproduces the CI lint run's selection exactly (same two flagged
inputs). The nightly job itself first runs on the next cron or a
`workflow_dispatch`.
> _This was written by Claude Code on behalf of max_
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
commit 9b2cbc6a8c2852a30e7fff393f8343f3558c10ed
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Thu Jul 9 19:42:57 2026 -0700
docs(llm-commits): fix 404 Claude Code docs link (#3399)
## Problem
The `lint` CI job (its `lychee-system` link-check step) fails on the
default branch:
```
[404] https://docs.anthropic.com/en/docs/build-with-claude/claude-code | Rejected status code: 404 Not Found | Followed 2 redirects: --[301]--> platform.claude.com/docs/en/docs/build-with-claude/claude-code --[307]--> platform.claude.com/docs/en/build-with-claude/claude-code
```
Anthropic migrated its docs and the old `build-with-claude/claude-code`
path now redirects to a dead `platform.claude.com` URL. The link was
green when it last ran on `main`; the upstream migration broke it since,
so the next push to the default branch fails `lint`.
## Solution
Point the link at
`https://docs.anthropic.com/en/docs/claude-code/overview` (verified
`200`), which is the same URL form already used elsewhere in the repo.
The link lives in `docs/content/llm-commits.md` (the primary source for
this non-command doc); `skills/worktrunk/reference/llm-commits.md` is
the generated mirror, regenerated via `test_docs_are_in_sync`.
## Testing
- `curl -sIL https://docs.anthropic.com/en/docs/claude-code/overview` →
`200`.
- `cargo test --test integration test_docs_are_in_sync` passes (source
and skill mirror in sync).
- No `build-with-claude/claude-code` occurrences remain under `docs/` or
`skills/`.
---
Separated from the [PTY-flake CI fix
(#3398)](https://github.com/max-sixty/worktrunk/pull/3398) to keep each
concern atomic. Surfaced while fixing [run
29053905721](https://github.com/max-sixty/worktrunk/actions/runs/29053905721).
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
commit 1d46c0083b6680f22a1e6306b38e326ef9b7183c
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Thu Jul 9 19:42:48 2026 -0700
fix(test): treat Linux PTY EIO-on-close as EOF in read helpers (#3398)
## Problem
The `code-coverage` job failed on [run
29053905721](https://github.com/max-sixty/worktrunk/actions/runs/29053905721)
(default branch, commit f593438) with a panic in a PTY test:
```
thread '...test_branch_name_with_dashes_underscores::case_3' panicked at tests/common/pty.rs:44:41:
```
Line 44 was `reader.read_to_string(&mut buf).unwrap()` in the Unix
branch of `read_pty_output`. Only `case_3` (fish) panicked;
`case_1`/`case_2` (bash/zsh) passed in the same run — the flaky
signature.
**Root cause:** On Linux, a `read` on a PTY master returns `EIO` once
the child exits and closes the slave side, instead of the clean 0-byte
EOF macOS returns. `read_to_string` propagates that `EIO` as an
`io::Error`, and `.unwrap()` panics. Whether the read observes the `EIO`
or a clean EOF depends on whether it raced ahead of or behind the
child's exit — hence the intermittent, per-case failure.
This is the Linux face of the same fragile read that #3144 diagnosed as
a macOS read-to-EOF timeout; that issue explicitly flagged a more robust
read in this path as the escalation if the flake recurred.
## Solution
Extract a shared `read_pty_master_to_string` helper that reads to
end-of-stream and treats `EIO` as EOF on Unix (a plain `read_to_string`
on Windows/ConPTY, behavior unchanged). Route both PTY-master reads that
shared the fragile `read_to_string().unwrap()` pattern through it:
- `read_pty_output` (`tests/common/pty.rs`) — the shell-wrapper and
README-example PTY path.
- `execute_shell_script` (`tests/common/shell.rs:108`) — the e2e-shell
path, which had the identical pattern and the same latent flake.
`libc` (already present transitively via `portable-pty`) is added to
`[dev-dependencies]` for the canonical `EIO` constant.
## Testing
- `cargo test --test integration --features shell-integration-tests` for
`test_branch_name_with_dashes_underscores` and
`test_source_flag_forwards_errors` (bash/zsh/fish cases) — all pass.
`case_4` (nu) is only skippable locally because nushell isn't installed
in the CI-fix sandbox; it fails at *spawn*, not the read path, and
passed in the original CI run.
- `e2e_shell::*` (7 tests exercising `execute_shell_script`) — all pass.
- `cargo clippy --tests --features shell-integration-tests` and `cargo
fmt --check` — clean.
---
Automated fix for [failed
run](https://github.com/max-sixty/worktrunk/actions/runs/29053905721)
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
commit 745f6a90df0b15f437983493ad4e6ef58980bc63
Author: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
Date: Thu Jul 9 18:11:48 2026 -0700
refactor(hook): share command-label rendering with approvals surfaces (#3397)
Follow-up to #3380: `wt hook show` still hand-built the `{phase} name:`
command label that `ApprovableCommand::label()` renders for the approval
prompt and `wt config approvals`. This extracts the format into a
`command_label` free function in `project_config.rs` and calls it from
both. It's free-standing rather than a method on `ApprovableCommand`
because hook show also labels user hooks, which are never approvable.
Output is byte-identical — no snapshot changes.
> _This was written by Claude Code on behalf of max_
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
commit f593438e7c1b4898c82ed222e235f6d116974408
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Thu Jul 9 15:13:46 2026 -0700
feat(remove): add experimental --reap to kill worktree processes (#3396)
Implements the design agreed in #3365: an opt-in, experimental flag on
`wt remove` that reaps processes left running in the worktree.
## The name
`--reap` — it's the term the whole thread and the issue title already
use, it's concise (matching worktrunk's flag style), and the user-facing
messages read naturally (`◎ Reaping 2 processes under feature
worktree`).
## What it does
```console
$ wt remove --reap feature
◎ Reaping 2 processes under feature worktree
┃ 51234 node
┃ 51240 esbuild
✓ Reaped 2 processes
◎ Removing feature worktree & branch in background (same commit as main, _)
```
Processes are discovered by working directory (`lsof -d cwd`): any
process whose cwd is at or under the worktree path. Termination reuses
the existing `SIGTERM`→wait→`SIGKILL` escalation (`escalate_terminate`,
shared with the fsmonitor sweep).
## Data-safety posture
Killing a process the user didn't mean to kill — a terminal editor with
unsaved buffers — is exactly the silent loss-of-work the project refuses
without consent, so two guards keep `--reap` conservative:
- **Controlling-terminal exclusion.** A process holding a controlling
terminal (an interactive shell, or `vim`/`nvim`/`emacs -nw`) is never
reaped (`ps -o tty=`). Only detached processes — the dev servers and
watchers this issue is about — remain candidates. This also spares the
shell `wt remove` was run from.
- **Self-exclusion.** The current `wt` process is never a candidate.
The flag itself is the explicit opt-in; the list is printed before
signalling for transparency.
## Scope / limitations (matching the #3365 discussion)
- **Under-inclusive by design.** cwd discovery misses a daemon that
forked and `chdir`'d away, or one that reparented to `init` — they no
longer report a cwd under the path. Those are what [`wt step
tether`](https://worktrunk.dev/step/#wt-step-tether) is built to reap
(whole process group). `--reap` and `tether` cover different gaps and
are complementary, not substitutes — the docs say so.
- **Ordering.** Reaping runs before the worktree directory is
staged/renamed (cwd matching needs the directory in place), so it's
independent of foreground/background removal, trash-vs-delete, and
`--force`.
- **Unix only.** Windows has no cheap per-process cwd; `--reap` is
rejected there with a clear error.
## Tests
- Pure parsers for `lsof`/`ps` output (`parse_lsof_cwd`,
`parse_ps_tty`).
- End-to-end against the real `lsof`/`ps`: spawns a child with a cwd
under a tempdir, asserts `processes_under` discovers it, asserts the
controlling-terminal guard keeps-or-drops it in agreement with the
child's *actual* TTY state (so the test is host-independent — CI has no
TTY, a dev box does), then reaps it and confirms `SIGTERM`.
- CLI snapshot (`test_remove_reap_no_processes`) covering the
no-candidates path, deterministic whether or not `lsof` is installed on
the runner.
Help text, `docs/content/remove.md`, and the skill reference mirror are
regenerated and in sync.
One thing worth a maintainer's eye: I chose to print-then-signal rather
than add an interactive confirm/`--dry-run` in this first cut — the
opt-in flag + TTY exclusion + printed list felt like enough for an
experimental flag, and a confirm step is easy to layer on if you'd
prefer it.
Closes #3365.
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
commit d96eedb747d315949a6d884280010bb2b88a83b0
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Thu Jul 9 12:48:51 2026 -0700
refactor(test): extract truncate_details_section helper (#3393)
Follow-up to #3328, per @max-sixty's
[request](https://github.com/max-sixty/worktrunk/pull/3328#discussion_r3554171596).
`normalize_report` in `tests/integration_tests/diagnostic.rs` had three
near-identical truncation blocks — for the **Environment variables**,
**Performance profile**, and **Trace log** `<details>` sections. Each
did the same find-summary → find-`</details>` → splice-placeholder
dance, differing only in the section title and placeholder string.
This extracts a `truncate_details_section(&mut result, title,
placeholder)` helper and collapses all three call sites to one-liners.
Net −9 lines, and adding a fourth truncated section is now one call
instead of a copied block.
Behaviour is unchanged: the existing
`test_diagnostic_report_file_format` snapshot (which runs
`normalize_report`) and `test_diagnostic_includes_environment_variables`
both pass without regenerating the snapshot, confirming the helper
produces byte-identical output to the three inlined blocks.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
commit adb417ede817273d51ac5736924f7dc2bb41f5ff
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Thu Jul 9 12:23:20 2026 -0700
fix(config): keep `wt config show` from suspending on the zsh compinit probe (#3327)
## Problem
After upgrading 0.56.0 → 0.63.0, `wt config show` suspends instead of
running:
```
$ wt config show
[1] + 3071417 suspended (tty output)
```
`suspended (tty output)` is SIGTTOU: a process in a **background**
process group touched the controlling terminal. The reporter's `wt -vv
config show` shows the suspend landing right after the log-path header
and before any config content — i.e. the pager (`less`) is being
suspended on its first terminal operation.
## Root cause
`wt config show` runs an interactive zsh probe to detect whether
`compinit` is configured (`check_zsh_compinit_missing`, mirrored by
`shell::detect_zsh_compinit`). An interactive `zsh -ic` with a
controlling terminal enables job control: it `setpgid`s itself into a
new process group and `tcsetpgrp`s to claim the terminal **foreground**.
[#3165](https://github.com/max-sixty/worktrunk/pull/3165) (shipped in
0.62.0, inside the reporter's upgrade window) added a 2s
**kill-on-timeout** to that probe. When the user's interactive rc is
slow to start or blocks on a prompt (e.g. compinit's
insecure-directories prompt), the probe is **SIGKILLed before it
restores the foreground process group**. SIGKILL can't be caught, so
zsh's terminal-restore-on-exit never runs — the terminal foreground is
left pointing at the dead probe's group, and `wt` is now a background
process group. The pager `wt config show` spawns moments later then
raises SIGTTOU on its first `tcsetattr`, suspending the command.
This is environment-specific: it only fires when the probe actually
times out (slow/prompting interactive rc), which is why it doesn't
reproduce for everyone.
## Solution
Pass `+m` (disable job control) to both interactive probes. With job
control off, the probe never grabs `wt`'s controlling terminal, so a
timeout-kill can't strand `wt` in a background process group. `+m`
doesn't affect compinit/`compdef` detection — it only disables terminal
foreground management the probe never needed.
## Testing
Verified at the OS level under a PTY, replicating the probe's exact
spawn (interactive `zsh -ic`, stdin/stdout/stderr redirected, slow rc,
SIGKILL on timeout):
- **Unpatched:** `tcgetpgrp` of the controlling terminal moves to the
probe's process group and **stays there** after the kill (foreground
stolen) — the next terminal op suspends with SIGTTOU.
- **With `+m`:** the terminal foreground is **intact** after the kill,
and `wt config show` reaches the pager normally.
Added regression guards (`test_compinit_probe_disables_job_control`,
`test_zsh_probe_disables_job_control`) asserting both probes pass `+m`
before `-ic`. Full terminal-suspend behavior depends on a real
controlling TTY + a slow zsh rc, so it isn't unit-testable in CI without
flakiness; the guards lock in the load-bearing flag.
---
Closes #3322 — automated triage
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
commit 29fa13dc9553e2b40d7856f3ca714b0b88abb8d4
Author: Worktrunk Bot <w@worktrunk.dev>
Date: Thu Jul 9 12:13:47 2026 -0700
docs(code-signing): add SignPath Foundation code-signing policy page (#3366)
Addresses the first, gating step of option #1 (SignPath Foundation) from
#3355 — the fix for the `Trojan:Win32/Wacatac.B!ml` false positive on
the unsigned Windows binary.
## What this PR does
Adds a **code signing policy** page at
[worktrunk.dev/code-signing/](https://worktrunk.dev/code-signing/). A
published policy page is a hard eligibility requirement for the
[SignPath Foundation](https://signpath.org/terms.html) free OSS
code-signing program, so it's the natural thing to land first — the
application can't proceed without it. The page documents:
- **Certificate provenance** — the required attribution ("Free code
signing provided by SignPath.io, certificate by SignPath Foundation")
and the HSM-held key.
- **What's signed** — `git-wt.exe` in the Windows release archive /
winget; nothing else.
- **The build+sign pipeline** — tag-triggered, cargo-dist on GitHub
runners, SignPath action for signing, all reproducible from public
source.
- **Project roles** (Author / Reviewer / Approver), **per-release manual
approval**, and the **no-telemetry** privacy stance SignPath asks
projects to state.
The generated skill mirror
(`skills/worktrunk/reference/code-signing.md`) and
`docs/static/llms.txt` entry are produced by `test_docs_are_in_sync` —
not hand-edited.
> **Roster check:** the roles table lists @max-sixty as
Author/Reviewer/Approver. Adjust if anyone else should hold signing
authority before this is submitted to SignPath.
## What this PR deliberately leaves out
The `release.yaml` signing job and the SignPath account are a
**follow-up**, for two reasons:
1. **The concrete slugs don't exist yet.** The SignPath action needs
`organization-id`, `project-slug`, and `signing-policy-slug` — all
issued *after* the project is registered and approved.
2. **cargo-dist doesn't sign nativ…
max-sixty
added a commit
that referenced
this pull request
Jul 12, 2026
…surface (#3433) Follow-up to #3403, applying the deferred `--persist` decision and two sibling subtractions found sweeping the perf tooling for the same shape (a vestigial option or dual path guarding nothing). - **`wt-perf setup` is persist-only.** The `--persist` flag and the interactive "Press Enter to clean up (or Ctrl+C to keep)" path are gone — it was the one blocking, stdin-reading path in an otherwise scriptable tool, and it guarded little: default-path fixtures land in `/tmp/wt-perf-<config>`, which the next `setup <config>` run already wipes and rebuilds. `prune-real` never offered cleanup, so setup's exit behavior is now uniform. - **`timeline --repo` deleted.** It only restated the traced repo: `run_timeline` resolves the repo from `-C`/cwd, `--repo` defaulted to exactly that, and the only documented use passed the identical path to both flags. `--cold` now invalidates the repo being traced — the only coherent target. - **`wt_perf` lib surface tightened.** Seven helpers plus `PruneFixtureState` had no callers outside `lib.rs` and are now private (`ensure_rust_repo`, `clone_rust_repo_at`, `history_spread_shas`, `add_diverged_backdrop`, `create_mixed_repo_at`, `create_prune_real_repo_at`, `prune_fixture_state`); the public surface now matches what the benches and CLI actually use. Doc references to them are plain code spans rather than intra-doc links — rustdoc's `private-intra-doc-links` lint rejects public docs linking private items even under `--document-private-items`. Checked and deliberately kept: `wt-perf trace` and `wt-perf invalidate` (distinct documented workflows), the `TraceResult`/`CacheReport` re-exports (public field types of `TraceEntry`/`Profile`), and the `create_repo`/`create_repo_at` two-layer fixture API (both layers used). Gate green: 4387 tests, clippy/fmt/doctests/rustdoc (`-Dwarnings`) clean. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the design proposal from this branch's first commit (b22aced,
design/simplify-bench-perf.md— reviewable there; dropped from the merge candidate per thedesign/convention). The perf tooling's emission side was already canonical; this consolidates the consumption side around one split: capture/harness work inwt-perf, analysis inworktrunk::tracewithwt config state logs profileas its one CLI surface.The seven design items:
wt-perf cache-checkdeleted — its output was thecachefield oflogs profile --format=json. The tend statusline recipe now useslogs profile(its old pipe also fed human-format stderr into a JSON parser, so it's now correct as well as canonical).worktrunk::trace(src/trace/timeline.rs), sharingcommand_label/render_table/fmt_durwith the profile so the two views can't drift (they already had); drops wt-perf'stabwriterandinstadeps. Durations now render in the profile's fixed-point format.Profile.by_context(BY CONTEXT table + JSON array): per-worktree subprocess totals, the one analysis that previously required trace_processor SQL. The SQL sections inbenches/CLAUDE.mdand thetracemodule docs are replaced by alogs profilepointer; Perfetto stays for visual critical-path work.wt_perf::bench_wt) owns theBatchSize::PerIterationrationale once, replacing five inline copies. All runs now assert child exit status (benches/list.rspreviously ignored it).real_repokeeps only the 8-worktree variants (scaling shape stays on syntheticworktree_scaling),cow_copydeleted (benchmarked a hand-written serial copy against production rayon — a settled choice),remove_e2e/first_outputdeleted (duplicate offirst_output/remove). Ends three gist series with this PR as the single discontinuity point; saves roughly 15–20 min of the ~80-min daily bench run.CARGO_BIN_EXE_wt-perf): the dummybuilds.rsis gone and the nextest setup script builds onlymock-stub. Notecargo test --test integrationno longer runs these four tests; full-workspace runs still do.parse_configreturns aSetupConfigenum coveringmixed-W-B; the two fixture builders shareinit_bench_repo.Merge with main (#3401): the new prune benches and fixtures landed mid-flight in exactly these files; the resolution routes them through the consolidated API —
prune-M-Uis aSetupConfig::Prunevariant, the pair parsing is one sharedparse_pair, andprune-realkeeps its own cache-managed path (no--path, self-repairing).benches/prune.rskeeps its customiter_batchedarms deliberately: every variant asserts the candidate count from stdout, whichbench_wt's shape doesn't express — the exact carve-out the cache-handling docs describe.Two review passes (correctness + subtraction) then folded in:
CacheReporttrimmed to its same-context fields (the dropped six duplicatedProfilefields or counted cross-context noise the text report never showed),render_tablepads by display width (replacing an ASCII-only invariant the new BY CONTEXT table violated), the timeline's instant-event row gained test coverage, and Chrome args dropped the redundantduration_ms.Decisions surfaced, not taken (from the review passes): (a)
wt-perf setupcould become--persist-only, dropping the interactive Enter-to-cleanup path — a UX preference; (b) whether the daily benchmark cron's gist time series justifies its remaining ~60 min/day is a cost/value call. Also deferred: measuring whetherskeleton/coldadds signal overskeleton/warmbefore cutting it.Gate green throughout (4374 tests);
clippy --features shell-integration-testsclean;setup/timeline(warm, cold, chrome)/logs profile(text + JSON)/traceall driven end-to-end on a scratchmixed-4-8repo.