Skip to content

feat(config)!: split the Pi and oh-my-pi plugin installers - #4135

Merged
max-sixty merged 7 commits into
mainfrom
feat/split-pi-and-omp-plugin-installers
Sep 16, 2026
Merged

max-sixty merged 7 commits into
mainfrom
feat/split-pi-and-omp-plugin-installers

Conversation

@worktrunk-bot

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

Copy link
Copy Markdown
Collaborator

wt config plugins pi installed an oh-my-pi hook. It wrote a HookAPI module to ~/.omp/agent/hooks/pre/worktrunk.ts and read $PI_CONFIG_DIR / $OMP_PROFILE / $PI_PROFILE — all oh-my-pi's. Pi (earendil-works/pi) loads ExtensionAPI extensions from ~/.pi/agent/extensions/, so a Pi user who ran that command got a file Pi never reads, and wt config show printed a PI section off which omp.

This splits them into two commands, because they are two agents:

wt config plugins omp wt config plugins pi
Writes ~/.omp/agent/hooks/pre/worktrunk.ts ~/.pi/agent/extensions/worktrunk.ts
Source dev/omp-hook.ts (HookAPI) dev/pi-extension.ts (ExtensionAPI)
Config roots $PI_CONFIG_DIR, $OMP_PROFILE/$PI_PROFILE, $PI_CODING_AGENT_DIR $PI_CODING_AGENT_DIR
Detected by which omp which pi

wt config show now renders PI and OH-MY-PI as separate sections, each gated on its own binary.

The pi rename is the breaking part

wt config plugins pi install has meant oh-my-pi since 0.77.0, and it now means Pi. Already-installed hooks keep working — the path is unchanged — so what breaks is the command name in scripts and dotfiles. Both halves of that now say so rather than failing quietly:

  • pi install would write a Pi extension for someone who has only oh-my-pi, and nothing downstream would mention it: the PI section of wt config show is gated on which pi, which they don't have. It now warns before writing — ▲ oh-my-pi is on PATH and Pi is not; for the oh-my-pi hook, run wt config plugins omp install.
  • pi uninstall would look at ~/.pi/agent/extensions/, print Plugin not installed, and exit 0 — a no-op that reads as a completed removal while the hook sits untouched under ~/.omp/. It now adds ↳ An oh-my-pi hook is installed; to remove it, run wt config plugins omp uninstall.

An oh-my-pi user who reruns pi install despite the warning is still routed home by wt config show: the hook source text changed in the rename, so the byte-compare misses and the OH-MY-PI section prints Plugin outdated. To update, run wt config plugins omp install.

The open decision is whether that's enough, or whether pi should warn-and-redirect for one release before it changes meaning. The flip is what's implemented here; the deprecation is a small change on top if preferred.

Provenance and verification

The first commit is @ashebanow's, from feat/pi-and-omp-plugin-targets, cherry-picked unmodified — this PR exists because #4133 asked for the implementation and the branch was already there and already verified. @ashebanow, if you'd rather carry this yourself, say so and I'll close it.

That verification is the part CI can't do: the commit message records an end-to-end run against pi 0.85.1 (extension discovered at ~/.pi/agent/extensions/, agent_start → 🤖, agent_end → 💬, session_shutdown → clear, worktree as cwd) and oh-my-pi 18.1.16 for all four config-root variables. I checked the static half against pi's extensions doc and core/exec.ts: the install path is auto-discovered, the import matches, and ExecOptions carries cwd.

The remaining four commits are mine. One moves the Pi extension's "waiting for input" marker from agent_end to agent_settled, which is what pi's own docs direct status integrations at — under agent_end an auto-retry or auto-compact flips wt list to 💬 and back to 🤖 while the agent never stopped. agent_settled has no oh-my-pi counterpart, which is why the hook keeps agent_end. The rest are the two redirects above, the FAQ's delete inventory and a stale opencode.rs docstring the split left behind, and a consolidation: wt config show had two copies of the same twenty-line "installed / outdated / not installed" section renderer and the new OH-MY-PI section would have made a third, so all three now call one function that differs only in the heading and the install command. Its config show output is byte-identical — those snapshots are untouched.

Local test run

cargo clippy --all-targets --all-features is clean; cargo test --test integration is 2178 passed / 2 failed and cargo test --lib --bins is 1543 passed / 1 failed. All three failures are sandbox limitations unrelated to this change, in copy tests that bind a unix socket (PermissionDenied on socket(AF_UNIX, …)) or assert a 0644 mode the sandbox's umask writes as 0664. Snapshots were regenerated with INSTA_UPDATE=always — cargo-insta isn't available in this sandbox.

One finding from the review is deliberately not fixed here: the default test env pins WORKTRUNK_TEST_{CLAUDE,CODEX,OPENCODE,GEMINI}_INSTALLED=0 and neither PI nor OMP, so config show snapshots fall through to the host's PATH. Pinning both in src/testing/mod.rs rewrites the recorded env: block in all 1027 snapshots that carry one, which is a separate change from this one. The two snapshots this PR's new pi install warning would have made PATH-dependent pin the CLIs themselves instead.

Closes #4133.

ashebanow and others added 2 commits September 16, 2026 07:24
`wt config plugins pi` targeted oh-my-pi: it wrote a `HookAPI` hook to
`~/.omp/agent/hooks/pre/worktrunk.ts`. Pi (earendil-works/pi) is a different
agent — it loads `ExtensionAPI` extensions from `~/.pi/agent/extensions/`, has
no profile concept, and reads none of the config-root variables the omp
installer honors — so one command cannot serve both.

- `wt config plugins pi` writes an extension to
  `~/.pi/agent/extensions/worktrunk.ts`, honoring `$PI_CODING_AGENT_DIR` and
  nothing else. Verified end-to-end against pi 0.85.1: pi discovers the
  extension at that path and fires `agent_start` → 🤖, `agent_end` → 💬,
  `session_shutdown` → clear, with the worktree as cwd.
- `wt config plugins omp` takes over the hook installer unchanged, including
  `$PI_CONFIG_DIR`, `$OMP_PROFILE`/`$PI_PROFILE`, and `$PI_CODING_AGENT_DIR`
  (all four confirmed against oh-my-pi 18.1.16).
- `wt config show` reports PI and OH-MY-PI as separate sections, each detected
  by its own binary (`pi`, `omp`), so an installed oh-my-pi no longer reads as
  an installed Pi.

BREAKING CHANGE: `wt config plugins pi` installed the oh-my-pi hook. That
installer is now `wt config plugins omp`, and hooks already installed at the
same path keep working; `wt config plugins pi` installs the Pi extension.
Before the split, `wt config plugins pi uninstall` removed the oh-my-pi
hook. Anyone repeating that command now looks at Pi's extension path,
finds nothing, and gets a bare "Plugin not installed" — a no-op that
reads as a completed removal while their hook sits untouched under
oh-my-pi. Name the command that removes it.
…function

Splitting the Pi-family installer left `wt config show` with a third copy
of the same twenty lines: heading, then installed / outdated / not
installed, differing only in the heading and the command that writes the
file. Fold OpenCode, Pi, and oh-my-pi onto one renderer they all call.

Output is unchanged — the config_show snapshots are untouched.

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Four fixes going onto this branch next, plus two things left open. The renderer consolidation in cc9eb96c reads clean and its config show output is byte-identical (the snapshots pass untouched); it also deleted the two lines that were codecov/patch's only misses on the previous head, so that check needs re-reading rather than the arithmetic I'd worked out.

Fixing

The Pi extension sets the "waiting for input" marker on the wrong event. Pi's extension docs say of agent_end: "Pi may still auto-retry, auto-compact and retry, or continue with queued follow-up messages. Use agent_settled for status integrations that need to know Pi will not continue running automatically." With agent_end, an auto-retry or an auto-compact-and-retry flips wt list to 💬 and then straight back to 🤖 while the agent never stopped working — the one distinction the marker exists to make. Pi's own status-integration example, examples/extensions/titlebar-spinner.ts, uses agent_start / agent_settled / session_shutdown: the same three-event shape as this extension. agent_settled has no counterpart in oh-my-pi (zero hits in can1357/oh-my-pi), which is why the hook carried agent_end over and why the divergence is Pi-only.

The FAQ's delete inventory missed omp. "What can Worktrunk delete?" → Other cleanup still reads wt config plugins opencode uninstall / wt config plugins pi uninstall. The create-side table and the "To remove:" line above it both picked up the new command; this bullet is the one CLAUDE.md → Data Safety names as the review checklist for a change to the deletion surface, so it's the one that matters.

A doc comment points at code that moved. opencode.rs's opencode_plugins_dir docstring says the empty-value handling matches "PI_CONFIG_DIR in config::pi" — config::pi no longer reads PI_CONFIG_DIR; config::omp does.

config plugins pi uninstall --help had no snapshot while all three omp pages got one. The comment above those cases in help.rs gives the reason they exist ("The uninstall pages describe when each command skips, which depends on what the harness's config still holds"), and pi uninstall is the one handler in this PR that gained conditional output. Adding the case, plus a line in its after_long_help naming the oh-my-pi redirect, so the page describes the behavior the snapshot pins.

Open

Install has no redirect, and a misfire there is invisible. uninstall got a hint; install is the command that sits in dotfiles, and it flips outright. An oh-my-pi user who reruns wt config plugins pi install now writes ~/.pi/agent/extensions/worktrunk.ts, and wt config show never mentions it — the PI section is gated on which pi, which they don't have. The description's argument that config show routes them ("OH-MY-PI … Plugin outdated") holds for the hook they still need, but the stray file under ~/.pi has no surface at all. The symmetric fix is a hint on install when omp resolves and pi doesn't, which also bears on the deprecation-release question the description leaves open — so not mine to pick.

The default test env pins WORKTRUNK_TEST_{CLAUDE,CODEX,OPENCODE,GEMINI}_INSTALLED=0 but neither PI nor OMP, so config show snapshots fall through to the host's PATH. That was a latent dependency on which omp; this PR adds which pi, and anyone who verified this branch against pi 0.85.1 has pi installed. Pinning both rewrites the recorded env: block in every affected snapshot, which is more churn than the flake warrants without your say.

Pi's extension docs say `agent_end` is not the end — Pi may auto-retry,
auto-compact and retry, or pick up a queued follow-up message — and direct
status integrations at `agent_settled`, which fires once Pi will not
continue on its own. On `agent_end` the marker flips to 💬 and back to 🤖
mid-run, reporting "waiting for input" while the agent is still working.

Pi's own status-integration example (examples/extensions/titlebar-spinner.ts)
uses agent_start / agent_settled / session_shutdown, the shape this extension
already has. oh-my-pi has no `agent_settled`, so dev/omp-hook.ts keeps
`agent_end`.
The FAQ's "What can Worktrunk delete?" inventory still listed only
opencode and pi uninstall, though the split gave omp its own removal — and
that section is the one CLAUDE.md names as the review checklist for a change
to the deletion surface.

opencode.rs's path-resolution docstring cited `PI_CONFIG_DIR` in
`config::pi`, which no longer reads it; `config::omp` does.

`config plugins pi uninstall --help` had no snapshot while all three omp
pages did, and it is the one handler here that gained conditional output —
so its page now describes the oh-my-pi redirect and the snapshot pins it.
@worktrunk-bot

Copy link
Copy Markdown
Collaborator Author

The re-read of codecov/patch I said the refactor called for: it fails again on 9291a0aa, for the same unreachable reason but at new lines, so the arithmetic below supersedes what the review quoted.

100 of 103 patch lines hit, 97.1%. All three misses are in src/commands/config/show.rs, and all three are the )?; closing one render_file_plugin_status(…) call — the Err edge of a function whose only fallible operations are writeln! into a String, which fmt::Write for String never fails. They arrived with cc9eb96c's consolidation, which replaced the two equivalent misses on 9fb083ef (and the two that predate the PR on main, in what was render_pi_status / render_opencode_status) with one per call site. So the count moved from 2 to 3 while the reachable surface went from three copies to one — no test can close any of them, and neither commit I pushed touches those lines.

Per CLAUDE.md → Coverage that leaves this in the "no deterministic trigger" bucket: it needs your approval before merge rather than a test.

test (linux), code-coverage, lint, fast-checks, check-docs, feature-check, and affected tests (linux) are green on that commit. test (macos) and test (windows) were still running when the poll budget ran out — unverified rather than green.

`wt config plugins pi install` meant oh-my-pi until the split, so anyone
running it out of muscle memory who has oh-my-pi and no Pi now writes an
extension nothing on their machine reads. Nothing downstream tells them:
`wt config show` gates its PI section on `which pi`, so the stray file has
no surface at all. Warn before writing, and name the oh-my-pi installer.

The section renderer that check exposed becomes infallible while it is
being touched — it writes into a `String`, where `fmt::Write` cannot fail,
so returning the section instead of threading `&mut String` and
`anyhow::Result` drops an error arm no caller could reach.

`test_pi_install_writes_extension` pins both CLIs rather than letting the
developer's PATH decide whether the new warning appears in its snapshot.

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Both tests that reach the new pi install redirect pass --yes, so the eprintln!() that separates the warning from the install prompt never runs under test — the spacing has no regression pin, and the line would land as a fresh codecov/patch miss on a PR that already needs a coverage decision. Pushing test_pi_install_redirect_precedes_the_prompt: a snapshot of the declined path, in the shape test_opencode_install_prompt_declined already uses, which covers the line and pins the two-message layout.

The deprecation-vs-flip decision in the description is still open.

Both tests reaching the new redirect pass `--yes`, so the blank line that
separates the warning from the install prompt never ran under test: its
rendering had no regression pin, and the line would have landed as a fresh
`codecov/patch` miss.

The new case declines the prompt (no `--yes`, piped stdin), the shape
`test_opencode_install_prompt_declined` already uses, and snapshots warning,
blank, and prompt together.
@worktrunk-bot

Copy link
Copy Markdown
Collaborator Author

That coverage exemption is no longer needed — codecov/patch is success on a87d2734, at 99.08% (108 of 109). The three )?; misses are gone rather than waived: the section renderer now returns the String instead of writing into &mut String behind anyhow::Result, so the Err edge that fmt::Write for String can never take isn't there to miss. The one remaining miss is the blank-line branch in pi.rs, which d0b22229 covers.

test (macos) and test (windows) are the only checks still outstanding, on the current head.

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

d0b22229 is clean — it covers the one branch the earlier heads left uncovered and pins the two-message layout. No findings on it.

One thing this PR has never had named, though, and it is the reason to hold it for a human rather than anything wrong with the code.

The uninstall surface was restructured, and .claude/skills/running-tend/references/review-pr.md → Data-Loss Surface holds a PR that does that. Before this branch, wt config plugins pi uninstall removed $PI_CONFIG_DIR|~/.omp/agent[/profiles/<name>]/hooks/pre/worktrunk.ts. After it, that command removes ~/.pi/agent/extensions/worktrunk.ts and the new wt config plugins omp uninstall owns the old path. Both reach std::fs::remove_file through uninstall_file_plugin in src/commands/config/mod.rs.

What I checked, so the hold is about the decision rather than a suspected bug:

  • omp.rs's omp_agent_dir / plugin_path are a faithful move of the pre-split pi.rs logic — same PI_CODING_AGENT_DIR precedence, same OMP_PROFILE/PI_PROFILE handling, same .omp default. A named-profile user's hook is still reachable.
  • uninstall_file_plugin keys its removal on target.exists(), not on a content match, so the rename of the hook source (dev/pi-plugin.ts → dev/omp-hook.ts) does not strand an already-installed old hook — omp uninstall still removes it.
  • Each target is a worktrunk-authored file the paired install regenerates byte-for-byte from include_str!, which is the reference's own "everything under its target can be regenerated" exemption.

So the contents test points at exempt, but the command that deletes a user's file changed meaning in the same release that the command that writes one did, and that pairing is exactly what the hold exists to put in front of a person. Requesting @max-sixty.

@max-sixty
max-sixty merged commit a869299 into main Sep 16, 2026
40 checks passed
@max-sixty
max-sixty deleted the feat/split-pi-and-omp-plugin-installers branch September 16, 2026 15:19
@worktrunk-bot worktrunk-bot mentioned this pull request Sep 16, 2026
ashebanow added a commit to ashebanow/nix-config that referenced this pull request Sep 16, 2026
Both changes the fork carried landed upstream: the Pi/oh-my-pi split
(max-sixty/worktrunk#4135 — the bot's version of our branch, so our
proposal; upstream closed #4133 by merging it) and the
`stdenv.hostPlatform.isDarwin` fix (#4132). Neither is in a release yet:
v0.77.0 is still the latest tag and main runs ahead of it under
release-please.

So the pin is no longer a fork necessity, just a hold until a release
ships both — and pinning `main` would re-inherit the unreleased-commit
churn the pin exists to avoid. Record that, name BOX-190 as the ticket
carrying the removal steps, and fix the branch description now that
`pi-omp-split` exists alongside the `feat/` branch.

Comment only; no functional change.
max-sixty added a commit that referenced this pull request Sep 16, 2026
Version bump to 0.78.0 plus the changelog section for this release, a comment-preservation fix the release review turned up, a PTY test-filter fix, and merges of `main` (commits that landed during the review, including #4124, #4122, #4141, #4140 and #4145).

**Bump level**: minor. `cargo semver-checks` reports 2 breaking library changes — a new `leftover_branch` field on the `GitError::WorktreeCreationFailed` struct variant, and `DEPRECATED_TEMPLATE_VARS` removed. Pre-1.0, so a breaking change takes a minor bump.

## Comment-preservation fix

A config save — declining the commit-generation offer, say — dropped the trailing comment on every value it rewrote. That was already true in v0.77.0 for a line the save itself changed (`skip-commit-generation-prompt = false  # note` → `true`, reproduced on the v0.77.0 binary). #4080 widened it to lines the command never touched: retired template variables now migrate on every load, so the next unrelated save rewrote any line naming one. #4080's own PR said this "needs a call before merge" and merged without one.

- `replace_keeping_decor` keeps the value's decor (spacing after `=`, trailing comment) when `merge_tables` replaces a scalar. Comments *above* a line sit on the key and were never at risk.
- `main`'s #4120 independently reworked the inline-table arm and moved a key's leading comment onto the rewritten header, but documented that a trailing comment after the closing brace "is still dropped". `replace_inline_with_table` now carries it after the header's `]`, so both of its callers keep it.
- Tests: the existing comment-preservation test goes back to a retired name (the only fixture that exercises the replacement) and adds a comment on the mutated line; a new test snapshots and re-parses an inline `[projects]` entry. Each fails without its half of the fix.

Not fixed, and identical on v0.77.0: a save deletes an explicitly written *default* value (e.g. `skip-commit-generation-prompt = false  # note`) as a stale key, comment included.

## `--help` correction

#4104 moved the OpenCode plugin path to etcetera's `Xdg` strategy, which uses `$XDG_CONFIG_HOME` only when absolute — a deliberate, documented choice — but `wt config plugins opencode --help` still said install mirrors OpenCode's precedence. OpenCode reads a relative value as-is, so the help now says an empty `$OPENCODE_CONFIG_DIR` and an empty or relative `$XDG_CONFIG_HOME` are ignored.

## Data-loss surface review

Independent finders swept the full diff, including the five commits merged from `main`. Nothing holds the tag; candidates were adjudicated against v0.77.0 binaries:

- **Fixed in this release, reproduced on v0.77.0**: #4104 stops `wt config shell install` (with Nushell) deleting a `nushell/vendor/autoload/wt.nu` under the current directory when `$XDG_CONFIG_HOME` is empty or relative; #4120 stops a save or migration from writing an inline section's comment inside `[commit]` brackets, which made the config unparseable, and from dropping unknown keys.
- **`wt step prune --min-age` doesn't apply where no reflog exists** — pre-existing, proven by building the pre-#4077 binary. `git clone --bare` and `git fetch` into `refs/heads/*` write no reflog, so in the bare layout `tips-patterns.md` recommends every branch starts unprotected. Docstring corrected here; behavioral fix is follow-up.
- **`wt config update --output <path>` replaces any named file whole**, no existence check or prompt — pre-existing, follow-up.
- **`wt config shell install fish --yes` replaces a hand-written wrapper at the XDG path** — accepted ("ownership is the path", predates #3602), newly reachable only for users with `$XDG_CONFIG_HOME` set. The FAQ's delete inventory now cross-references it.
- **`--var <retired-name>` binds a name nothing references** after #4080 — inert, no shipped usage; follow-up.
- **#4135 migrates nothing**: an existing oh-my-pi hook stays put, `pi uninstall` points at `omp uninstall`. `PI_CODING_AGENT_DIR='~/x'` is used literally (Pi expands `~`) — creates a stray directory, deletes nothing; follow-up.

## Changelog

37 entries; each new or edited entry was re-verified against v0.77.0 until a pass came back clean (six passes in all). Passes caught, among others: the OpenCode `-x` recipe was wrong since 0.76.0, not 0.75.0; #4080's JSON reaches every hook, not just `pre-start`/`post-start`; a quoted git error string git never prints; a copy-button overlap that existed only inside this release; #4104's relative-`XDG_CONFIG_HOME` OpenCode change described as a fix when OpenCode reads that path as-is; and which commands a broken inline section stopped. **Fixed** leads with the data-loss entries.

## Open PRs checked against v0.77.0

- **#4124** fixed a bug new in this release, caused by #4080, and has landed and been merged in here. The hand-rolled template-variable scanner it extends is slated for removal; see its commit message.
- **#4112** — no data-loss regression vs v0.77.0; two small non-safety regressions from #4111 (an uncapped auto-staging file list, a narrow `Nothing to commit` refusal under `status.showUntrackedFiles = no`). Not merged, by decision.
- **#4122** (merged in) and **#4123** — pre-existing bugs, identical on v0.77.0.

## PTY filter fix

`test (macos)` failed once on a shell's failed-`setpgid` diagnostic that landed straight after the capture's trailing `\x1b[0m`, with no newline between, so #4136's line-anchored filter missed it. The filter now also matches right after an SGR escape and keeps the escape; its test gains that shape and fails on the old pattern.

## Validation

- `cargo run -- hook pre-merge --yes` — 4930 tests passed, 1 skipped; clippy, fmt, doc-sync, snapshot and lockfile checks green
- Nightly on the original cut-from tip (https://github.com/max-sixty/worktrunk/actions/runs/35060617020): green across `full-tests` on linux/macos/windows, `feature-powerset`, all `release-target`s, `nix-flake`, `minimal-versions`, `link-check`

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)
max-sixty pushed a commit that referenced this pull request Sep 17, 2026
)

The Agent Integration page contradicts itself about oh-my-pi. Its
capability
table marks activity tracking ✓ for oh-my-pi and the Installation
section
documents `wt config plugins omp install`, but the Activity tracking
section
below still reads "The Claude Code, Codex, OpenCode, Pi, and Gemini
plugins
track agent sessions…" and "All five plugins clear the marker when a
session
ends." Both predate #4135, which split the Pi and
oh-my-pi
installers and added the sixth column without updating the prose.

oh-my-pi does track and does clear: `dev/omp-hook.ts` sets 🤖 on
`agent_start`, 💬 on `agent_end`, and clears on `session_shutdown`.

Rather than adding a sixth name and bumping the count to six, both
sentences
now quantify instead of enumerating — "Every plugin tracks…" and "Every
plugin
clears…". The duplicated host list is what went stale, and the
capability table
directly above already carries it, so removing it means the next host
doesn't
have to be added in two places. Quantifying also reads correctly for
someone
arriving on the `#activity-tracking` deep link from `tips-patterns.md`,
who
never sees that table.

No test: the change is prose. `test_docs_are_in_sync` covers the two
generated
mirrors, which are regenerated here.

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: worktrunk-bot <worktrunk-bot@users.noreply.github.com>
ashebanow added a commit to ashebanow/nix-config that referenced this pull request Sep 18, 2026
Both changes the fork carried shipped in upstream's v0.78.0 — the
Pi/oh-my-pi split (max-sixty/worktrunk#4135, which upstream landed after
closing our #4133) and the `stdenv.hostPlatform.isDarwin` fix (#4132) —
so the fork pin is gone. `nix-pin` is retired; nothing points at a fork
branch again.

Pinned to the tag rather than `main`: `main` runs ahead of releases with
release-please, and release-please commits, dependabot bumps, and
in-flight features all land on it, so `main` pins an arbitrary unreleased
commit. `v0.78.0` is upstream's newest *published* release — a `Release
v0.79.0` commit exists on `main` but the tag is not pushed, so there is
nothing newer to point at.

Adds `just update-worktrunk`, which bumps the ref itself instead of
re-locking in place. `nix flake update worktrunk` alone would re-resolve
whatever ref flake.nix names, which cannot express "newest tagged
release" — the ref IS the version selector. The recipe finds the highest
semver tag upstream publishes, rewrites the URL if it moved, then locks.
The rewrite happens in flake.nix rather than via `--override-input` so
the committed file stays the source of truth for the pinned version; an
override would bump the lock while flake.nix still claimed the old tag.

Two failure modes the recipe guards against, both found by testing:

  - A bare-substring rewrite also edited prose comments citing an older
    release, silently changing documentation. Now anchored to the
    `url = "..."` line.
  - With no matching url line, the rewrite was a no-op that still exited
    0, so the recipe reported success after `nix flake lock` had dropped
    the worktrunk input entirely. It now fails with the lock intact.

The whole pin and the overlay in lib/overlays/worktrunk.nix drop once
nixpkgs unstable ships >= 0.78.0.

refs BOX-145, BOX-190
social4hyq pushed a commit to social4hyq/homebrew-core that referenced this pull request Sep 20, 2026
worktrunk 0.78.0

Created-by: HarmonybrewBot
Commit-by: HarmonybrewBot
Merged-by: HarmonybrewBot
Description: Created by `brew bump`

---

Created with `brew bump-formula-pr`.<details>
  <summary>release notes</summary>
  <pre>## Release Notes

### Improved

- **Hook scripts read renamed context keys**: the JSON piped to every hook drops `worktree`, `repo_root`, `main_worktree`, and `main_worktree_path` in favour of `worktree_path`, `repo_path`, `repo`, and `primary_worktree_path`. Config templates migrate on load, so hooks written with the old names keep working; scripts reading the JSON, `--execute` templates, and `--var` overrides need updating. (Breaking.) ([#4080](max-sixty/worktrunk#4080))

- **`wt config plugins pi` installs for Pi; oh-my-pi moves to `wt config plugins omp`**: `pi install` wrote an oh-my-pi hook, which Pi never loads; Pi users re-run it to get a Pi extension. Existing oh-my-pi hooks keep working, listed as outdated until `wt config plugins omp install`. (Breaking: scripts that run `wt config plugins pi` for oh-my-pi need `omp`.) [Docs](https://worktrunk.dev/claude-code/#oh-my-pi) ([#4135](max-sixty/worktrunk#4135), thanks @ashebanow)

- **Interactive prompts no longer open with a blank line**: `wt config shell install`, `wt config plugins claude install`, and the commit-message setup offer in `wt merge` began with one. ([#4059](max-sixty/worktrunk#4059))

- **`wt config show` gives a single `wt config shell install` hint**: an outdated wrapper, a fish wrapper at the deprecated `conf.d` path, and missing fish completions each printed their own hint. The zsh `compinit` snippet is now syntax-highlighted. ([#4059](max-sixty/worktrunk#4059))

- **`wt config update --output <path>` confirms the write**: it now prints `✓ Wrote user config migration @ ~/migrated.toml` where it printed nothing. `--output=-` stays silent. ([#4053](max-sixty/worktrunk#4053))

### Fixed

- **Removal no longer discards untracked files hidden by `status.showUntrackedFiles = no`**: under that setting, a worktree holding only untracked files looked clean, so `wt remove`, `wt merge`, and `wt step prune` removed it without `--force`. Safety checks now always count untracked files, and auto-staging warnings list every file they stage. ([#4111](max-sixty/worktrunk#4111), thanks @Duang777)

- **`wt remove` and `wt merge` check the right worktree under an inherited `GIT_DIR`**: run from a `!wt` git alias or a git hook, which pass `GIT_DIR` down, `wt remove` could delete a dirty worktree without `--force`, and `wt merge` could refuse a clean merge. Both now check the worktree they act on. ([#4082](max-sixty/worktrunk#4082), thanks @Duang777)

- **`wt merge` no longer deletes a worktree locked with `git worktree lock`**: the merge completes and prints `Worktree preserved (locked)`. Every Worktrunk removal refuses a locked worktree, even with `--force`; run `git worktree unlock <path>` first. ([#4073](max-sixty/worktrunk#4073), thanks @Duang777)

- **`wt config update` no longer overwrites a config edited while its prompt is open**: it wrote the pre-prompt snapshot over the newer file and reported success. It now fails with the path and leaves the newer file; re-running previews the current contents. ([#4127](max-sixty/worktrunk#4127), thanks @zach-hammad-vs for reporting)

- **`wt step prune` no longer deletes a branch created minutes ago from an older commit**: `--min-age` aged a branch without a worktree by its commit's date, so a new branch off a day-old commit was pruned on the next run. Age now comes from the branch's oldest reflog entry. ([#4077](max-sixty/worktrunk#4077))

- **A comment or blank line above an inline config section no longer breaks the config**: a save or migration that rewrote `commit = { … }` as `[commit]` put that line inside the brackets. `wt switch`, `wt merge`, `wt remove`, and several `wt step` commands then failed with `Failed to load config`; `wt list` warned and ran without the config. Saves also dropped unrecognized keys from inline sections. ([#4120](max-sixty/worktrunk#4120), thanks @zach-hammad-vs for reporting)

- **Config saves keep the trailing comment on a value they rewrite**: a save such as answering the commit-generation offer dropped the comment after each value it rewrote, including one after an inline section's closing brace. ([#4138](max-sixty/worktrunk#4138))

- **Ignored project-config keys warn from every worktree**: an unknown key in `.config/wt.toml`, or one that belongs in user config such as `[merge]`, warned only in the main worktree, so `wt merge` from a linked worktree ignored it silently. ([#4145](max-sixty/worktrunk#4145), thanks @ee-prog for reporting)

- **`wt merge` skips `post-commit` when it removes that hook's worktree**: before, the hook ran in the primary worktree when the removed one sat inside the repository, and otherwise didn't run. The merge now prints `▲ Skipped post-commit`; move work that must finish there to `pre-remove`. ([#4049](max-sixty/worktrunk#4049))

- **`wt switch` recovers from a removed worktree in a bare repository**: `wt switch` and the picker surfaced git's raw `fatal: Unable to read current working directory` instead, and the removed-directory message lacked `wt switch ^`. ([#4067](max-sixty/worktrunk#4067))

- **`wt config shell install fish` puts the wrapper where fish reads it under a custom `$XDG_CONFIG_HOME`**: the wrapper always went to `~/.config/fish/functions/`, so install reported success while `wt switch` changed no directory. On Windows, the fish completion moves from `%APPDATA%\fish\completions` to the wrapper's directory. ([#4107](max-sixty/worktrunk#4107))

- **`wt config shell install zsh` honours `$ZDOTDIR` only when it is absolute**: an empty or relative value resolved against the current directory, so install appended to a `.zshrc` under the current directory and reported success while the file zsh reads went untouched. A non-absolute value now falls back to `$HOME`. ([#4085](max-sixty/worktrunk#4085))

- **`wt config shell install` for Nushell no longer deletes a `wt.nu` under the current directory**: an empty or relative `$XDG_CONFIG_HOME` resolved against it, so install removed a `nushell/vendor/autoload/wt.nu` beneath it unread, as a stale wrapper. It now falls back to `~/.config`, which also fixes unread `tea` logins that let `wt switch pr:<n>` treat a Gitea host as GitHub. ([#4104](max-sixty/worktrunk#4104))

- **`wt switch --create` names the branch a failed worktree add leaves behind**: `git worktree add -b` writes the branch ref before populating the worktree, so a failure in between left the branch with no worktree, and the next attempt reported a name collision. The error now says how to delete or reuse it. ([#4109](max-sixty/worktrunk#4109), thanks @technicalpickles for reporting)

- **`wt config shell install` no longer tells an already-wrapped shell to restart**: reinstalling from a shell with the wrapper loaded, such as after a version bump, printed `↳ Restart shell to activate shell integration`. ([#4059](max-sixty/worktrunk#4059))

- **`wt config shell install` migrates a deprecated `conf.d` fish wrapper when `~/.config/fish/functions` is missing**: before, only `wt config shell install fish` migrated it. `wt config show` now reports that wrapper even when fish isn't on `PATH`. ([#4059](max-sixty/worktrunk#4059))

- **Plugin status and uninstall checks ask the agent CLIs**: `wt config show` could list an installed Claude Code plugin or Gemini CLI extension as not installed, and `wt config plugins claude|codex uninstall` could report success over a marketplace still configured. A repeat Claude uninstall now runs both removals instead of stopping at `Plugin not installed`. ([#4048](max-sixty/worktrunk#4048), [#4054](max-sixty/worktrunk#4054))

- **`wt config plugins opencode install` no longer writes the plugin into the current directory**: with `OPENCODE_CONFIG_DIR` set but empty, it wrote `plugins/worktrunk.ts` there, typically inside a repository, reporting success though OpenCode never saw it. An empty value now counts as unset. ([#4084](max-sixty/worktrunk#4084))

- **The OpenCode plugin clears its activity marker when OpenCode 1 disposes it**: only explicit session deletion cleared it, so a finished session kept its `💬` and `wt list` showed it as active until `wt config state marker clear` was run by hand. ([#4129](max-sixty/worktrunk#4129), thanks @drewdas for reporting)

- **`wt config update` migrates `[ci] platform` into a `[forge]` section that only sets `hostname`**: that config, common with GitHub Enterprise and self-hosted GitLab, kept the deprecated key with no deprecation warning. ([#4061](max-sixty/worktrunk#4061))

- **Deprecated keys in an inline `[projects]` entry now migrate**: `"example.com/org/repo" = { merge = { no-ff = true } }` skipped the migration the section form got, so the per-project setting was ignored. ([#4122](max-sixty/worktrunk#4122), thanks @zach-hammad-vs for reporting)

- **A deprecated config section the migration can't rewrite now warns**: `select = "not a table"`, or a `[select]` whose `[switch.picker]` destination is already set, was ignored without a message. It now reports `unknown field select (will be ignored)`. ([#4121](max-sixty/worktrunk#4121), thanks @zach-hammad-vs for reporting)

- **`wt config update` no longer changes what a template renders**: it could rename `{{ repo_root }}` inside a quoted s

See merge request: Harmonybrew/homebrew-core!20435
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: pi skill installer actually installs in Oh My Pi (omp)

3 participants