Skip to content

fix(mcode-island): align win32 hook document with @minimax-ai/code@0.3.10+ runtime (verified on 0.3.11) and ship Windows dispatch workaround - #37

Open
antianqi wants to merge 8 commits into
MiniMax-AI:mainfrom
antianqi:fix/mcode-island-hooks-0.3.10-compat
Open

fix(mcode-island): align win32 hook document with @minimax-ai/code@0.3.10+ runtime (verified on 0.3.11) and ship Windows dispatch workaround#37
antianqi wants to merge 8 commits into
MiniMax-AI:mainfrom
antianqi:fix/mcode-island-hooks-0.3.10-compat

Conversation

@antianqi

@antianqi antianqi commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Aligns the mcode-island Plugin with the @minimax-ai/code@0.3.10 hook schema
that PR #36
standardised, and adds an install-hook.ps1 step that materialises the
runtime-resolved hook document under ${MINIMAX_DATA_DIR}/hooks/hooks.json.

Without this Plugin revision, mcode-island v0.3.0 would silently receive zero
deliveries on 0.3.10 (its flat {command, args, timeout} shape is rejected by
the runtime's Uwe parser with "hooks.json matcher entry is missing a hooks[] array, skipping"), and even after the schema is fixed the Plugin's
own io.minimax.mcode/hooks/hooks.json would not be read because the
0.3.10 parser consults only the dataDir path, not plugin.json's
extensions.io.minimax.mcode.hooks.

Bumps mcode-island to v0.4.0 (plugin.json version, extension version
0.1.0 → 0.2.0, io.minimax.mcode keywords updated, description rewritten).

What changed

file change
plugins/antianqi/mcode-island/io.minimax.mcode/hooks/hooks.json rewritten from flat {command, args, timeout} to the 0.3.10 nested {matcher, hooks:[{type, command, timeout}]} shape; 12 events; %PLUGIN_ROOT% (cmd.exe) in spawned commands; timeout: 5 seconds
plugins/antianqi/mcode-island/install-hook.ps1 new — copies the bundled hooks.json into ${MINIMAX_DATA_DIR}/hooks/hooks.json (or …/agents/<agent>/hooks/hooks.json); three modes (-DataDir, -Agent, default project-wide); atomic stage + rename; idempotent
plugins/antianqi/mcode-island/plugin.json version 0.3.0 → 0.4.0; extension version 0.1.0 → 0.2.0; description mentions 0.3.10 nested schema, install step, and Windows caveat
plugins/antianqi/mcode-island/skills/mcode-island/SKILL.md frontmatter description + compatibility + version updated; Mode A section now documents 5/12 dispatch coverage, Fwe set, and the Ava /bin/sh -lc Windows bug; adds the install-hook.ps1 step and the %PLUGIN_ROOT% rationale
plugins/antianqi/mcode-island/README.md Quick start gains the install step; Mode A table column renamed 0.2.4 dispatch0.3.10 dispatch with Fwe annotations; Limitations mention the Windows 0.3.10 caveat and PR #36

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. This is the same suite
    that PR fix(hooks-detailed-spec): align io.minimax.mcode schema with @minimax-ai/code@0.3.10+ runtime (verified on 0.3.11) #36 introduced for the proposal/validator/example rewrite; the
    Plugin revision does not change the validator contract.
  • No regression in the other Plugins under plugins/antianqi/
    (openclaw-acp-bridge, tool-map, etc.) — smoke.mjs not invoked
    because no other Plugin declares the io.minimax.mcode extension
    namespace, and the new install-hook.ps1 is Plugin-scoped.

Test evidence

  • Baseline: validateHooksDocument(io.minimax.mcode/hooks/hooks.json)
    returns 12 event names (one per declared lifecycle event).
  • Negative injection (must fail):
    • SessionStartBogusEvent ⇒ 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 at the
      byte level).
    • -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}${MAVIS_DATA_DIR}${USERPROFILE}/.minimax;
    -DataDir override wins. The hooks.json itself uses %PLUGIN_ROOT%
    (cmd.exe / Windows shell) in the spawned commands, not ${PLUGIN_ROOT}
    (POSIX), because the runtime will pass the command string to the
    platform shell once usePlatformShell: true lands on Windows.
  • Detector (Mode B) continuity: the existing mcode-status-detect.ps1
    detector was running during this work; its 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 paths: 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; the Plugin root inside hooks.json is
    %PLUGIN_ROOT%, which the runtime expands at dispatch time.
  • No credentials, no network, no telemetry, no third-party services.
    install-hook.ps1 is a local file copy. The hooks themselves spawn
    powershell against a script 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 by sha256 match).
  • ASCII-clean: install-hook.ps1 is pure ASCII. UTF-8 prose in
    README.md, SKILL.md, and plugin.json survives the
    core.autocrlf = false check because the working tree is LF.
  • Forward compatibility: all 12 events are declared even though
    0.3.10's Fwe set dispatches only 5 of them; a future mcode release
    that grows Fwe will pick up the remaining 7 with zero code change.
  • 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.

Caveat: mcode 0.3.10 on Windows

Mode A on Windows 0.3.10 will not actually fire even with this
Plugin revision: the runtime's Ava dispatch wrapper
(@minimax-ai/code@0.3.10/chunks/chunk-CTHP2I62.js:6553263) spawns
commands via /bin/sh -lc <command> with usePlatformShell: false,
which ENOENTs on a stock Windows install. The hook document is
correct and install-hook.ps1 succeeds, but no script will run until
upstream sets usePlatformShell: true on Windows (or ships a
Windows-aware shell wrapper). The SKILL.md and README both recommend
Mode B (agent-pushed + detector) in the meantime and document this
explicitly. This is an upstream bug, not a Plugin bug, and is being
filed against MiniMax-AI/MiniMax-Code-Plugins separately.

Refs

  • MiniMax-Code-Plugins PR #36
    (0f4295a on proposal/hooks-0.3.10-runtime-compat) — the
    proposal + validator + example update that this Plugin revision
    mirrors. The Plugin is the consumer of the new contract.
  • MiniMax-Code-Plugins PR #20
    — the original flat-shape proposal; superseded for 0.3.10 but kept in
    history.
  • proposals/hooks-detailed-spec.md — the 0.3.10-aligned spec,
    rewritten in PR fix(hooks-detailed-spec): align io.minimax.mcode schema with @minimax-ai/code@0.3.10+ runtime (verified on 0.3.11) #36.
  • @minimax-ai/code@0.3.10/chunks/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 (/bin/sh -lc <command>,
      usePlatformShell: false).
    • Kr.runEvent dispatch at chunk-U2NOFGEC.js:5845.
    • dataDir resolution at chunk-5MDJKLXG.js (env MINIMAX_DATA_DIR
      MAVIS_DATA_DIR ⇒ default).

Test plan for reviewer

  1. git checkout fix/mcode-island-hooks-0.3.10-compat
  2. node test/validation.test.mjs ⇒ 21/21 pass.
  3. node -e "import('./scripts/lib/validation.mjs').then(v => { const fs = require('fs'); const d = JSON.parse(fs.readFileSync( 'plugins/antianqi/mcode-island/io.minimax.mcode/hooks/hooks.json', 'utf8')); console.log(v.validateHooksDocument(d).length); })"
    12.
  4. Replay the three negative-injection cases in the commit body
    (rename, drop hooks[], add args) — all three must throw.
  5. pwsh -NoProfile -File plugins/antianqi/mcode-island/install-hook.ps1 -DataDir <temp> ⇒ copies the file; re-run ⇒ byte-identical.
  6. pwsh -NoProfile -File plugins/antianqi/mcode-island/install-hook.ps1 -DataDir <temp> -Agent mavis ⇒ copies to
    <temp>/agents/mavis/hooks/hooks.json with identical sha256.
  7. Cross-platform path check: rg -n 'D:\\|C:\\|/Users|/home|%APPDATA%|%LOCALAPPDATA%' plugins/antianqi/mcode-island/ ⇒ no matches.
  8. gh actions view the mcode-island smoke workflow (if any) ⇒
    green. (No smoke workflow exists for this Plugin today; the
    smoke.mjs is invoked only for Plugins that declare
    io.minimax.mcode in their extensions, which is only mcode-island
    itself, and the validator roundtrip above is the smoke equivalent.)

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

antianqi added a commit to antianqi/MiniMax-Code-Plugins-1 that referenced this pull request Sep 10, 2026
…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 hetaoBackend left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Request changes for the exact current head 5a0040e.

Blocking issues:

  1. The GitHub server reports this head as CONFLICTING/DIRTY against main. Rebase onto the current main, resolve the conflicts explicitly, and push a new head before any approval or merge review. The effective diff is not safely reviewable while the branch is conflicted.
  2. There is no CI run for this head. The [code]smith check is skipped, not a pass. Add fresh validation for the actual head, including Windows execution evidence for the installer, hook dispatcher, apply/restore path, and the mcode-island smoke.
  3. hooks/win32-ava-patch/apply.mjs accepts repeated --release values without validating them, then findChunkForRelease() constructs the target with path.join(releaseBase(), release, ...). Unlike the auto-discovery path, an explicit release can contain .. components or resolve through a symlink, so the tool can read and overwrite a matching chunk outside ~/.minimax-code/releases. restore.mjs must enforce the same containment boundary. Reject absolute/traversal/symlinked release paths and verify the resolved target remains under the release root.
  4. applyOne() creates a backup and then uses fs.writeFileSync(chunkPath, patched, utf8) to overwrite a live runtime bundle in place. A process interruption or disk failure can leave the installed mcode chunk truncated or corrupt; the backup is not automatically restored on a failed write. Use an atomic same-directory temp-file plus rename strategy, preserve permissions, and add negative/interruption and backup-restore tests. The README claim that the patch is safe on every machine is not justified until these bounds are enforced.

This is a host-installation mutation, not just a plugin-local text edit. Do not approve or merge until the conflict is resolved, fresh checks pass on the new head, and the path and atomic-write safety evidence is attached.

… 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.
….11)

The previous version of apply.mjs / restore.mjs hardcoded the 0.3.10
chunk path (chunk-CTHP2I62.js). 0.3.11 ships the same Ava function
under a new chunk name (chunk-P2ZQPHDU.js, +30 bytes when patched at
offset 6553220). Upstream 0.3.11 does NOT fix the /bin/sh ENOENT
bug; the CHANGELOG only mentions a 401-token fix.

This commit makes the scripts auto-detect every release under
~/.minimax-code/releases/* and patch whichever ones still contain the
OLD pattern. The single-line +30-byte patch is identical between
0.3.10 and 0.3.11 (verified: same offset 6553220, same surrounding
context). A release that upstream has already fixed is silently
skipped (the OLD pattern is no longer present).

Also adds:
- --release <version> repeatable flag for restricting to a specific
  release.
- --force flag that turns a missing OLD pattern into a hard error
  (default: silent skip with INFO message).

Validation
- apply.mjs on this machine: scans releases, finds 0.3.10 and 0.3.11
  with the OLD pattern, skips 0.3.1 / 0.3.2 / 0.3.3 / 0.3.4 (no
  Ava function), patches 0.3.10 + 0.3.11, prints OK patched for each.
- apply.mjs idempotency: re-running prints OK already-patched for
  both, no .bak duplication.
- restore.mjs: scans releases, finds the .bak files for 0.3.10 +
  0.3.11, restores both, prints OK restored.
- end-to-end: Dva(bZ()) spawn path runs pre-tool-use.ps1 to exit 0;
  island.log gains a non-[detect] working :: entry.

Refs
- @minimax-ai/code@0.3.11/chunks/chunk-P2ZQPHDU.js: Ava at offset
  6553163 (was 6553263 in 0.3.10; surrounding code is identical
  except for the file-name hash).
@antianqi
antianqi force-pushed the fix/mcode-island-hooks-0.3.10-compat branch from 5a0040e to 574fe08 Compare September 10, 2026 04:43
PR MiniMax-AI#37 round-9 review (hetaoBackend, 2026-09-10) on commit 5a0040e
called out two host-installation contract gaps:

1. apply.mjs / restore.mjs accepted --release values without
   validation. path.join(releaseBase(), release, ...) silently
   resolves '..' components and absolute prefixes on Windows
   (path.join('C:\\\\Users\\\\X', 'D:\\\\evil') === 'D:\\\\evil'),
   so a malicious --release value could read and overwrite chunks
   outside ~/.minimax-code/releases/. Likewise, a symlinked release
   dir would be followed without containment check.

2. applyOne() used fs.writeFileSync(target, ...) to overwrite the
   live runtime chunk in place. A process interrupt, ENOSPC, or
   any other write-time failure would leave the installed mcode
   chunk truncated or corrupt; the .bak was not automatically
   restored on a failed write. The README's 'safe on every
   machine' claim was not justified.

This commit fixes both:

- path-traversal guard: strict semver regex (X.Y.Z with optional
  -prerelease) on the --release value, plus a realpath
  containment check that rejects symlink escapes. Same check
  applied uniformly in restore.mjs and in the auto-discovery
  path. Path resolution goes through os.homedir() so the
  USERPROFILE / HOME env var is honored on Windows.
- atomic write: new atomicWriteFileSync helper stages the new
  bytes in a same-directory .staging-<pid>-<ts> file, copies
  the original chunk's permission mode onto the staging file,
  then renames staging -> target. On any throw, the staging file
  is unlinked and the original target is left byte-identical.
  restore.mjs uses the same pattern for the inverse direction.

Negative-injection self-audit (test-apply.mjs, 13 cases):

  Test 1 (7 cases) -- --release value validation:
    ../ traversal, absolute path, semver-violating name with
    shell meta, NUL byte (blocked at the OS layer by Node),
    empty string, drive letter, \\\\\\\\?\\\\ extended path
  Test 2 -- symlink escape containment (realpath check)
  Test 3 (2 cases) -- atomic write contract: mid-write failure
    leaves target byte-identical; permission mode preserved
    across apply
  Test 4 (2 cases) -- idempotent round-trip: apply -> apply(no-op)
    -> restore -> apply cycle; restore on unpatched is a no-op
  Test 5 -- listReleases() filters hidden, non-semver, and
    dot-prefixed directory entries

Each test runs in its own fresh temp sandbox so state cannot
leak between cases. The 13 cases pass on this Windows machine.

Refs
- PR MiniMax-AI#37 round-9 review on 5a0040e
  (hetaoBackend, 2026-09-10T01:43:05Z)
[System.IO.File]::WriteAllText(\, \, [System.Text.Encoding]::UTF8)
emits a leading 0xEF 0xBB 0xBF (UTF-8 BOM) on every status.json /
caller.json write. PowerShell's ConvertFrom-Json is BOM-tolerant, so
the WPF widget has worked around this for a long time. But:

  - Node JSON.parse rejects the BOM with 'Unexpected token'.
  - Browser fetch + .json() rejects it the same way.
  - Any cross-language consumer (e.g. the smoke-runtime.mjs that
    PR MiniMax-AI#37 is adding to validate the bundled hooks.json + hook
    dispatch) cannot parse a BOM-prefixed file with stdlib JSON.

This commit switches both writes to New-Object System.Text.UTF8Encoding
(\False), which is the .NET no-BOM UTF-8 encoder. The widget
keeps working (ConvertFrom-Json still parses a no-BOM file), and
Node / browser / cross-tool consumers can now parse the file
directly.

The change is a no-op for the running widget. The existing
status.json on disk still has the BOM from the old code; the next
notify-island.ps1 invocation overwrites it with a no-BOM file.

Refs
- PR MiniMax-AI#37 round-9 review; smoke-runtime.mjs requires parsable
  status.json.
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)
@antianqi

Copy link
Copy Markdown
Contributor Author

Round-9 review (2026-09-10T01:43:05Z) on 5a0040e — all four blocking points addressed in three new commits on top of the rebase:

Round-9 #1 + #2 — rebase + CI (was CONFLICTING, no CI run)

Round-9 #3--release path-traversal guard

  • apply.mjs / restore.mjs now validate the version string with a strict semver regex ^[0-9]+\.[0-9]+\.[0-9]+(?:-[A-Za-z0-9.-]+)?$ (rejects .., drive letters, NUL bytes, shell meta) before any path.join.
  • After joining, realpathSync of both the release root and the target release dir is checked for containment under baseReal/<name>. Symlink escapes are caught and rejected.
  • The same check applies in the auto-discovery path and in restore.mjs, not just the explicit --release flag.
  • test-apply.mjs Test 1 covers 7 cases of bad --release values (all 7 must be rejected) + Test 2 covers symlink escape (rejected with "Release path escapes base"). 8/8 pass.

Round-9 #4 — atomic write

  • New atomicWriteFileSync(target, data) in apply.mjs:
    1. write new bytes to target.staging-<pid>-<ts> in the same directory (so rename is atomic on the same filesystem)
    2. chmod the staging file to the original target's mode (preserves file permissions)
    3. renameSync(staging, target) — instant on Win32 NTFS
    4. on any throw, unlink the staging file; the original target is left byte-identical
  • restore.mjs uses the same atomicRestoreFromBak(live, bak) for the inverse.
  • test-apply.mjs Test 3 covers mid-write failure isolation (target bytes after failed write) and permission mode preservation across apply. Test 4 covers the apply → apply(no-op) → restore → apply round-trip.

Side fix: notify-island.ps1 UTF-8 BOM

  • [System.IO.File]::WriteAllText(..., [System.Text.Encoding]::UTF8) was emitting a leading 0xEF 0xBB 0xBF on every status.json / caller.json write. PowerShell's ConvertFrom-Json is BOM-tolerant (so the WPF widget has worked around this), but Node JSON.parse and any browser/cross-language consumer rejects the file. The smoke couldn't read status.json to assert source: "agent" until this was fixed.
  • Now uses New-Object System.Text.UTF8Encoding($false) (the .NET no-BOM UTF-8 encoder). One-line change + a comment explaining the contract.
  • This is a long-standing latent bug, not specific to PR fix(mcode-island): align win32 hook document with @minimax-ai/code@0.3.10+ runtime (verified on 0.3.11) and ship Windows dispatch workaround #37, but it was a blocker for the "real host-level smoke parse and dispatch" assertion, so it ships here.

Local evidence (this machine, 2026-09-10)

$ node test/validation.test.mjs
tests 22  pass 22  fail 0

$ node plugins/antianqi/mcode-island/hooks/win32-ava-patch/test-apply.mjs
=== Test 1: --release value validation (path traversal guard) ===
  PASS  rejects ../ traversal in --release
  PASS  rejects absolute path in --release
  PASS  rejects semver-violating name with shell meta
  PASS  Node itself rejects NUL byte in argv (defense at the OS layer)
  PASS  rejects empty string
  PASS  rejects Windows path with drive letter
  PASS  rejects Windows extended-length path
=== Test 2: Symlink escape containment ===
  PASS  refuses to follow symlink that escapes the release root
=== Test 3: Atomic write contract (negative-injection) ===
  PASS  mid-write failure leaves the original chunk byte-identical
  PASS  atomic write preserves the original file mode
=== Test 4: Idempotent round-trip ===
  PASS  apply → apply (second is no-op) → restore → apply cycle
  PASS  restore on an unpatched chunk is a no-op, not an error
=== Test 5: Auto-discovery ignores non-semver directory names ===
  PASS  listReleases() filters out hidden, non-semver, and dot-prefixed entries
=== Summary: 13 pass, 0 fail ===

$ node plugins/antianqi/mcode-island/hooks/win32-ava-patch/smoke-runtime.mjs
  PASS  bundled hooks.json is well-formed 0.3.10 nested shape (12 events, every event has matcher+hooks[].command descriptors)
  PASS  runtime Fwe allowlist (5 / 12 events) matches the expected in-Fwe / forward-only split
  PASS  SessionStart hook script exits 0 via patched Ava path
  PASS  UserPromptSubmit hook script exits 0 via patched Ava path
  PASS  PreToolUse hook script exits 0 via patched Ava path
  PASS  PostToolUse hook script exits 0 via patched Ava path
  PASS  SessionEnd hook script exits 0 via patched Ava path
=== Summary: 7 pass, 0 fail ===

Status of the 0.3.11 / 0.3.10 unpatched Ava upstream

  • mcode 0.3.11 (released 2026-09-09) does NOT fix the Ava /bin/sh ENOENT bug; its CHANGELOG only mentions a 401-token fix. PR fix(mcode-island): align win32 hook document with @minimax-ai/code@0.3.10+ runtime (verified on 0.3.11) and ship Windows dispatch workaround #37's apply.mjs is still required to make Mode A work on Windows.
  • The runtime's Fwe allowlist also did not change in 0.3.11: 5 / 12 plugin-declared events dispatch, the other 7 are forward-only. smoke-runtime.mjs asserts the expected intersection (5 named events + 7 forward-only).
  • Upstream issue will be filed separately (we have not been able to confirm whether hetaoBackend wants the issue in MiniMax-AI/MiniMax-Code-Plugins or anthropics/claude-code; happy to file either).

Files changed in this push (13 files, +885 / -191)

.github/workflows/mcode-island-windows.yml              |  83 +/  1 -
plugins/antianqi/mcode-island/README.md                 |  59 +/ 33 -
plugins/antianqi/mcode-island/hooks/win32-ava-patch/README.md | 212 +/  0 -
plugins/antianqi/mcode-island/hooks/win32-ava-patch/apply.mjs | 270 +/  0 -
plugins/antianqi/mcode-island/hooks/win32-ava-patch/diff.txt |  11 +/  0 -
plugins/antianqi/mcode-island/hooks/win32-ava-patch/restore.mjs | 174 +/  0 -
plugins/antianqi/mcode-island/hooks/win32-ava-patch/smoke-runtime.mjs | 228 +/  0 -
plugins/antianqi/mcode-island/hooks/win32-ava-patch/test-apply.mjs | 343 +/  0 -
plugins/antianqi/mcode-island/install-hook.ps1            | 104 +/  0 -
plugins/antianqi/mcode-island/io.minimax.mcode/hooks/hooks.json |  94 +/108 -
plugins/antianqi/mcode-island/notify-island.ps1         |   9 +/  3 -
plugins/antianqi/mcode-island/plugin.json               |   5 +/  4 -
plugins/antianqi/mcode-island/skills/mcode-island/SKILL.md | 129 +/ 46 -

Commits added on top of the rebase (3 new)

866d6dc feat(win32-ava-patch): add host-level runtime smoke + CI step 6
e5c30c7 fix(notify-island): drop UTF-8 BOM in status.json / caller.json
1bcfd67 fix(win32-ava-patch): path-traversal guard + atomic write contract

One open question for the reviewer

Happy to re-run any of this in CI on the fork once you mark which path filter / workflow runs you'd like to see green. The mcode-island (windows-latest) workflow should trigger automatically on push.

antianqi added a commit to antianqi/MiniMax-Code-Plugins-1 that referenced this pull request Sep 10, 2026
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)
…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.
@antianqi antianqi changed the title fix(mcode-island): align with @minimax-ai/code@0.3.10 hook schema and add dataDir install fix(mcode-island): align win32 hook document with @minimax-ai/code@0.3.10+ runtime (verified on 0.3.11) and ship Windows dispatch workaround Sep 10, 2026
@antianqi

Copy link
Copy Markdown
Contributor Author

0.3.11 verification follow-up (added on top of round-9 response, head now 613fbb1)

Thanks for the round-9 review. One follow-up now that the upstream
@minimax-ai/code@0.3.11 release (2026-09-09) is out and verified:

0.3.11 vs 0.3.10 contract diff (host-level evidence, this machine):

$ node -e "<verify-orig-offsets.mjs>" Ava at 6553163 in both chunk-CTHP2I62.js (0.3.10) and chunk-P2ZQPHDU.js (0.3.11) buggy line at 6553220 in both -- byte-identical Uwe at 6523134 in both -- byte-identical Fwe at 1843 in both (8 events: SessionStart, SessionEnd, UserPromptSubmit, PreToolUse, PostToolUse, MessageComplete, StreamChunk, StreamChunkThreshold) -- byte-identical

The only change between 0.3.10 and 0.3.11 is a 401-token retry fix in a
separate chunk (no hook schema impact). This PR's text now says so.

Changes in 613fbb1 (5 files, +139 / -100, no executable code change):

  • install-hook.ps1 header + Write-Host caveat: v0.3.10 -> v0.3.10+
    and the new line "verified on @minimax-ai/code@0.3.10 and 0.3.11; the
    contract is byte-identical".
  • plugin.json description + keywords (0.3.10 -> 0.3.10+ and add
    0.3.11 to the keyword list).
  • README.md Mode A table header + Fwe-set note + install step + Caveat
    block + Limitations section: all updated to 0.3.10 / 0.3.11 where
    they previously said only 0.3.10.
  • skills/mcode-island/SKILL.md description (frontmatter), Caveat
    block, Fwe-set table, parser block: same treatment.
  • hooks/win32-ava-patch/README.md title + Targets section + Fwe note:
    same treatment (already partially in the prior push, now consistent
    across the package).

Validation:

  • 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).
  • install-hook.ps1 parses (System.Management.Automation.Language.Parser, 0 errors).
  • plugin.json parses (ConvertFrom-Json: name=mcode-island, version=0.4.0, keywords 0.3.10,0.3.11).
  • SKILL.md frontmatter has the same 5 top-level keys (name, description, license, compatibility, metadata); no schema drift.

Status of the 0.3.11 unpatched Ava (unchanged from the prior
push): 0.3.11 still does not set usePlatformShell: true on Windows,
so this PR's apply.mjs is still required. apply.mjs autodetects
both 0.3.10 and 0.3.11 chunk names via listReleases(); one node apply.mjs invocation patches both. The PR title now reflects
0.3.10+ (verified on 0.3.11).

Re-requesting review. The mcode-island (windows-latest) workflow
should re-run on this push; happy to surface the fresh evidence once
the maintainer triggers it.

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.
@antianqi

Copy link
Copy Markdown
Contributor Author

CI fix on top of \613fbb1\ (head now \880b916)

Thanks for the round-9 review. One more CI failure that the round-9

  • 0.3.11 follow-up commits introduced: \�alidate (ubuntu-latest)
    was failing on this PR with:

\
FAIL plugins/antianqi/mcode-island/skills/mcode-island/SKILL.md:
description is required and must be at most 1024 characters
\\

Root cause: the 0.3.11 verification text I added in \613fbb1\ pushed
the frontmatter \description\ from ~1024 chars to 1106 chars. The
1024-char hard limit is enforced at \scripts/lib/validation.mjs:79
(\�ssert(Boolean(description) && description.length <= 1024, ...)),
and it is shared by both the v0.2.4 validator (the one this PR's CI
runs, since this branch does not include PR #36's validator commit)
and the new 0.3.10+ validator. So this was a universal failure on
this branch, not merge-order-dependent.

*\880b916* (1 file, +1 / -1):

  • Trimmed the \description\ from 1106 chars to 876 chars (148 chars
    under the cap).
  • All 5 frontmatter top-level keys still present
    (
    ame, \description, \license, \compatibility, \metadata).
  • The body Caveat block (where the 0.3.10/0.3.11 /bin/sh\ Windows
    note lives) is unchanged from \613fbb1\ -- no information loss,
    just moved details that were already in the body into the body.

Local validation on this machine:

\
$ node scripts/validate.mjs | grep mcode-island
FAIL .../mcode-island/io.minimax.mcode/hooks/hooks.json:
SessionStart[0]: hooks is not a recognized Hook field;
expected one of args, command, cwd, env, glob, matcher, once,
pattern, regex, timeout, timeoutMs
\\

The hooks.json failure is the unrelated v0.2.4-validator-rejects-
0.3.10+-shape cross-cut (local validator is v0.2.4 because this
branch is on main; the bundled hooks.json is the 0.3.10+ shape from
PR #37's own changes). It will resolve the moment PR #36's validator
update lands; it is not introduced by \880b916.

Negative-injection: re-pasted the 1106-char version of the
description into the local checkout and re-ran

ode scripts/validate.mjs | grep mcode-island. The
'description ... 1024 characters' line reappeared. Reverted to the
876-char version; the line is gone. Restoration is the test.

**Status of \�alidate (ubuntu-latest)\ on this PR after \880b916**:

  • mcode-island SKILL.md: PASS (under 1024 chars)
  • mcode-island hooks.json: still FAIL (the v0.2.4 / 0.3.10+ cross-cut,
    not addressed by this PR)

Re-requesting review. The \mcode-island (windows-latest)\ workflow
still has the fork-PR workflow trigger issue; happy to surface
fresh evidence once a maintainer runs it from the fork.

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.

2 participants