Repository navigation
Description
[Windows 11] GOA on Claude Code loses every hook path (hooksSupported()=false): turn-end state never reported, no idle wake-up, worker limit miscounts — OpenCode is unaffected
Environment
- enowX v0.7.2, Windows 11 (x64), GOA agent = Claude Code, workers on non-Anthropic models via an LLM gateway
- Related but distinct from [Windows 11] Follow-up for v0.7.2: OpenCode GOA composer regex mismatch, watchdog stall flood on reported tasks, and worker tool scoping #51: that one covers OpenCode composer regexes and the stall flood's symptom; this one is the upstream cause on the Claude Code path
Summary
On Windows, core/agent/hooks_windows.go:7 hard-disables all of enowX's agent hooks:
//go:build windows
func hooksSupported() bool { return false }This is understandable (hook commands are POSIX shell), but Claude Code on Unix is a first-class citizen through those hooks — and the mechanisms around it silently fall back or break on Windows:
| Mechanism | Unix (hook-driven) | Windows (fallback) |
|---|---|---|
HookArgs("claude") (hooks.go:43-45) |
full --settings hooks JSON |
nil |
| Turn-end state reporting | Stop → StateDone |
never — SessionInfo.State never becomes StateDone from agent hooks, so working→done transitions are never reported |
WorkerStatus (core/goa/board.go:160-176) |
maps state → board status, event-driven | can never return GOATaskTurnEnded from session updates — the board never advances past a finished turn that way |
Idle wake-up (GOAHookArgs, hooks.go:113) |
asyncRewake background hook wakes GOA on exit 2 |
absent — no route into an idle GOA except typed-in lines |
Context injection into GOA (goaHookLines, goa_inject.go:196-201) |
additionalContext / Stop-continue |
no path at all |
Enforcement (LevelOf, level.go:21-26) |
GOAEnforced (PreToolUse refuses non-enowX tools) |
GOALimited (pinned by TestLevels windows branch, core/goa/goa_test.go:91-94) — restricted at start only, nothing policed at run time |
ReportsItself("claude") (platform.go:27) |
true |
false |
PermissionRequest |
exact ask/answer round-trip via hook | regex-only via PromptShown/PromptKeys (docs/goa-tools.md:57 confirms "any agent on Windows") |
(Codex is on the same path, worse: its hook invocation is POSIX argv (/bin/sh -c, hooks.go:162) and it is GOAWatched on Windows — core/goa/goa_test.go:95-97.)
What still works on Windows (to avoid conflation)
- The screen watch still produces some state:
watchPrompts(daemon/session_prompts.go:71-78) setsStateWaitingfrom recognized prompts andStateWorkingafter one clears — so a worker asking permission does move the board. What never happens is the working→done transition. - Queued tasks still deliver via the stall watch's
queuedCanGo(goa_stall.go:118-134), and turn-over tasks can move viaturnOverOnScreen → goaTurnOverSeen(goa_stall.go:152-156 → goa_tasks.go:517-535). goa report+goaReportSeen(goa_tasks.go:493-513) records outcome/said but does not change task status — so the board status still never advances from reports alone.
Concrete failures (code-level, cited)
-
Turn-end is never reported by Claude Code itself on Windows.
WorkerStatuscan't returnGOATaskTurnEndedwithoutStateDone, which no Windows path sets. GOA relies entirely on (a) workers voluntarily running$ENOWX_BIN goa report— which small non-Claude models routinely skip — and (b) the screen fallback. -
The screen fallback's reliability at runtime is uncertain — observed failures, mechanism needs evidence. The repo's fixtures do match Claude Code's idle composer (
PromptEmpty/TurnOverShowntests at prompts_test.go:110, 136-137;emptyPlaceholderprompts.go:168 contains Claude Code's exact placeholder; Windows ConPTY hardening exists insplitWrappedRuleprompts.go:302-322,turnOverSpinlockStillgoa_stall.go:139-141,goaInjectWithWakegoa_inject.go:124-142). Yet on this machine GOA was not told of finished turns and lines sat queued. Two candidate mechanisms we could not separate without a live capture:- Sticky
StateWorking: once a session reportsStateWorking(watchPrompts after a prompt clears, or anything else),goaCanType(goa_inject.go:156) requiresState != Workingand nothing on Windows ever resets it toStateDone— typing into idle GOA is blocked regardless of the composer. - ConPTY rendering mismatch: the real idle/completed screen on Windows may differ from the fixtures (escape sequences, box-drawing variants) so
PromptEmpty/TurnOverShownreturn false where the fixtures return true.
Attaching realenowx read <goa-session>/ worker screen text from a Windows machine would settle this; happy to capture on request.
- Sticky
-
False worker limit.
goaActiveWorkerCount(goa_tasks.go:706-721) counts!info.Exited && info.State != proto.StateDone. On WindowsStateis neverStateDone, so finished-but-idle workers count as active until the process exits or hibernates → delegation is refused with "too many active workers (4/4)" (goa.go:21, goa_tasks.go:293-294) while nothing is actually running. -
Slow stale-task detection.
goaStaleWork(goa_stale.go:31-34) shortens the wait toqueuedQuiet(20 s) only whenState==StateDone && ReportsItself(agent); for Claude Code on Windows it falls to the full idle limit (120 s default, core/goa/stall.go:15). -
Lines waiting for an idle GOA can pile up. They live in the tell queue (
goaTellMax = 200, daemon/goa.go:370-388) until GOA's next hook or a typing moment; with noasyncRewakeon Windows and the typing gate in 2, real worker reports can be evicted by the cap — the eviction flood documented in [Windows 11] Follow-up for v0.7.2: OpenCode GOA composer regex mismatch, watchdog stall flood on reported tasks, and worker tool scoping #51 §2.
Why OpenCode doesn't hit this
OpenCode never had hooks on any platform, so its whole path is the screen: enforcement via its config file (goaOpenCodeEnv, platform-agnostic; LevelOf returns GOAEnforced for it everywhere, level.go:19-20) and composer detection via openCodeEmpty (core/agent/prompts_opencode.go:32-63), which explicitly handles the Windows ctrl+p commands hint row.
Suggested fixes
- Port the hooks to Windows — rewrite, not wrap. The command text is POSIX (
[ -z "$ENOWX_BIN" ] || "$ENOWX_BIN" hook …, hooks.go:21-35); neithercmd /cnor PowerShell can execute it as-is. A Windows branch would rewritehookCommand/eventCommandper shell (if defined ENOWX_BIN %ENOWX_BIN% hook …for cmd,if ($env:ENOWX_BIN) { … }for PowerShell) and pass hooks in the settings-file form (docs/agents.md:203; the file-form machinery already exists, core/agent/sessionfiles.go:31). Two things need verifying/sign-off: which shell Claude Code uses for hook commands on Windows (the repo's own comment says agents "may not" run POSIX ones), and that flippingLevelOf("claude")fromGOALimitedtoGOAEnforcedis intended. - Dedicated Claude Code Windows composer capture if 2's mechanism turns out to be ConPTY mismatch: a
splitWrappedRule-style reader proven against real Windows screens (the treatmentopenCodeEmptygot), plus un-stickingStateWorking(or lettinggoaCanTypefall back to the screen whenStateis stale). - Fix the miscount regardless of 1/2.
goaActiveWorkerCountshould treatState == ""as unknown, not "active forever": fall back to a screen/CPU probe (the existingqueuedCanGopattern, goa_stall.go:118-134, withturnOverCPUgoa_stall.go:145) before counting a worker active.
Happy to test a build on this machine — the daemon and a full GOA+workers setup is already running here, and I can attach screen captures for 2.
Activity
[-][Windows 11] GOA on Claude Code loses every hook path (hooksSupported()=false): state never reported, idle lines never delivered, worker limit miscounts - OpenCode is unaffected[/-][+][Windows 11] GOA on Claude Code loses every hook path (hooksSupported()=false): turn-end state never reported, no idle wake-up, worker limit miscounts - OpenCode is unaffected[/+]sicknewbie commentedon Nov 11, 2026
Revised the report after a self-review pass against the code: two headline claims were overstated and one fix was wrong as written.
Findings 3-5 (worker miscount, slow stale detection, tell-queue pile-up) verified unchanged.