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>
Migrates the `wt switch` picker from skim 0.20.5 (tuikit backend) to
skim 4.8.0 (ratatui/crossterm), removes the `vendor/skim-tuikit/` patch
tree we carried against the old line, and makes the full test suite
green under the new backend.
## Why the vendor tree goes away
We vendored skim-tuikit for two patches: `alt-<digit>` key parsing and
an `Output::flush` `write_all` fix for dropped bytes under PTY pressure.
Both are moot in 4.8. Key parsing is native — `binds.rs` routes every
single character (digits included) through `KeyCode::Char` rather than a
hardcoded `alt-a..alt-z` arm list. The flush fix is subsumed by stdlib
`BufWriter::flush` in the ratatui backend, which loops over partial
writes correctly. So the migration carries zero vendor patches against
skim, and the build machinery that kept the vendored source alive
(Taskfile `vendor-diff`, flake `vendorSrc`/`extraDummyScript`) is
deleted too.
## The picker rewrite
skim 4.x is a different API. `WorktreeSkimItem::display()` now returns
`ratatui::text::Line` instead of an `AnsiString`. alt-r removal rides
skim's `reload(remove {})` token (the selected row's `output()`,
expanded into the reload command) instead of the old
`as_any().downcast_ref` path (which never worked across compilation
units) plus a signal file. Preview-tab switching and the progressive
list redraw are driven by `Skim::event_sender()` +
`Event::Render`/`Event::RunPreview`.
## The regression that motivated the bulk of this
skim 4.x renders on demand. There is no 100ms tuikit heartbeat
re-rendering the frame, so the progressive `wt switch` list rendered
blank while collection ran in the background — the original report. The
fix pokes `Event::Render` from the list-collect callbacks (throttled to
16ms). The same on-demand model surfaced four more regressions, all
fixed here and verified head-to-head against the 0.20 picker in a
terminal: preview-tab lag, current-row highlight, Shift-Tab back-cycle
(crossterm reports Shift-Tab under three distinct `KeyEvent` shapes, so
all three are bound), and alt-r removal (4.x `execute-silent` is
fire-and-forget and raced its reader).
## Help text and the test harness
Two things the integration suite caught that the unit tests did not (the
prior pass ran `--lib --bins` only):
- **Styled help.** skim 0.20 transitively pulled in
`clap/unstable-markdown`, which renders worktrunk's `///` doc-comments
as styled help. skim 4 with `default-features = false` drops it, so help
reverted to raw text (`[experimental]` → `\[experimental\]`). Restored
by depending on `unstable-markdown` explicitly, keeping help output
identical and the `test_help` / `test_step_alias` /
`test_docs_are_in_sync` snapshots passing unchanged.
- **PTY harness.** skim 4.x queries cursor position (`ESC[6n`) at
startup in partial-height mode and blocks in `select()` for the reply;
`portable_pty` is a bare PTY and never answered, so every
`switch_picker` test failed init with "Cursor position detection timed
out." The Unix harness now answers the query, mirroring the existing
ConPTY responder. skim 4.x also draws the list/preview separator one
column left, so the snapshot panel-split columns shifted to match. The
regenerated picker snapshots show two cosmetic changes: the match
counter no longer overlaps the preview tab header, and the HEAD column
shows the full short-SHA instead of a truncated one.
## Minimal-versions CI
The skim 4.8 dep tree pins tighter floors than our manifests declared,
so the nightly `minimal-versions` job needed updating. It now runs `-Z
direct-minimal-versions` — minimize only our own direct deps and let
transitive crates resolve normally — and the manifests raise each
under-specified floor to the minimum the workspace builds against
(largely mirroring skim 4.8's own requirements). Full `-Z
minimal-versions` would instead drag in skim's transitive TUI/image
stack (ansi-to-tui, ratatui's `instability` macro, color-eyre,
ratatui-image → image/avif), whose crates under-declare their floors and
don't compile at the picked versions; direct minimization confines the
check to floors we own, so no transitive pins are needed. Normal
resolution (the committed `Cargo.lock`) is unchanged. Full floor list
and the `signal-hook` libc-pin detail are in the `ci(min-versions)`
commit message.
## Reviewing
Start at `src/commands/picker/mod.rs` (the `run_skim` entry point, the
action keybinds, and `parse_reload_remove_token`), then
`progressive_handler.rs` (the render pokes) and `items.rs` (the
`Line`-based `display()`). Test-harness changes are in
`tests/common/pty.rs` (the `ESC[6n` responder) and
`tests/integration_tests/switch_picker.rs` (the panel-split columns).
The rest is the dependency swap, the vendor and build-machinery
deletions, and regenerated snapshots.
All 3980 tests pass (`cargo run -- hook pre-merge --yes`), including the
`shell-integration-tests` PTY suite that exercises the picker end-to-end
across the list, previews, scroll, create/remove, and accept flows. No
automated test drives a real terminal; the interactive surface was also
checked by hand against the 0.20 picker.
> _This was written by Claude Code on behalf of max_
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per @max-sixty's [direction in
#2838](https://github.com/max-sixty/worktrunk/issues/2838#issuecomment-4509447593):
revert the docs portion of #2840 and keep the code. Docs continue to
recommend `pre-start`/`post-start`; both names work in code so anyone
who already followed the briefly-changed docs (e.g. @EcksDy) isn't
stranded once a release ships these aliases.
## User-visible — back to `pre-start`/`post-start`
- README, docs site, skill mirrors, `dev/*.example.toml`,
`plugins/worktrunk/README.md`, `flake.nix`, `.config/wt.toml`
- `src/cli/mod.rs` / `src/cli/config.rs` / `src/cli/step.rs` /
`src/help.rs` after_long_help and example snippets — and the auto-synced
`docs/content/` and `skills/worktrunk/reference/` mirrors
- `wt hook --help` canonical subcommand names; completion advertises
`-start` only
- `HookType` Display via strum, serde `rename`, and clap `ValueEnum`
name — all `pre-start`/`post-start`. The Rust variant identifiers stay
`PreCreate`/`PostCreate` (internal; we already paid for that rename in
#2840, and now the eventual flip is a Display-only change)
- `HooksConfig` serde canonical fields
## `*-create` still works (kept code)
- `wt hook pre-create` / `post-create` — CLI alias on the canonical
subcommand
- `pre-create` / `post-create` in config: top-level, `[hooks.*]`, and
per-project, in string, `[table]`, and `[[array-of-tables]]` form.
Mechanism: serde `alias = ...` on the field, plus a silent in-memory
rename in `migrate_content()` so the round-trip in `unknown_tree`
doesn't flag table forms as schema-unknown.
- The pre-0.32.0 `post-create` fatal-load-error machinery stays removed
— the name is reclaimed, and both forms load without error.
## Smaller bits
- `valid_user_config_keys()` / `valid_project_config_keys()` append
`pre-create` / `post-create` so the unknown-field round-trip skips them.
`test_valid_*_keys_all_deserialize` skips both aliases (they can't sit
alongside the canonical without a duplicate-field error).
- `DEPRECATED_SECTION_KEYS` drops the `pre-start`/`post-start` entries
#2840 added — `pre-start`/`post-start` are canonical again.
- `find_pre_start_from_doc` / `find_post_start_from_doc` /
`find_renamed_hook_key` / `is_non_empty_item` /
`migrate_start_hooks_doc` and their tests are removed; the migration
direction flips via a new `migrate_create_hooks_doc` (silent, mirrors
the prior shape).
- Test files `e2e_shell_post_create.rs` and `post_create_commands.rs`
rename back to `_post_start_` (via `git mv`, so the rename shows as a
rename).
## Testing
`cargo run -- hook pre-merge --yes` — 3806 tests pass; the 10 failures
are all `case_4` of `shell_wrapper::unix_tests::*` (nu-shell case; `nu`
isn't installed in this runner; same failures occur on `main`).
Also manually verified that a fresh `wt switch --create` against a
project config with `[post-create]` loads cleanly with no unknown-field
warning and the hook fires as `post-start`.
## Follow-up
Per @max-sixty: in a couple of weeks, once a release with
both-names-work is out and users have had a chance to upgrade, the docs
flip is straightforward (most of it is in `src/cli/mod.rs`'s
`after_long_help` and the doc-sync test propagates).
Re #2838.
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Phase 1 of the staged hook rename tracked in #2838: the worktree-creation hooks `pre-start`/`post-start` become `pre-create`/`post-create`. The old names keep working with no deprecation warning yet (Phase 2, months out, adds the warning).
## What changes
- `pre-create`/`post-create` are canonical everywhere: the `HookType` enum, the `HooksConfig` serde fields, the `wt hook` CLI, completion, and all docs.
- Old names keep working: `migrate_content()` rewrites `pre-start`/`post-start` config keys to `-create` before serde, and `parse_hook_type` accepts the old CLI names as silent aliases. `wt config update` rewrites them on disk; `wt config show` shows the migration diff. `wt hook <type>` execution and `wt hook show` both accept the old names; completion and `--help` advertise only the canonical names.
- `detect_deprecations()` flags the old keys so `update`/`show` act on them, but `format_deprecation_warnings()` stays silent (Phase 2 adds the warning). A new empty-warnings guard in `check_and_migrate` keeps a `-start`-only config from emitting a stray hint.
- The dead pre-0.32.0 `post-create` machinery is removed: the fatal `POST_CREATE_REMOVED_MSG` load error, the vestigial `HooksConfig.post_create` merge-fold, and `find_post_create_from_doc`. `post-create` is reclaimed as the canonical background creation hook.
## Semantic flip
Before v0.32.0, the key `post-create` named a *blocking* hook. It now names the *background* one.
Since v0.44.0, a pre-0.32.0 `post-create` config is a fatal load error on the `check_and_migrate` paths: `ProjectConfig::load` and user/system config loading, which fire on essentially every `wt` command. A repo carrying one has been unusable ever since. The one path that skips that check is `project_config_at_ref` (the base-ref read behind `wt switch --create`), which applies only structural migration. A pre-0.32.0 `post-create` surviving solely on a base ref, never checked out into a worktree, would now load as a background hook rather than folding into the blocking `pre-start`. That edge case is accepted: once `post-create` is valid again, reclaiming the name and detecting the dead key are mutually exclusive.
## Reviewing this diff
205 files, but the substance is ~36 files under `src/`. The rest is regenerated snapshots and auto-synced doc mirrors. Start with:
- `src/config/deprecation.rs` — detection (`find_renamed_hook_key`), migration (`rename_hook_key`), removal of the fatal block, the empty-warnings guard, and the `DEPRECATED_SECTION_KEYS` entries that stop unknown-field detection from flagging the migrated keys.
- `src/config/hooks.rs`, `src/git/mod.rs` — the serde field and enum renames.
- `src/config/project.rs` — `ProjectConfig::load` deserializes `check_and_migrate`'s migrated content, so a current-worktree config using the old keys loads into the canonical fields.
- `src/cli/hook.rs`, `src/commands/hook_commands.rs`, `src/completion.rs`, `src/main.rs` — the CLI alias layer; `wt hook show` accepts the old type names as hidden value-parser aliases.
- `src/cli/mod.rs` — the `wt hook` docs, including the soft-deprecation note linking #2838.
The ~93 modified snapshots also pick up deterministic env-block lines (`GIT_*: ""`, `LLVM_PROFILE_FILE`) that pre-existing snapshots already carry. That is stale-snapshot drift surfaced by the regeneration, not a behavior change.
## Testing
Full suite green (3799 tests). New coverage: `snapshot_migrate_start_to_create` (migration preserves value shape and position), `test_deprecated_start_hook_key_runs_silently` and `test_standalone_hook_start_alias_runs_silently` (old config and CLI names run with no warning), `test_config_show_displays_start_hook_migration` (`config show` reveals the diff without an "unknown field" warning), and `test_hook_show_accepts_deprecated_start_hooks` (a current-worktree config using the old keys loads, and `wt hook show` takes both the canonical and the deprecated type arguments).
Part of #2838.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Restore the `nix-flake` nightly job (disabled in #2647) and fix the
sandbox-specific test failures it surfaces. With this merged, the
nightly cron run is back to gating against packaging-environment bugs.
## What's in here
- **Build-mode snapshot redaction**: 49 snapshots hardcoded
`target/debug/wt` in "Invoked as:" and diagnostic output, which broke
under crane's release builds. Collapse both modes to
`target/[BUILD_MODE]/wt` in the shared insta filter.
- **Nix sandbox env**: add `pkgs.python3` and `pkgs.procps` to
`worktrunk-tests`' native build inputs (needed by argv-quoting,
post-start, and pgid-invariant tests). Replace
`#!/usr/bin/env python3` with the absolute python path resolved at
script-write time, since `/usr/bin/env` doesn't exist on NixOS.
- **PTY filter ordering**: extend `add_pty_binary_path_filters`'
alternation to match the `[BUILD_MODE]` placeholder so PTY snapshots
still collapse to `[BIN]` after the prelude rewrite runs first
(caught by worktrunk-bot review).
- **One inherited-CWD test fix**: `test_config_init_already_exists`
branched on whether the inherited CWD had a project config — pin
it to a no-config tempdir so the snapshot is deterministic across
cargo and nix.
## Test plan
- [x] `nix-flake` job passes in CI (1633 / 1633, was 54 failing)
- [x] All required `ci` checks pass
- [x] Auto-reviewer approved
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Adds a `nix-flake` job to `.github/workflows/nightly.yaml` that runs
`nix flake check`.
- Adds a `worktrunk-tests` flake check that runs `cargo test` (default
features) against `pkgs.lib.cleanSource ./.`, which keeps `tests/`
fixtures and `src/` `.snap` files visible to the test build. The
production package src filter stays narrow.
- Wires the new job into the existing `create-issue-on-nightly-failure`
flow so failures open the standard nightly-failure issue.
## Why
Surfaces packaging-environment bugs before nixpkgs maintainers hit them.
The canonical example is #2624 — a unit test that depends on the process
CWD being inside a git repo, which fails in the nix build sandbox where
source is extracted from a tarball without `.git`.
`shell-integration-tests` is intentionally not enabled — it requires
zsh/fish/nushell + PTY, and nextest's InputHandler conflicts with
interactive shells in CI sandboxes (see CLAUDE.md → "Shell/PTY
Integration Tests"). Worth a follow-up if/when the basic check is
stable.
Cold runs are slow (~10–15 min build + test). Nightly cadence absorbs
the latency. If we want to cut that we can add a binary cache (cachix or
FlakeHub) in a follow-up.
## Test plan
- [ ] First scheduled run (or `workflow_dispatch`) succeeds.
- [ ] If `worktrunk-tests` fails, the failure surfaces against the
existing nightly-failure issue.
- [ ] Locally: `nix flake check` passes on a machine with nix installed
(not validated — no nix on this dev host).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
Implements the first two proposals from [#2185
(comment)](https://github.com/max-sixty/worktrunk/pull/2185#issuecomment-4234599092):
- **`flake.nix` reads the Rust channel from `rust-toolchain.toml`** —
one source of truth for rustup and Nix. The weekly MSRV bump now
propagates to the Nix toolchain automatically, turning silent drift into
a loud "channel not in locked rust-overlay" error if the lock also falls
behind.
- **Weekly maintenance playbook now runs `nix flake update`** — covers
the residual case where a newly pinned channel is newer than the locked
`rust-overlay` revision. The `flake.nix` row in the files table is gone
since there is nothing to edit there anymore.
Extensions (`rust-src`, `rust-analyzer`) stay in `flake.nix` rather than
moving into `rust-toolchain.toml` — putting them in the toolchain file
would force rustup users and CI to download them too, and they are only
useful inside the Nix dev shell.
Skipped proposal #3 (adding a `nix build` CI job) — that's a bigger
change and worth a separate discussion.
## Test plan
- [ ] `nix build .#worktrunk` succeeds (could not verify in CI runner —
no `nix` installed)
- [ ] `nix develop -c cargo build` succeeds
- [ ] `nix flake check` passes
Co-authored-by: worktrunk-bot <worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
I am using the home-manager module and this warning is a bit annoying.
```bash
evaluation warning: 'system' has been renamed to/replaced by 'stdenv.hostPlatform.system'
```
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use WT_TEST_EPOCH instead of SOURCE_DATE_EPOCH for test time control
SOURCE_DATE_EPOCH is a reproducible builds standard commonly set by NixOS/direnv
in development shells. When set to a past timestamp (typical for builds), the
Age column in `wt list` incorrectly shows "future" for all commits.
Switch to WT_TEST_EPOCH which is only set by our test harness, leaving
production users unaffected.
Fixes#763
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: restore PTY test snapshots with WT_TEST_EPOCH
The previous commit deleted PTY-based test snapshots that can only be
regenerated in CI (they require Linux/macOS PTY support). This commit
restores those 46 snapshots from main and updates them to use
WT_TEST_EPOCH instead of SOURCE_DATE_EPOCH.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: wrap bare URL in angle brackets for rustdoc
RUSTDOCFLAGS='-Dwarnings' fails on bare URLs in doc comments. Wrap the
GitHub issue link in angle brackets to make it a proper hyperlink.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add Nix flake for packaging
Use crane for efficient Rust builds with dependency caching.
Provides packages, dev shell, and CI checks (clippy, fmt, tests).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: skip tests in flake build
* fix: anchor Nix result patterns to repo root
Use `/result` instead of `result` to avoid accidentally ignoring
legitimate `result` files/directories in subdirectories.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Maximilian Roos <m@maxroos.com>