mirror of
https://github.com/max-sixty/worktrunk.git
synced 2026-09-14 20:00:38 +08:00
dependabot/cargo/dirs-7.0.0
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
80e795b271 |
fix(plugin): run the Codex Windows hooks through Git Bash, not the WSL launcher (#4008)
## Problem Codex resolves a hook `command` through the platform shell — `/bin/sh -lc` on Unix, `cmd.exe /C` on Windows ([`default_shell_command`](https://github.com/openai/codex/blob/main/codex-rs/hooks/src/engine/command_runner.rs)). All four Codex hooks in the plugin manifest lead with a bare `bash`, and under `cmd.exe` that resolves through the Windows PATH to `System32\bash.exe` — the WSL launcher, not Git Bash. In a sandboxed session the launcher refuses to start (`Access is denied. Error code: Bash/Service/CreateInstance/E_ACCESSDENIED`), so every prompt, permission request, turn end, and session end raises a `Hook failed` banner (#4007). ## Solution Git for Windows stays a requirement; only the bare *name* `bash` is the problem, since `cmd.exe` resolves it through PATH. Each hook now also carries Codex's per-handler `commandWindows`, which **replaces** `command` on Windows (`command_windows.unwrap_or(command)` in `codex-rs/hooks/src/engine/discovery.rs`). It calls a new `cmd.exe` shim, `plugins/worktrunk/hooks/wt.cmd`, that locates `bash.exe` by path the way [`find_git_bash`](https://github.com/max-sixty/worktrunk/blob/2405b8b434cfa9d7a604405d3ace966ada60ff8a/src/shell_exec.rs#L481-L518) does in `src/shell_exec.rs` — derived from `git.exe`'s install directory, then the system-wide and per-user install defaults — and then runs the same `hooks/wt.sh` that Claude, Gemini, and Unix Codex already go through. Worktrunk's own binary resolution stays in that one script instead of being spelled a second time in cmd. Two details the shim inherits from the Rust resolver: `Git\bin\bash.exe` before `Git\usr\bin\bash.exe`, because the former is the wrapper that sets up the MSYS environment for a caller outside Git Bash (which is what puts `uname` within reach of `wt.sh`); and a PATH-scoped lookup, because an unscoped `where` searches the current directory first — for a hook that's the user's project, so a `git.exe` committed to a repo would otherwise choose the bash every event runs. The lookup is spelled `"%SystemRoot%\System32\where.exe" "$PATH:git.exe"` on both counts: the `$PATH:` prefix scopes what is searched, and the absolute path to `where.exe` closes the same surface one level up — `where` is `System32\where.exe` rather than a cmd built-in, so cmd resolves that bare name from the current directory too. `wt.sh` now clears `WT` before its branches. On Windows all of them can be skipped (neither `git-wt.exe` nor `wt` on PATH), and a hook is handed the caller's whole environment, so an inherited `WT` was what the final `command -v "$WT"` check accepted and ran. The Windows commands brace the plugin root as `${PLUGIN_ROOT}` because Codex substitutes only that form textually, before the shell runs; the unbraced `$PLUGIN_ROOT` the Unix commands use survives to `/bin/sh`, and `cmd.exe` would pass it through literally. The tail is `|| exit /b 0`, the cmd.exe spelling of the Unix `|| true`: a marker is decoration, and a nonzero exit is what raises the banner. `.gitattributes` pins `plugins/worktrunk/hooks/*.cmd` to a CRLF checkout, since cmd.exe resolves a `goto` label by seeking through the file and can fail that search on an LF-only batch file. Two doc changes ride along. `plugins/worktrunk/CLAUDE.md` records why every hook carries `commandWindows`, the shim's resolution order and the two lookup-scoping decisions, and why `SessionEnd` keeps `timeout: 3` (Codex's ceiling for that event, so the longer Windows chain has no more budget to ask for). And because the CHANGELOG entry opens an `## Unreleased` section, `.claude/skills/release/SKILL.md` step 9 now says to rename that heading at release time rather than insert a new one above it — otherwise the release ships a stale `## Unreleased`. ## Testing Six tests, all new: - `test_codex_hooks_carry_windows_commands` (all platforms) — pins that every Codex command hook has a `commandWindows`, that it names neither `bash` nor bare `wt`, that it calls the shim, and that the two `PLUGIN_ROOT` spellings stay on their respective sides. Written first: it failed on the manifest as shipped, with the exact hook command from the report. - `test_codex_windows_hook_commands_set_the_marker` (Windows leg of CI) — runs the real `commandWindows` the way Codex spawns it, reproducing both steps: the `${PLUGIN_ROOT}` substitution and `cmd.exe /C "<command>"` with the command line as a single quoted raw argument. It asserts `UserPromptSubmit` stores 🤖, `Stop` replaces it with 💬, `SessionEnd` clears it, and that a hook which cannot find worktrunk still exits 0. PATH is pinned to the shape a default Git for Windows install produces — `Git\cmd` and nothing else from the install — so the run covers cmd.exe's quote handling, the shim's search for bash, `wt.sh` running under the bash it picks, and the emoji surviving both hops. - `test_wt_sh_ignores_an_inherited_wt` (Windows leg) — points `WT` at a real worktrunk, spelled the way bash can run it, on a PATH where `wt.sh` finds none itself, and pins that the inherited value is not what runs. - `test_shim_ignores_a_git_in_the_current_directory` (Windows leg) — plants an unrunnable `git.exe` in the hook's current directory plus the `bash.exe` the shim would derive from it, puts a real Git and worktrunk on the pinned PATH, and asserts the shim prints a version line — which it could not do had it taken the decoy. - `test_shim_ignores_a_where_in_the_current_directory` (Windows leg) — the same question one level up: a `where.bat` in the hook's current directory names a decoy Git install whose `bin\bash.exe` exists, so an unscoped `where` would reach the derive branch and leave `BASH` set and unrunnable rather than falling through to a real install. A separate probe pins the premise — that the planted `where` really does shadow `System32\where.exe` — so the test cannot go green because the decoy was never consulted. - `test_shim_derives_bash_from_the_git_on_path` (Windows leg) — points `ProgramFiles` and `LOCALAPPDATA` at an empty directory, which leaves the derive branch as the only route to a bash. The two tests above go red only when the shim picks the *wrong* bash; this one goes red when the lookup finds nothing, so it is what observes that `"%SystemRoot%\System32\where.exe" "$PATH:git.exe"` survives the quoting `for /f` wraps it in. <details><summary>What this does not verify</summary> Nothing here drives a real Codex session on Windows, so the end-to-end claim — that Codex selects `commandWindows` and that the banner stops — rests on reading `codex-rs/hooks/src/engine/{discovery,command_runner}.rs` rather than on observation. What CI does exercise is the command string itself, executed the way that source says Codex executes it. The shim's two standard-install fallbacks (`%ProgramFiles%\Git`, `%LOCALAPPDATA%\Programs\Git`) are unexercised by CI. Every test that reaches a bash takes the derive branch above them, and the one test that touches the fallbacks empties them to force that branch rather than to exercise them; they share the whole tail with it. Two adjacent things are deliberately left alone, as separate concerns: the Gemini hooks at the repo-root `hooks/hooks.json` use the same bare `bash` (Gemini's Windows hook execution isn't established here), and Claude's `hooks/hooks.json` is unaffected because Claude Code runs hook commands through Git Bash on Windows. </details> --- Closes #4007 — automated triage --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> |
||
|
|
cfd6f099bc |
fix(codex): clear activity marker on session end (#3660)
Codex now exposes `SessionEnd`, but Worktrunk’s Codex plugin only returned the activity marker to idle at turn end. This adds a main-session exit hook that clears the marker, using Codex’s three-second maximum hook timeout so cleanup has the best chance to complete without delaying shutdown. The CLI help, plugin-layout guidance, user documentation, generated mirrors, metadata test, and help snapshot now describe and verify the complete Codex lifecycle. Tested with `cargo run -- hook pre-merge --yes` (4,564 tests passed; 1 skipped). > _This was written by Claude Code on behalf of max_. |
||
|
|
36ba57bdca |
fix(plugin): ship skills to Codex installs via a generated real-file mirror (#3440)
Codex installs of the worktrunk plugin carried no skills. `codex plugin add` copies the plugin into `$CODEX_HOME/plugins/cache/` via `copy_dir_recursive` (codex-rs core-plugins), which handles only regular files and directories — so the `skills -> ../../skills` symlink (and the nested `reference/README.md` link inside the tree) were silently dropped, and sessions load from that cache copy. Verified against codex-cli 0.144.1 both by reading the tagged sources and by installing this repo's plugin into a scratch `CODEX_HOME`: the installed root had no `skills/` at all and `codex debug prompt-input` showed an empty plugin skill inventory. This PR cuts the plugin's `skills` symlink over to a **generated real-file mirror** of the authored repo-root `skills/`, kept current by a new `sync_plugin_skills_mirror` stage in `test_docs_are_in_sync` (dereferences symlinks, deletes stale files, self-heals and fails on drift — same pattern as the other generated mirrors). Repo-root `skills/` stays the authored home: Gemini hard-probes it at the extension root, the docs sync writes into it, and Windows checkouts read it. Reversing the symlink direction instead would put a symlink at the repo root, breaking Gemini's install copy and every Windows checkout — which also surfaces a latent bug this fixes: symlinks materialize as plain text files on Windows clones, so installs from a Windows checkout shipped no skills to Claude or Codex either. It also drops the Codex manifest's `skills: "./skills/"` key: with no key, Codex scans `<plugin-root>/skills/` by convention (`default_skill_roots` in `codex-rs/core-plugins/src/loader.rs`), and the explicit path resolves to the same directory, so the key was redundant — symmetric with the Claude manifest cutover in #3431. **For the reviewer:** - The 20 files under `plugins/worktrunk/skills/` are the generated mirror (byte-identical to repo-root `skills/`; git stores shared blobs once). `plugins/worktrunk/CLAUDE.md` → "Plugin skills are a generated mirror" documents the rationale with codex-rs citations. - `sync_plugin_skills_mirror` in `tests/integration_tests/readme_sync.rs` is the sync stage (Step 3b of the pipeline); it already proved itself once in this branch — merging main regenerated `reference/{config,list}.md` in the mirror. - `test_plugin_layout_is_consolidated` now pins the mirror shape cross-platform (real directory, no symlinks anywhere under it), replacing the unix-only symlink assertion. **Verification:** fresh scratch-`CODEX_HOME` install now carries both skills into the cache and `codex debug prompt-input` lists `worktrunk` and `wt-switch-create`; `claude plugin validate` passes and `claude --plugin-dir … plugin details` discovers Skills (2) plus all 8 hooks; Gemini's repo-root path is untouched. A 2×2 probe matrix (manifest key present/absent × real dir/symlink) confirmed key-absence changes nothing and symlinks ship nothing. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
512a94de2f | feat(plugin): ship Codex-native activity hooks (#3364) | ||
|
|
233cd493d0 |
fix(codex): drop activity-marker hooks Codex can't drive (#2786)
The Codex plugin shipped `hooks.json` with `SessionStart`→💬, `UserPromptSubmit`→🤖, and `Stop`→💬. But codex-cli 0.130.0's `HookEventNameWire` schema (extracted from the binary) has **no `Stop`/turn-end event**: `PreToolUse`, `PermissionRequest`, `PostToolUse`, `PreCompact`, `PostCompact`, `SessionStart`, `UserPromptSubmit`. So on Codex the 🤖 marker set at `UserPromptSubmit` could never return to 💬 — a Codex worktree stuck at "working" for the entire session. That's worse than the docs claimed ("rests at 💬"). This removes the marker hooks entirely until Codex exposes a turn-end hook event. The Codex plugin now ships only the configuration skill; activity tracking joins worktree-isolation and `/wt-switch-create` as Claude-Code-only. A re-enablement comment (exact conditions + the files to restore) lives in `src/commands/config/codex.rs` and a new `CLAUDE.md` → "Codex Plugin" section, which also documents the accepted tradeoff that the `skills/` symlink exposes the Claude-only `wt-switch-create` skill to Codex (harmless — Codex can't act on it). Bundled adjacent fixes: `plugin.json` metadata drift (`description`/`longDescription` no longer claim activity hooks; `homepage`/`websiteURL` `https://worktrunk.dev/claude-code/` → `https://worktrunk.dev`), and the install/uninstall/`--help`/docs text made honest about Codex having only the configuration skill. **Reviewer navigation:** `src/commands/config/codex.rs` + `src/cli/config.rs` (runtime + `--help` text), `docs/content/claude-code.md` (skill ref + `llms.txt` auto-synced), `CLAUDE.md` (new section), `tests/integration_tests/config_show.rs` (`test_codex_plugin_metadata_is_valid_json` now asserts `hooks` absent + guards metadata regression). Snapshot env-block churn is stale-fixture convergence to the repo's current `/nonexistent/wt/` convention, not a leak. **Testing:** Full pre-merge gate green locally (3704 tests, 0 skipped; lints clean). The metadata test was strengthened; the marker behavior itself is removed, so there's nothing runtime-testable to add. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f00b787e1b |
fix(codex): relocate plugin into subdir so the marketplace resolves it (#2782)
## Problem #2780 put `.agents/plugins/marketplace.json` at the path codex-cli reads, but the plugin was still **uninstallable**. Driving the Codex `/plugins` TUI surfaced the real, complete set of constraints in codex-cli 0.130.0: 1. A plugin `source` must be the **object** form `{"source":"local","path":"./plugins/<name>"}` — a bare string `"./"` fails with `local plugin source path must not be empty`. 2. The path must be a **non-empty subdirectory**. Codex resolves it relative to the marketplace root (repo root), so a repo-root plugin (`./.codex-plugin/` at the top) can't be referenced at all — every Codex marketplace plugin lives in `./plugins/<name>/.codex-plugin/plugin.json`, exactly like the OpenAI-curated marketplace. So the plugin had to move into a subdirectory. ## Changes - Move `.codex-plugin/` → `plugins/worktrunk/.codex-plugin/` and `hooks/` → `plugins/worktrunk/hooks/` (a plugin-root sibling, matching the curated layout where `skills/` and `assets/` sit beside `.codex-plugin/`). - `plugins/worktrunk/skills` → symlink to `../../skills` so the shared `worktrunk` + `wt-switch-create` skills stay single-source (no duplication; the repo still auto-syncs `skills/worktrunk/reference/`). - Rewrite `.agents/plugins/marketplace.json` with the object `source`, `policy.installation: "AVAILABLE"`, and `category` that Codex actually parses. - `plugin.json` `hooks` → `./hooks/hooks.json` (plugin-root relative). - Update the install hint (`src/commands/config/codex.rs`), docs, auto-synced skill reference, the validity test, and the install snapshot. ## Verification Verified end-to-end against codex-cli 0.130.0: repointed a local marketplace at the worktree and drove `/plugins`. The `Worktrunk` marketplace now appears (plugin count 123 → 124), the plugin shows as **Available** with **both skills resolved** (`worktrunk:worktrunk`, `worktrunk:wt-switch-create` — the symlink works), and it installs. `test_codex_plugin_metadata_is_valid_json` now asserts the object `source`, `policy.installation`, `interface.displayName`, and the new paths. `test_docs_are_in_sync` and the install snapshot are updated; full `config_show` module (119 tests) + pre-commit pass. **Caveat (documented, not a regression):** plugin-bundled *command* hooks remain gated by Codex's `plugin_hooks` feature flag — already covered in the troubleshooting docs (`codex features enable plugin_hooks`). The pre-install plugin detail view shows "No plugin hooks", consistent with every one of the 123 curated plugins (none bundle command hooks, so this path has no working precedent to verify against in-TUI). Marker behavior with `plugin_hooks` enabled is the remaining thing to confirm in a real installed session. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |