Skip to content

fix: run the hook bridge asynchronously and deliver guidance per call - #55

Merged
vitaliyslion merged 1 commit into
mainfrom
fix/hook-bridge-async
Oct 25, 2026
Merged

vitaliyslion merged 1 commit into
mainfrom
fix/hook-bridge-async

Conversation

@vitaliyslion

@vitaliyslion vitaliyslion commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The hook bridge ran gortex hook --agent=pi through execFileSync with inherited stderr. Every tool call paid for that in the TUI, and the same path had a few contract gaps.

  • Async seam (src/runtime.ts): the process seam is now an async execFile, with stdin piped and closed and stderr collected and dropped.
    • A slow hook no longer freezes the TUI.
    • Hook stderr no longer paints into the editor.
    • Esc kills the hook through ctx.signal.
  • Windows shims: with a .cmd/.bat shim, a timeout or an abort kills the whole tree under cmd.exe (taskkill /T /F). Node alone kills only cmd.exe and leaves gortex running. The old execFileSync timeout leaked the same way.
  • Fail open (src/hook.ts): callHook returns an empty decision for any reply that is not a JSON object. Before, null threw inside the tool_call handler and blocked the call.
  • PowerShell: Pi's powershell tool maps to Bash, as Gortex's own Copilot adapter does. Cmdlet forms such as Get-Content still pass, because Gortex's shell classifier knows only POSIX commands. That half belongs upstream.
  • Guidance placement (src/index.ts): additional_context is held per call and appended to that call's tool_result. Before, it was a displayed steer that arrived after the batch and cost one model request per advised call. A call a codemode script makes has its guidance carried to the top-level call's result, so the script's result is not altered.
  • Docs: docs/architecture.md (Channel 2, Failure posture).

Item 3 of the issue no longer applies since #36.

User-visible change: guidance now sits inside the tool result, so it shows only when the row is expanded (ctrl+o). Before, it was always shown as a [gortex] chat block.

Reproduction

Real Pi 1.3.0 loading this checkout with pi -e, a fake OpenAI-compatible model, and a wrapper around the real gortex v0.64.8 on PATH.

Item Before After
Hook writes to stderr Line drawn inside the editor frame Nothing on screen
2 s hook, 3 parallel bash calls, Esc at about 4 s No repaint for 6 s; all three commands ran Spinner animates; Esc aborts within about 0.5 s; no command runs; the running hook is killed
GORTEX_HOOK_MODE=enrich, 2 parallel reads 2 chat blocks, 3 model requests Guidance inside each read's tool result, 2 model requests
Same cat of indexed source via bash and powershell powershell reached the hook raw and was allowed Both reach the hook as Bash and are blocked
Hook answers null Call failed with "Cannot read properties of null" Call runs, no extension error

Closes #24

Testing

  • npm run check passes (180 tests)
  • New behaviour has a suite that drives it through Pi's ExtensionRunner (see docs/tests.md):
    • test/hook.test.ts covers Pi's event loop running while a gated hook is pending, an abort through ctx.signal, a null reply, and soft guidance. The guidance cases are: in its own call's result, never sent as a message, parallel calls, a call ending without a result, kept structuredContent, a result with no content, and calls made by a script.
    • It also runs the real seam against node children: stdin, non-zero exit, missing binary, timeout, stderr kept off the host's, and abort killing the child.
    • The hook double is async and can gate a call; the harness gives handlers an abortable ctx.signal.
  • Tried by hand in Pi (pi -e /path/to/checkout), Pi 1.3.0 on Linux against a live gortex daemon:
    • every row of the table above
    • the briefing still arrives on the first turn
    • a plain read of indexed source is still blocked
    • a Gortex edit still renders its diff
    • a 5 s hook timeout kills the hook and lets the call run
    • pgrep -af "gortex hook" is empty after an Esc abort

Checklist

  • No runtime dependencies added

  • README configuration table updated, if config changed (no config change)

  • docs/architecture.md updated briefly, only if a design decision changed: the hook runs off Pi's event loop and is killed on abort, and guidance travels with its call's result

  • Works on Windows paths and process spawning, if touching either. Verified with Windows Node 24 under Wine, using npm's gortex.cmd shim in a path with spaces and parentheses:

    • the hook reaches the shim: briefing on the first turn, Pi read blocked
    • a stdin envelope with " % ^ & | < > !, unicode and a newline arrives intact
    • hook stderr stays off the host
    • after a timeout and after an abort, the gortex process under cmd.exe is gone; before the tree kill it survived both
    • the two new Windows-only tree-kill tests pass, and both fail with the tree kill removed
    • test/hook.test.ts passes 38/38

    On windows-latest all 184 tests pass with none skipped, including the bat-by-path-with-spaces test (which fails only under Wine's cmd.exe), diff.test.ts (which crashes at load under Wine) and both tree-kill tests.

The hook ran through execFileSync with inherited stderr, so a slow hook
froze Pi's TUI and Esc for its whole run, and anything it wrote to
stderr was painted into the editor. The process seam now runs it
asynchronously with stderr piped and dropped, and an Esc abort kills it.
On Windows, a .cmd shim's timeout or abort kills the whole tree under
cmd.exe, so gortex is not left running.

A reply that is not a JSON object, such as null, now fails open instead
of blocking the call. Pi's powershell tool maps to Bash. Soft guidance is
appended to the result of the call it concerns (the top-level call for
one a script made) instead of a displayed steer that cost a model
request per advised call.

Closes #24
@vitaliyslion
vitaliyslion merged commit b03ea54 into main Oct 25, 2026
3 checks passed
@vitaliyslion
vitaliyslion deleted the fix/hook-bridge-async branch October 25, 2026 15:29
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.

Run the hook bridge asynchronously, with piped stdio and per-call guidance

1 participant