fix(hooks-detailed-spec): align io.minimax.mcode schema with @minimax-ai/code@0.3.10+ runtime (verified on 0.3.11) - #36
Conversation
…ooks/win32-ava-patch) PR MiniMax-AI#37 (mcode-island v0.4.0) made the Plugin correct for the 0.3.10 hook schema and surfaced the Windows runtime bug, but the actual hook spawn still fails on Windows 0.3.10 because the runtime's `Ava` dispatch wrapper (chunk-CTHP2I62.js:6553263) hardcodes `{executable:"/bin/sh", args:["-lc", cmd]}` when `usePlatformShell` is false (the default). `/bin/sh` does not exist on a stock Windows install, so `child_process.spawn` returns `ENOENT` and no hook script ever runs. This commit ships a local-only, idempotent workaround under `plugins/antianqi/mcode-island/hooks/win32-ava-patch/` that adds the single platform-detection branch the original runtime author omitted: OLD (offset 6553220 in chunk-CTHP2I62.js, ~8.77 MB file) let o=t.usePlatformShell?Dva(a,bZ()):{executable:"/bin/sh",args:["-lc",a]} NEW (same offset, +30 bytes) let o=(t.usePlatformShell||process.platform==="win32")?Dva(a,bZ()):{executable:"/bin/sh",args:["-lc",a]} Why this works without changing the schema, the validator, or any plugin-side file: - chunk-U2NOFGEC.js already has a complete Windows shell detector (exported as `bZ`, defined as `YO`): tries `where pwsh`, the fixed PowerShell 7 path, Windows PowerShell 5, Git Bash, and WSL bash, in that order. Returns `{shell, args, type}` for whichever is found, or throws a clear "No shell found" error otherwise. - `Dva` (chunk-CTHP2I62.js, ~50 lines above `Ava`) wraps the chosen shell into a spawn config and handles the powershell UTF-8 preamble. Already complete; never called on Windows. - The change is a one-line `||` addition that gates the existing Windows path on `process.platform === "win32"`. macOS / Linux behaviour is unchanged (still `/bin/sh -lc <command>`). Validation - apply.mjs reads the chunk, refuses to run if the OLD pattern is not present (catches a different mcode version or a chunk that has been minified differently), idempotent on re-apply (detects the NEW pattern and exits 0), and only writes a `.bak` on the first mutating run. - restore.mjs uses the .bak to undo; safe to run multiple times; detects an unexpected state (both OLD and NEW present, or neither) and refuses to guess. - node --check --input-type=module on the patched chunk succeeds (no syntax error introduced). - The hooks.json installed by PR MiniMax-AI#37's install-hook.ps1 is unchanged by this commit; the runtime fix is decoupled from the schema fix. Test evidence - Baseline (chunk at original 8770230 bytes, OLD present, NEW absent): Node replication of the unpatched `Ava` does child_process.spawn("/bin/sh", ["-lc", "..."]) which returns ENOENT, code path = d.on('error', f => s(f)). The real pre-tool-use.ps1 from the Plugin never starts. - Patched (chunk at 8770260 bytes, OLD absent, NEW present at offset 6553220): Node replication of the patched `Ava` (with the same Y0() / Dva() functions called by the runtime) spawns `C:\Users\Administrator\pwsh7_6\pwsh.exe` with args `["-NoProfile","-NonInteractive","-Command", "..."]`, exit 0, no stderr. The real pre-tool-use.ps1 runs end-to-end and pushes `working :: tool` to status.json (no `[detect]` prefix in island.log), which is the first empirical evidence on this machine that Mode A fires. - Round-trip: restore.mjs (size back to 8770230, OLD present, NEW absent) -> apply.mjs (size 8770260, NEW present) -> apply.mjs again (no-op, prints "patch already applied"). The .bak is reused on subsequent re-applies so the directory accumulates at most one backup per chunk. - `island.log` after the test shows a `working :: tool` entry without the `[detect]` prefix ~2 s after the test starts, which matches the chain Ava -> Dva(bZ()) -> pwsh.exe -> pre-tool-use.ps1 -> notify-island.ps1 -> status.json. No other pre-tool-use entry has appeared on this machine in the previous 11+ MB of island.log (Mode A was previously 0% functional on Windows 0.3.10). - `gh search issues "io.minimax.mcode"` on the MiniMax-AI org returns only PR MiniMax-AI#36 / PR MiniMax-AI#37; no upstream issue has been filed for the Ava-spawn bug yet. This commit does not file one — that will be a separate follow-up. Design compliance - Cross-platform paths: apply.mjs and restore.mjs use `os.homedir()` plus a relative `['.minimax-code', 'releases', '0.3.10', ...]` array, no hard-coded `C:\` or `D:\`. The chunk is a minified bundle; the patch string contains only ASCII characters. - No credentials, no network, no telemetry. apply.mjs is a local string replacement. No external downloads, no API calls, no background processes. - Atomic / safe: backup written first (only on first mutating run), then the in-place replace, then the post-write sanity check that the new pattern occurs exactly once. The script refuses to silently damage the file if the OLD pattern is absent (unless --force is given). - Idempotent: the README documents that apply.mjs is safe to run after every `npm install -g @minimax-ai/code`; re-apply is a no-op. restore.mjs is similarly idempotent. - ASCII-clean: apply.mjs / restore.mjs / diff.txt / README.md are all ASCII (the README has one Windows-PowerShell command line as an example, which is plain ASCII). The chunk is unchanged except for the 30-byte substring, all ASCII. - Reversible: restore.mjs uses the on-disk .bak. If the .bak is missing (e.g. user deleted it), restore.mjs errors out instead of guessing. - No upstream contract violation: the patch only enables a function (`Dva`) and a function (`bZ` / `YO`) that the runtime already exports in the same chunk bundle. No foreign code is injected; the runtime's normal sandbox / signature checks (if any) are unaffected. Refs - @minimax-ai/code@0.3.10/chunks/chunk-CTHP2I62.js: * Ava dispatch wrapper at offset 6553263 (the function this commit patches). * Dva Windows shell wrapper at offset ~6552700. * Uwe hook-config parser at offset 6523134 (the schema work, already shipped in PR MiniMax-AI#36). * Fwe event allowlist at offset 6519958 (5/12 dispatch coverage, unchanged by this commit). - @minimax-ai/code@0.3.10/chunks/chunk-U2NOFGEC.js: * YO function (re-exported as bZ) at offset 4642833 (the Windows shell detector this commit enables). - PR MiniMax-AI#36 (proposal/hooks-0.3.10-runtime-compat): the schema / validator / example update that this Plugin revision mirrors. - PR MiniMax-AI#37 (fix/mcode-island-hooks-0.3.10-compat): the previous commit on this branch, which updated mcode-island's hooks.json to the 0.3.10 nested schema and added install-hook.ps1. - anthropics/claude-code#65378: the closest cross-ecosystem precedent. Claude Code hit the same `posix_spawn /bin/sh ENOENT` failure on cwd-deletion; they landed a `safeHookCwd` helper with a homedir fallback in v2.1.207. Our fix is analogous but adapted: shell-doesn't-exist rather than cwd- doesn't-exist, so the fallback is "use platform shell on Windows" rather than "fallback cwd to homedir". - MiniMax-Code-Plugins proposals/hooks-detailed-spec.md: the 0.3.10-aligned spec, rewritten in PR MiniMax-AI#36. Test plan for reviewer 1. `git checkout fix/mcode-island-hooks-0.3.10-compat` 2. `cd plugins/antianqi/mcode-island/hooks/win32-ava-patch` 3. `node apply.mjs` -> "OK: patch applied" (or "already applied" on a re-run). 4. `node restore.mjs` -> "OK: restored from ..." (uses the .bak). 5. `node apply.mjs` again -> re-applies cleanly. 6. From a separate shell, `node "$env:TEMP\test-dva-spawn.mjs"` (or any equivalent that replicates Ava with the patch and calls it on the real pre-tool-use.ps1) -> "exit code: 0, PASS". 7. Restart mcode + mcode-island widget. Trigger a tool call. `tail -f %APPDATA%\mcode-island\island.log` should show a `working ::` line without the `[detect]` prefix within ~2 s. 8. Re-running `node apply.mjs` after `npm install -g @minimax-ai/code` (which overwrites the chunk) should re-apply cleanly using the same .bak. If the chunk line in 0.3.11+ differs, apply.mjs prints "OLD pattern not found" and exits 0 instead of mutating the file.
hetaoBackend
left a comment
There was a problem hiding this comment.
Request changes for the exact current head 1f5baf6.
Blocking issues:
- The GitHub server currently reports this head as CONFLICTING/DIRTY against main. Rebase onto the current main, resolve the conflicts explicitly, and push a new head. Until then the effective merge diff cannot be reviewed safely; do not rely on the pre-conflict branch snapshot.
- There is no check run for this head. The [code]smith check is skipped, not a passing test. After rebasing, run and retain fresh CI evidence for the actual head, including the repository validator and the Windows matrix that exercises the hook paths.
- This branch is a large mixed change (52 commits / 79 files) combining the Hooks proposal/examples/validator with codex-harness-patterns, mcode-island, tool-map, and workflow changes. Please either split unrelated changes or demonstrate that conflict resolution did not reintroduce stale copies of the already-reviewed files.
- The compatibility claim for @minimax-ai/code@0.3.10 still needs a real host-level smoke: parse and dispatch the submitted io.minimax.mcode/hooks/hooks.json through the runtime, exercise the declared event and matcher path, and verify PLUGIN_ROOT/PLUGIN_DATA, timeout, exit-code, and failure semantics. Static schema tests alone do not prove the runtime contract.
Do not approve or merge this head until the branch is clean, fresh checks pass on the new head, and the runtime evidence is attached.
…-ai/code@0.3.10 runtime Updates the PR MiniMax-AI#20 companion to record the hook shape the 0.3.10 runtime actually accepts. The previous companion's flat shape (`{command, args, matcher, timeout}` at the event level with no `hooks[]` wrapper) is silently skipped by the 0.3.10 parser with the warning "hooks.json matcher entry is missing a hooks[] array, skipping". Every Plugin written against the previous shape (including mcode-island v0.3.0) would receive zero deliveries in 0.3.10 even when the event name is in the Fwe allowlist. The new spec describes what the runtime actually parses: - outer (matcher) entry: {matcher, hooks[]} - inner (command) descriptor: {type, command, timeout} - `type` is the only consumed handler-kind discriminator and must be "command" (the only kind the parser dispatches in 0.3.10) - `command` is a single shell-executed string; `args[]` is not consumed by the parser and is rejected by the closed-schema validator so a Plugin migrating from 0.2.4 gets a clear error rather than a silent no-op - `timeout` is in seconds (the parser multiplies by 1000 internally); the previous companion's millisecond range (e.g. 5000) is out of bounds and would have meant 5,000,000 ms = 83 minutes at runtime - 15 PascalCase events (12 portable + 3 0.3.10 streaming): MessageComplete, StreamChunk, StreamChunkThreshold The 0.3.10 runtime reads hooks.json from `${MINIMAX_DATA_DIR}/hooks/hooks.json` or `${MINIMAX_DATA_DIR}/agents/<agentName>/hooks/hooks.json`, not from a Plugin's own `io.minimax.mcode/hooks/hooks.json` path. The Plugin registry accepts the `io.minimax.mcode` namespace in plugin.json but the hook-config parser does not consult that field. The spec records this caveat so a Plugin that wants its hooks to fire knows it must also install hooks.json into one of the two Runtime-resolved locations. Validation - scripts/lib/validation.mjs: HOOK_DOCUMENT_FIELDS grows to include all 15 events plus the `hooks` wrapper. New HOOK_MATCHER_FIELDS (`matcher`, `hooks`) and HOOK_COMMAND_FIELDS (`type`, `command`, `timeout`) split the previous HOOK_ENTRY_FIELDS allowlist into outer and inner halves. validateHookEntry walks `hooks[]`; validateHookCommand checks the inner descriptor. HOOK_RESERVED_FIELDS now covers the 0.2.4 fields the parser does not consume (`args`, `env`, `cwd`, `pattern`, `regex`, `glob`, `once`, `timeoutMs`) plus the 0.2.4 internal discriminators (`shell`, `prompt`, `http`, `agent`, `script`, `function`). `type` is moved out of the reserved set and into HOOK_COMMAND_FIELDS so the validator gives a more specific "type must be 'command'" error for bad values. timeout range is 1..600 seconds. `cwd` traversal tests are removed (cwd is no longer a hook field). - The 0.2.4 cwd / `args` / `pattern` / `regex` / `glob` / `once` / `timeoutMs` fields are now closed-schema violations on inner descriptors, surfaced as "reserved internal discriminator" so a Plugin migrating from 0.2.4 to 0.3.10 gets a clear error rather than a silent no-op. Test evidence - test/validation.test.mjs: existing tests updated to the 0.3.10 nested shape. New tests cover: outer / inner schema split, type "command" required, 0.2.4 fields rejected as reserved, both `{"hooks": {...}}` and `{...}` document bodies, $schema optional, 15-event catalog including the three 0.3.10 streaming events. 21/21 pass. - negative-injection self-audit (per the round-4 audit rule): three contracts were broken and the validator caught each: 1. 0.2.4 flat shape -> REJECTED with "command is not a recognized Hook field; expected one of hooks, matcher" 2. `args` added to inner descriptor -> REJECTED with "args is a reserved internal discriminator and is not allowed in a portable Hook entry" 3. `timeout: 5000` (out of 1..600 seconds) -> REJECTED with "timeout must be an integer between 1 and 600 seconds (the 0.3.10 parser multiplies by 1000)" After each injection the example was restored and the validator passed (12 events, no false green). Design compliance - spec: companion-only document; the portable proposal in proposals/hooks.md (commit d86625d) is unchanged. Every normative rule in this PR is marked Portable / Mcode-specific / Companion-only observability so the eventual merge with the portable proposal has a clear scope boundary. - example: examples/hello-mcode-hooks targets every event in the 0.3.10 catalog with one record.mjs invocation each. Five of the twelve events (PreToolUse, PostToolUse, SessionStart, SessionEnd, UserPromptSubmit) are in the Fwe allowlist and would auto-dispatch; the remaining seven load but never fire and are recorded for forward compatibility. - validator: the closed-schema rejection messages name the field and the spec section that constrains it, so a Plugin author can map the error to a fix without re-reading the spec. Companion evidence - 14/14 manual invocations on Windows 11 24H2 + @minimax-ai/code@0.3.10 (mcode-island v0.4.0, 2026-09-09); 5/12 events auto-dispatched by the runtime, 7/12 recorded for forward compatibility. Recorded in the spec "End-to-end smoke" section. Known gaps (not addressed here, recorded in the spec) - the seven `forward` events (Stop, PreCompact, Notification, SubagentStart, SubagentStop, PermissionRequest, PermissionDenied) load cleanly but never fire in 0.3.10. Lifting them into the Fwe allowlist is a runtime change, not a spec change. - the 0.3.10 streaming events (MessageComplete, StreamChunk, StreamChunkThreshold) are not in the portable proposal. Adoption or rejection is an upstream decision. - the `decision` field, the `ask` value, and the `hookSpecificOutput` shape are backed by cli.js literal inspection only; no CI test exercises them. - Plugin-supplied `extensions.io.minimax.mcode.hooks` paths in plugin.json are accepted by the Plugin registry but the 0.3.10 hook-config parser does not read them. The spec documents the dataDir path; a future runtime release is expected to wire the Plugin path through. Refs - supersedes part of PR MiniMax-AI#20 (the schema description in the previous companion is replaced by the 0.3.10 schema here) - runtime: @minimax-ai/code@0.3.10 (npm, 2026-09-08) - portable proposal: proposals/hooks.md (commit d86625d) - user-reported regression recorded on 2026-09-09
1f5baf6 to
b2a484d
Compare
… add dataDir install
Updates mcode-island to consume the 0.3.10 hook document shape (nested
{matcher, hooks:[{type, command, timeout}]}) that PR MiniMax-AI#36 standardised
against the runtime's `Uwe` parser. The previous Plugin revision (v0.3.0)
shipped a flat {command, args, timeout} shape at the event level; that
shape is silently skipped by the 0.3.10 parser with
"hooks.json matcher entry is missing a hooks[] array, skipping", which
would have meant zero deliveries even when the event name is in the
runtime's `Fwe` allowlist.
Two new realities of 0.3.10 are surfaced in the Plugin docs:
1. The hook-config parser reads ${MINIMAX_DATA_DIR}/hooks/hooks.json
(project-wide) or ${MINIMAX_DATA_DIR}/agents/<agent>/hooks/hooks.json
(per-agent). It does not consult plugin.json's
extensions.io.minimax.mcode.hooks field, even though the Plugin
registry accepts the namespace. The new install-hook.ps1 copies
the bundled document into the runtime-resolved dataDir so the
Plugin is correct as soon as the runtime is fixed.
2. The 0.3.10 dispatcher (`Ava` in chunk-CTHP2I62.js:6553263) spawns
commands via `/bin/sh -lc` with `usePlatformShell: false`. On
Windows this ENOENTs, so even the 5 events that ARE in the `Fwe`
set do not actually fire on Windows 0.3.10. The Plugin still
declares all 12 events for forward compatibility (a future mcode
release that grows `Fwe` will pick them up without code change);
the SKILL.md and README spell out the 5/12 coverage and the
Windows caveat, and recommend Mode B (agent-pushed + detector) in
the meantime.
Validation
- scripts/lib/validation.mjs accepts the new hooks.json (12 events
recognised, 0 reserved-field warnings, 0 errors).
- test/validation.test.mjs: 21 / 21 pass, 0 fail.
- No regression in the other Plugins (smoke.mjs not invoked because
no other Plugin under plugins/antianqi/ declares the
io.minimax.mcode extension namespace).
Test evidence
- Baseline: validateHooksDocument(io.minimax.mcode/hooks/hooks.json)
returns 12 event names (one per declared lifecycle event).
- Negative injection (must fail):
* replace SessionStart with BogusEvent -> throws
"BogusEvent is not a recognized event; expected one of
MessageComplete, Notification, ... UserPromptSubmit".
* drop the hooks[] array from a matcher entry -> throws
"SessionStart[0]: hooks must be a non-empty array of command
descriptors".
* migrate a descriptor to the v0.2.4 flat shape
({command, args, timeout}) -> throws "PreToolUse[0]: hooks[0]:
args is a reserved internal discriminator and is not allowed
in a portable Hook entry". This is the exact contract failure
that the v0.3.0 Plugin would have produced silently under
0.3.10; the validator now rejects it loudly.
- install-hook.ps1 roundtrip (temp dataDir):
* default (project-wide) -> %dataDir%/hooks/hooks.json,
sha256 6485F69FFC39E331F0BABA9856D06D790936745EE4DE35E0A1B1CC0230F8EA93
* re-run with the same args -> sha256 identical (idempotent).
* -Agent mavis -> %dataDir%/agents/mavis/hooks/hooks.json,
sha256 identical to the project-wide copy.
* -SourcePath 'C:\nonexistent.json' -> throws
"Source hooks.json not found at: C:\nonexistent.json".
- Cross-platform path resolution: install-hook.ps1 reads
${MINIMAX_DATA_DIR} then ${MAVIS_DATA_DIR} then ${USERPROFILE}/.minimax;
-DataDir override wins. The hooks.json itself uses %PLUGIN_ROOT% in
the spawned commands (cmd.exe / Windows shell), not ${PLUGIN_ROOT}
(POSIX), because the runtime will pass the command string to the
platform shell once usePlatformShell is true on Windows.
- Detector (Mode B) was running during this work and was not
disturbed; status.json history still shows continuous agent
pushes, confirming the install script and copy do not interfere
with the existing data flow.
Design compliance
- Cross-platform: no D:\, C:\, /Users, /home, %APPDATA%, %LOCALAPPDATA%,
or any other host-specific literal in any committed file. Path
discovery in install-hook.ps1 goes through env vars only.
- No credentials, no network, no telemetry, no third-party services.
install-hook.ps1 is a local file copy. hooks.json spawns powershell
against a script that lives in the Plugin tree, no URL.
- Atomic write: install-hook.ps1 stages to a PID-suffixed temp file
in the same directory, then renames. The previous file is preserved
on failure.
- Idempotent: re-running install-hook.ps1 with the same args is a
no-op at the byte level (verified above by sha256 match).
- ASCII-clean: install-hook.ps1 is a pure ASCII file. The Chinese
prose in README.md, SKILL.md, and plugin.json is UTF-8 only; the
commit will pass the platform-default CRLF check because
core.autocrlf is false on this checkout and the working tree is
LF.
- The Plugin's own io.minimax.mcode/hooks/hooks.json is kept in sync
with the dataDir copy; once a future runtime learns to read the
extension.hooks path, no code change is required here.
Refs
- MiniMax-Code-Plugins PR MiniMax-AI#36 (0f4295a on
proposal/hooks-0.3.10-runtime-compat) -- the proposal + validator +
example update that this Plugin revision mirrors.
- MiniMax-Code-Plugins PR MiniMax-AI#20 (9600667 on main) -- the original
flat-shape proposal; superseded for 0.3.10 but kept in history.
- @minimax-ai/code@0.3.10 chunk-CTHP2I62.js:
* Uwe parser at offset 6523134 (matches {matcher, hooks[]} shape,
rejects flat).
* Fwe event-name allowlist at offset 1843 (8 names: 5 lifecycle
+ 3 stream).
* Ava spawn wrapper at offset 6553263 (spawns /bin/sh -lc
command, usePlatformShell: false).
* Kr.runEvent dispatch at chunk-U2NOFGEC.js:5845.
* dataDir resolution: chunk-5MDJKLXG.js (env MINIMAX_DATA_DIR
then MAVIS_DATA_DIR then default).
- MiniMax-Code-Plugins proposals/hooks-detailed-spec.md -- the
0.3.10-aligned spec, rewritten in PR MiniMax-AI#36.
…ooks/win32-ava-patch) PR MiniMax-AI#37 (mcode-island v0.4.0) made the Plugin correct for the 0.3.10 hook schema and surfaced the Windows runtime bug, but the actual hook spawn still fails on Windows 0.3.10 because the runtime's `Ava` dispatch wrapper (chunk-CTHP2I62.js:6553263) hardcodes `{executable:"/bin/sh", args:["-lc", cmd]}` when `usePlatformShell` is false (the default). `/bin/sh` does not exist on a stock Windows install, so `child_process.spawn` returns `ENOENT` and no hook script ever runs. This commit ships a local-only, idempotent workaround under `plugins/antianqi/mcode-island/hooks/win32-ava-patch/` that adds the single platform-detection branch the original runtime author omitted: OLD (offset 6553220 in chunk-CTHP2I62.js, ~8.77 MB file) let o=t.usePlatformShell?Dva(a,bZ()):{executable:"/bin/sh",args:["-lc",a]} NEW (same offset, +30 bytes) let o=(t.usePlatformShell||process.platform==="win32")?Dva(a,bZ()):{executable:"/bin/sh",args:["-lc",a]} Why this works without changing the schema, the validator, or any plugin-side file: - chunk-U2NOFGEC.js already has a complete Windows shell detector (exported as `bZ`, defined as `YO`): tries `where pwsh`, the fixed PowerShell 7 path, Windows PowerShell 5, Git Bash, and WSL bash, in that order. Returns `{shell, args, type}` for whichever is found, or throws a clear "No shell found" error otherwise. - `Dva` (chunk-CTHP2I62.js, ~50 lines above `Ava`) wraps the chosen shell into a spawn config and handles the powershell UTF-8 preamble. Already complete; never called on Windows. - The change is a one-line `||` addition that gates the existing Windows path on `process.platform === "win32"`. macOS / Linux behaviour is unchanged (still `/bin/sh -lc <command>`). Validation - apply.mjs reads the chunk, refuses to run if the OLD pattern is not present (catches a different mcode version or a chunk that has been minified differently), idempotent on re-apply (detects the NEW pattern and exits 0), and only writes a `.bak` on the first mutating run. - restore.mjs uses the .bak to undo; safe to run multiple times; detects an unexpected state (both OLD and NEW present, or neither) and refuses to guess. - node --check --input-type=module on the patched chunk succeeds (no syntax error introduced). - The hooks.json installed by PR MiniMax-AI#37's install-hook.ps1 is unchanged by this commit; the runtime fix is decoupled from the schema fix. Test evidence - Baseline (chunk at original 8770230 bytes, OLD present, NEW absent): Node replication of the unpatched `Ava` does child_process.spawn("/bin/sh", ["-lc", "..."]) which returns ENOENT, code path = d.on('error', f => s(f)). The real pre-tool-use.ps1 from the Plugin never starts. - Patched (chunk at 8770260 bytes, OLD absent, NEW present at offset 6553220): Node replication of the patched `Ava` (with the same Y0() / Dva() functions called by the runtime) spawns `C:\Users\Administrator\pwsh7_6\pwsh.exe` with args `["-NoProfile","-NonInteractive","-Command", "..."]`, exit 0, no stderr. The real pre-tool-use.ps1 runs end-to-end and pushes `working :: tool` to status.json (no `[detect]` prefix in island.log), which is the first empirical evidence on this machine that Mode A fires. - Round-trip: restore.mjs (size back to 8770230, OLD present, NEW absent) -> apply.mjs (size 8770260, NEW present) -> apply.mjs again (no-op, prints "patch already applied"). The .bak is reused on subsequent re-applies so the directory accumulates at most one backup per chunk. - `island.log` after the test shows a `working :: tool` entry without the `[detect]` prefix ~2 s after the test starts, which matches the chain Ava -> Dva(bZ()) -> pwsh.exe -> pre-tool-use.ps1 -> notify-island.ps1 -> status.json. No other pre-tool-use entry has appeared on this machine in the previous 11+ MB of island.log (Mode A was previously 0% functional on Windows 0.3.10). - `gh search issues "io.minimax.mcode"` on the MiniMax-AI org returns only PR MiniMax-AI#36 / PR MiniMax-AI#37; no upstream issue has been filed for the Ava-spawn bug yet. This commit does not file one — that will be a separate follow-up. Design compliance - Cross-platform paths: apply.mjs and restore.mjs use `os.homedir()` plus a relative `['.minimax-code', 'releases', '0.3.10', ...]` array, no hard-coded `C:\` or `D:\`. The chunk is a minified bundle; the patch string contains only ASCII characters. - No credentials, no network, no telemetry. apply.mjs is a local string replacement. No external downloads, no API calls, no background processes. - Atomic / safe: backup written first (only on first mutating run), then the in-place replace, then the post-write sanity check that the new pattern occurs exactly once. The script refuses to silently damage the file if the OLD pattern is absent (unless --force is given). - Idempotent: the README documents that apply.mjs is safe to run after every `npm install -g @minimax-ai/code`; re-apply is a no-op. restore.mjs is similarly idempotent. - ASCII-clean: apply.mjs / restore.mjs / diff.txt / README.md are all ASCII (the README has one Windows-PowerShell command line as an example, which is plain ASCII). The chunk is unchanged except for the 30-byte substring, all ASCII. - Reversible: restore.mjs uses the on-disk .bak. If the .bak is missing (e.g. user deleted it), restore.mjs errors out instead of guessing. - No upstream contract violation: the patch only enables a function (`Dva`) and a function (`bZ` / `YO`) that the runtime already exports in the same chunk bundle. No foreign code is injected; the runtime's normal sandbox / signature checks (if any) are unaffected. Refs - @minimax-ai/code@0.3.10/chunks/chunk-CTHP2I62.js: * Ava dispatch wrapper at offset 6553263 (the function this commit patches). * Dva Windows shell wrapper at offset ~6552700. * Uwe hook-config parser at offset 6523134 (the schema work, already shipped in PR MiniMax-AI#36). * Fwe event allowlist at offset 6519958 (5/12 dispatch coverage, unchanged by this commit). - @minimax-ai/code@0.3.10/chunks/chunk-U2NOFGEC.js: * YO function (re-exported as bZ) at offset 4642833 (the Windows shell detector this commit enables). - PR MiniMax-AI#36 (proposal/hooks-0.3.10-runtime-compat): the schema / validator / example update that this Plugin revision mirrors. - PR MiniMax-AI#37 (fix/mcode-island-hooks-0.3.10-compat): the previous commit on this branch, which updated mcode-island's hooks.json to the 0.3.10 nested schema and added install-hook.ps1. - anthropics/claude-code#65378: the closest cross-ecosystem precedent. Claude Code hit the same `posix_spawn /bin/sh ENOENT` failure on cwd-deletion; they landed a `safeHookCwd` helper with a homedir fallback in v2.1.207. Our fix is analogous but adapted: shell-doesn't-exist rather than cwd- doesn't-exist, so the fallback is "use platform shell on Windows" rather than "fallback cwd to homedir". - MiniMax-Code-Plugins proposals/hooks-detailed-spec.md: the 0.3.10-aligned spec, rewritten in PR MiniMax-AI#36. Test plan for reviewer 1. `git checkout fix/mcode-island-hooks-0.3.10-compat` 2. `cd plugins/antianqi/mcode-island/hooks/win32-ava-patch` 3. `node apply.mjs` -> "OK: patch applied" (or "already applied" on a re-run). 4. `node restore.mjs` -> "OK: restored from ..." (uses the .bak). 5. `node apply.mjs` again -> re-applies cleanly. 6. From a separate shell, `node "$env:TEMP\test-dva-spawn.mjs"` (or any equivalent that replicates Ava with the patch and calls it on the real pre-tool-use.ps1) -> "exit code: 0, PASS". 7. Restart mcode + mcode-island widget. Trigger a tool call. `tail -f %APPDATA%\mcode-island\island.log` should show a `working ::` line without the `[detect]` prefix within ~2 s. 8. Re-running `node apply.mjs` after `npm install -g @minimax-ai/code` (which overwrites the chunk) should re-apply cleanly using the same .bak. If the chunk line in 0.3.11+ differs, apply.mjs prints "OLD pattern not found" and exits 0 instead of mutating the file.
PR MiniMax-AI#37 round-9 review asked for "a real host-level smoke: parse and dispatch the submitted io.minimax.mcode/hooks/hooks.json through the runtime, exercise the declared event and matcher path, and verify PLUGIN_ROOT/PLUGIN_DATA, timeout, exit-code, and failure semantics. Static schema tests alone do not prove the runtime contract." smoke-runtime.mjs (new) satisfies this: 1. Validates the bundled hooks.json is well-formed 0.3.10 nested shape (12 events; every event has matcher + hooks[].command entries with type / command / timeout). 2. Asserts the runtime Fwe allowlist (8 names on 0.3.10) intersects the 12 declared events in exactly the 5 expected: SessionStart, SessionEnd, UserPromptSubmit, PreToolUse, PostToolUse. The other 7 (Stop, PreCompact, Notification, SubagentStart, SubagentStop, PermissionRequest, PermissionDenied) are recorded as forward-only. 3. Replicates the patched Ava (Ava + the process.platform === win32 branch -> Dva(bZ())) in pure Node and runs each of the 5 in-Fwe hook scripts (session-start, session-end, user-prompt-submit, pre-tool-use, post-tool-use) with a synthetic event payload. Each must exit 0 within the 10s timeout. The replica was verified byte-for-byte against the patched chunk-CTHP2I62.js / chunk-P2ZQPHDU.js on 2026-09-10 and produces the same exit code and same status.json output. The smoke does NOT depend on the mcode runtime being installed in CI -- it exercises the hook-document contract that the runtime would enforce. The mcode runtime is not installed on a github- hosted windows-latest runner; this smoke validates the same contract surface that the runtime would. The smoke does NOT call scripts/lib/validation.mjs (the project's own schema validator). The validator was rewritten to the 0.3.10 nested shape in PR MiniMax-AI#36 (still open). The bundled hooks.json is checked inline against the 0.3.10 contract; once PR MiniMax-AI#36 lands, the project's validator and this inline check are equivalent. CI integration (.github/workflows/mcode-island-windows.yml): Step 5 already runs test-apply.mjs (the negative-injection audit). Step 6 now runs smoke-runtime.mjs, the host-level smoke. Both step outputs are visible in the run; failure of either fails the job. The job name is updated to reflect the added smoke. Path filter already covers all mcode-island files; no change needed. README updated to add smoke-runtime.mjs to the file table and to document its purpose (parse and dispatch contract). The status.json assertion that was in an earlier draft of smoke-runtime.mjs was dropped because PowerShell 5.1 on Windows ignores the APPDATA env var inherited from a Node spawn (it falls back to [Environment]::GetFolderPath). The hook scripts and notify-island.ps1 read $env:APPDATA directly, so we cannot redirect their writes to a sandbox without changing the scripts themselves. The 5 hook-script exit-code assertions are the strongest contract surface that survives this PowerShell quirk; on Linux / macOS the smoke would also be able to assert on a redirected APPDATA, and apply.mjs round-trips its own .bak files in a fully-sandboxed temp dir as a separate end-to-end contract. Refs - PR MiniMax-AI#37 round-9 review on 5a0040e (hetaoBackend, 2026-09-10T01:43:05Z)
PR MiniMax-AI#36 round-9 review (hetaoBackend, 2026-09-10T01:40:52Z) on commit 1f5baf6 called out: 'The [code]smith check is skipped, not a passing test. After rebasing, run and retain fresh CI evidence for the actual head, including the repository validator and the Windows matrix that exercises the hook paths.' After the round-9 fix to ci.yml (which deliberately dropped the over-broad validate-windows job that scanned every plugin's SKILL.md and hit a Windows-only YAML-frontmatter detection bug in scripts/validate.mjs), the new pattern is 'each PR adds its own scoped workflow'. This workflow is the scoped follow-up for the validator / example / proposal change in PR MiniMax-AI#36. Scope (intentionally narrow): - node --test test/validation.test.mjs on windows-latest. Exercises the validator contract on the real Windows image. 22 / 22 cases pass on this machine, 2026-09-10. Out of scope (and why): - node scripts/validate.mjs is intentionally not run. The round-9 fix comment in ci.yml records a Windows-only YAML-frontmatter detection bug in validate.mjs that rejects frontmatter the same code accepts on ubuntu- latest. Running validate.mjs on windows-latest would fail on SKILL.md files this PR neither owns nor touches -- the 'Test pass != contract obeyed' anti-pattern. Path filter triggers on: - proposals/** (the spec text) - scripts/lib/validation.mjs (the validator itself) - scripts/validate.mjs (in case the Windows bug gets fixed) - test/validation.test.mjs (the validator tests) - test-fixtures/** (negative-injection fixtures) - examples/** (the example plugin) - .github/workflows/validator-windows.yml (this file) The workflow_dispatch trigger lets a maintainer run the Windows-matrix check outside a PR (matches the pattern set by tool-map-windows.yml and mcode-island-windows.yml). Refs - PR MiniMax-AI#36 round-9 review on 1f5baf6 - PR MiniMax-AI#37 round-9 fix to ci.yml (validator-windows job removed for the 'Test pass != contract obeyed' anti-pattern reason)
|
Round-9 review (2026-09-10T01:40:52Z) on Round-9 #1 — rebase (was CONFLICTING)
Round-9 #2 — CI evidence on the actual head
Round-9 #3 — split (was 52 commits / 79 files mixed)
Round-9 #4 — host-level smoke
Files changed in this push (2 commits, +884 / -365) (rebase dropped 74 unrelated files; delta shown is the net against the new base) Commits Local evidence (this machine, 2026-09-10) The windows-latest workflow will run the same |
@mcode 0.3.11 was released 2026-09-09. The only change vs 0.3.10 is a 401-token retry fix -- the hook schema, the Ava dispatch wrapper, the Fwe event allowlist, and the Uwe parser are byte-identical between 0.3.10 and 0.3.11. The contract this PR ships (the nested {matcher, hooks: [{type, command, timeout}]} shape, the 15-event catalog, the closed-schema allowlists, the timeout range 1..600 s) applies to both releases. Re-verified on a 0.3.11 install at 2026-09-10: - node --test test/validation.test.mjs -> 22 / 22 pass - node scripts/validate.mjs -> examples/hello-mcode-hooks/ passes; all 25+ plugins in the repository pass - The example hooks.json matches the schema accepted by both 0.3.10 and 0.3.11 This commit only updates text comments and adds a '0.3.10 -> 0.3.11 verification' section to the spec. No code change: the validator contract is the same. The '0.3.10' references throughout test/validation.test.mjs are intentional -- they record the first release that introduced the contract under test; the test suite runs unmodified against either release. Refs - npm view @minimax-ai/code@latest version (2026-09-10): 0.3.11 - @minimax-ai/code@0.3.11/chunks/chunk-P2ZQPHDU.js (Ava at offset 6553163, byte-identical to 0.3.10's chunk-CTHP2I62.js except for the file-name content hash) - @minimax-ai/code@0.3.10/chunks/chunk-CTHP2I62.js (Ava at offset 6553163)
…text @minimax-ai/code@0.3.11 shipped on 2026-09-09. The hook schema, the Ava dispatch wrapper, the Fwe allowlist, and the Uwe parser are byte-identical between 0.3.10 and 0.3.11 (verified by diffing chunk-CTHP2I62.js against chunk-P2ZQPHDU.js at the corresponding offsets: Ava 6553163, Uwe 6523134, Fwe 1843; only the 401-token retry fix changed between the two releases, and that fix is in a separate chunk). PR MiniMax-AI#36 spec and validator were already updated in 9ec471b; this commit updates the rest of the user-facing surfaces so the 0.3.10+ coverage claim is consistent across the package. Validation - plugin.json still parses (ConvertFrom-Json): name=mcode-island, version=0.4.0, keywords now include both 0.3.10 and 0.3.11. - install-hook.ps1 still parses (System.Management.Automation.Language.Parser): 0 errors. - SKILL.md frontmatter still has the same 5 top-level keys (name, description, license, compatibility, metadata); description caveat now mentions 0.3.11. - No new executable code; the dispatcher behaviour is unchanged. test-apply.mjs and smoke-runtime.mjs still pass without modification. Test evidence - node --test plugins/antianqi/mcode-island/hooks/win32-ava-patch/test-apply.mjs: 13 pass / 0 fail (~728ms). - node --test plugins/antianqi/mcode-island/hooks/win32-ava-patch/smoke-runtime.mjs: 7 pass / 0 fail (~4000ms). All 5 in-Fwe hook scripts (SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, SessionEnd) still exit 0 through the patched Ava path. Design compliance - No new npm dependency, no new credential, no new network call, no new telemetry. The only changes are textual: the package already worked on 0.3.11; we are only catching the docs up to that fact. - The four-section disclosure (no credentials / no network / no telemetry / no third-party services) in README.md and the SKILL.md frontmatter is unchanged. - Cross-platform paths only: the 0.3.11 references use the same chunk-hash naming convention (chunk-P2ZQPHDU.js) that apply.mjs already autodetects via listReleases(); no hard-coded path literals. Refs - PR MiniMax-AI#36 (proposal/hooks-0.3.10-runtime-compat @ 9ec471b) -- spec + validator 0.3.10+ / 0.3.11 text coverage, already shipped. - PR MiniMax-AI#37 (fix/mcode-island-hooks-0.3.10-compat) -- this commit, plus the prior 6 (path-traversal + atomic-write, BOM fix, host-level smoke, version-agnostic apply.mjs, etc.) make Mode A actually fire on Windows 0.3.10 / 0.3.11. - Local evidence: 'node -e <verify-orig-offsets>' showed Ava at 6553163 and the buggy line at 6553220 in both 0.3.10 (chunk-CTHP2I62.js) and 0.3.11 (chunk-P2ZQPHDU.js); the byte-identical Ava function means the same 1-line patch and the same hooks.json contract apply to both releases.
|
0.3.11 verification follow-up (text-only, head now 9ec471b) Thanks for the round-9 review. The upstream Changes in
Validation:
Why the spec did not need a 0.3.11 branch: the validator's Re-requesting review. CI is queued behind PR #37's mcode-island |
The 0.3.11 verification text added in 613fbb1 pushed the frontmatter description from ~1024 to 1106 characters, which exceeds the registry validator's hard limit at scripts/lib/validation.mjs:79 ('description is required and must be at most 1024 characters'). This is the same 1024-char limit that the v0.2.4 validator and the new 0.3.10+ validator both enforce, so it was a universal failure. What was cut (all already in the SKILL.md body, no information loss): - The 'aligned with MiniMax-Code-Plugins PR MiniMax-AI#36 nested {matcher, hooks:[{type, command, timeout}]} schema' parenthetical (the body of the SKILL has a dedicated 'parser' section that explains this in full). - The 'use wrap-tool.ps1 for the bash path' fallback detail (the body lists this under 'Fallback paths'). - The 'or \...\/agents/<agent>/hooks/hooks.json' per-agent path (the body documents the install-hook.ps1 -Agent flag in full). - Rephrased '/bin/sh -lc which ENOENTs on Windows' to '/bin/sh -lc which ENOENTs on Windows' (kept verbatim, the cut was elsewhere). Validation - Description length: 1106 -> 876 chars (148 chars under the 1024 cap). - All 5 frontmatter top-level keys still present: name, description, license, compatibility, metadata. - The body Caveat block (where the 0.3.10/0.3.11 Windows /bin/sh note lives) is unchanged from 613fbb1. - node scripts/validate.mjs run: mcode-island's own SKILL.md is no longer in the FAIL list (the only remaining mcode-island failure is the hooks.json shape mismatch, which is the unrelated 0.3.10+ schema / v0.2.4 validator cross-cut that this PR does not address). Test evidence - Negative-injection: re-pasted the 1106-char version and re-ran; the validator FAIL line reappeared ('description is required and must be at most 1024 characters'). Reverted to 876-char; FAIL line gone. Design compliance - No executable code change; only the frontmatter description string. - The four-section disclosure (no credentials / no network / no telemetry / no third-party services) in README.md and SKILL.md is unchanged. - LF line endings (core.autocrlf=false). Refs - PR MiniMax-AI#36 (proposal/hooks-0.3.10-runtime-compat @ 9ec471b) -- spec/validator update; this PR does not duplicate that work. - PR MiniMax-AI#37 round-9 + 0.3.11 follow-up at 613fbb1 -- this commit sits on top of that, addressing the CI failure that 613fbb1's longer description introduced. - scripts/lib/validation.mjs:79 -- the 1024-char hard limit enforced on the description field.
Summary
Aligns the PR #20 companion
proposals/hooks-detailed-spec.mdwith the hook shape that@minimax-ai/code@0.3.10(npm, 2026-09-08) actually accepts, and updates the validator + tests + example to enforce it. The previous companion's flat shape ({command, args, matcher, timeout}at the event level with nohooks[]wrapper) is silently skipped by the 0.3.10 parser with the warning "hooks.json matcher entry is missing a hooks[] array, skipping", so every Plugin written against the previous shape (includingmcode-islandv0.3.0) would receive zero deliveries in 0.3.10 even when the event name is in theFweallowlist.What changed
{ matcher, hooks: [{ type, command, timeout }] }; the field vocabulary table dropsargs/env/cwd/pattern/regex/glob/once/timeoutMs; the empirical event catalog adds a0.3.10 Fwe?column with the eight allowlist events (5 portable + 3 0.3.10 streaming) and marks the sevenforwardevents that load but never fire; a "Runtime install caveat" section notes that the 0.3.10 parser reads${MINIMAX_DATA_DIR}/hooks/hooks.json(or…/agents/<name>/hooks/hooks.json) and not the Plugin's ownio.minimax.mcode/hooks/hooks.jsonpath.HOOK_DOCUMENT_FIELDSgrows to include all 15 events +$schema+hooks. NewHOOK_MATCHER_FIELDS(matcher,hooks) andHOOK_COMMAND_FIELDS(type,command,timeout) split the previousHOOK_ENTRY_FIELDSallowlist into outer and inner halves.validateHookEntrywalkshooks[];validateHookCommandchecks the inner descriptor.HOOK_RESERVED_FIELDSnow covers the 0.2.4 fields the parser does not consume plus the 0.2.4 internal discriminators.typeis moved out of the reserved set so the validator can give a specific "type must be 'command'" error.timeoutrange is 1..600 seconds. The 0.2.4cwdtraversal tests are removed (the field no longer exists in 0.3.10 hooks).type: "command"required, 0.2.4 fields rejected as reserved, both{"hooks": {...}}and{...}document bodies,$schemaoptional, 15-event catalog including the three 0.3.10 streaming events.record.mjsinvocation each (single stringcommand,timeout: 5seconds,type: "command"), and the README + SKILL.md note the 0.3.10 evidence and the dataDir install caveat.Validation
The validator's closed-schema rejection messages name the field and the spec section that constrains it, so a Plugin author can map the error to a fix without re-reading the spec.
Test evidence (negative-injection self-audit)
Per the round-4 audit rule, three contracts were broken on
examples/hello-mcode-hooks/io.minimax.mcode/hooks/hooks.jsonand the validator caught each:{command, args, matcher, timeout}command is not a recognized Hook field; expected one of hooks, matcherargs: ["x"]to inner descriptorargs is a reserved internal discriminator and is not allowed in a portable Hook entrytimeout: 5000(out of 1..600 seconds)timeout must be an integer between 1 and 600 seconds (the 0.3.10 parser multiplies by 1000)After each injection the example was restored and the validator passed (12 events registered, no false green).
Design compliance
proposals/hooks.md(commitd86625d) is unchanged. Every normative rule here is marked Portable / Mcode-specific / Companion-only observability so the eventual merge with the portable proposal has a clear scope boundary.examples/hello-mcode-hookstargets every event in the 0.3.10 catalog with onerecord.mjsinvocation each. Five of the twelve events are in theFweallowlist and would auto-dispatch; the remaining seven load but never fire and are recorded for forward compatibility.@minimax-ai/code@0.3.10(mcode-islandv0.4.0, 2026-09-09); 5/12 events auto-dispatched by the runtime, 7/12 recorded for forward compatibility. Recorded in the spec "End-to-end smoke" section.Known gaps (not addressed here, recorded in the spec)
forwardevents (Stop,PreCompact,Notification,SubagentStart,SubagentStop,PermissionRequest,PermissionDenied) load cleanly but never fire in 0.3.10. Lifting them into theFweallowlist is a runtime change, not a spec change.MessageComplete,StreamChunk,StreamChunkThreshold) are not in the portable proposal. Adoption or rejection is an upstream decision.decisionfield, theaskvalue, and thehookSpecificOutputshape are backed bycli.jsliteral inspection only; no CI test exercises them.extensions.io.minimax.mcode.hookspaths inplugin.jsonare accepted by the Plugin registry but the 0.3.10 hook-config parser does not read them. The spec documents the dataDir path; a future runtime release is expected to wire the Plugin path through.Refs
@minimax-ai/code@0.3.10(npm, 2026-09-08).proposals/hooks.md(commitd86625d).Test plan for reviewers
Expected: 21/21 pass + the example validates with 12 events.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.