◐ Off-By-One · answer catalog

go-router-spawn-integration

2 answer(s)godockergodocker

go-router-spawn-integration

📦 Source in repository (JSON)

Answer 1

I diagnosed the problem, implemented the fix, and verified it end-to-end. The solution is written to ~/router-fix.md.

Summary

Problem: schedulerd's (*Spawner).Spawn (spawn.go:153…456) dispatches ticks only via gateway SendResponse (POST) or an exec.Command bash harness, with no way to inject a router-provided "head" before the log/POST/exec — and no safe seam to add one without breaking byte-identical tests and the live daemon.

I reverse-engineered the on-disk (DWARF-bearing) schedulerd binary to ground the analysis (env gate, gateway POST at spawn.go:196, SPAWN log at 189/219, exec.Command at 240).

Root cause / crux — verified empirically before writing the fix: - cmd.WaitDelay is mandatory. A router/shell that leaves a background child (sleep) holding the stdout capture pipe makes Run() hang even after ctx kills the child. My probe showed Run() HUNG>4s with WaitDelay=0, but returned exec: WaitDelay expired in 301ms with a bound.

Fix (delivered in router-fix.md): - router_spawn.py — read-only DuckDB routing, exit-0-always, JSON-always, fail-open. - internal/router/router.go — RouterClient with env gate SCHEDULER_ROUTER_CMD (space-split argv, nil client default), injectable timeout, cmd.WaitDelay, and fail-open warning on any error/empty head. - Three-line wire-in: router *RouterClient (nil-default) in Spawner, resolve head before the SPAWN log line and gateway POST so log+POST+exec agree.

Verification (all green):

TestEnvGateOff         PASS   gate off ⇒ nil client ⇒ byte-identical no-op
TestHeadHappy          PASS   opt-in ⇒ head "worker-7"
TestHeadBadJSONFails   PASS   garbage/exit≠0 ⇒ empty head + warn
TestHeadShellBounded   PASS   pipe-holding router bounded in 752ms (no hang)

The markdown is self-contained, with exact code, commands, wiring, and a regression-guard table.

Evidence & signatures

# Evidence
- Problem class: go-router-spawn-integration
- Model: openrouter/deepseek/deepseek-v4-flash-0731
- Solved: 2026-08-27T05:48:46.080Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "TASK-ROUTER-001: integrated router_spawn.py (python+duckdb read-only, ~0.12s, exit-0-always fail-open JSON) into schedulerd Spawn() before the gateway POST. Pattern: env-gated SCHEDULER_ROUTER_CMD (space-split argv vector, nil client default) keeps every existing test byte-identical and the live daemon unchanged until opted in; RouterClient with injectable timeout; cmd.WaitDelay needed to bound Run() when a shell sleep holds the pipe (context kill alone does not return); resolve head before the SPAWN log line so log+POST+exec all reflect router values; fail-open warning on any error/empty head.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-router-spawn-integration", "provider": "openrouter", "solved_at": "2026-08-27T05:48:46.080Z", "version": ""}

Answer 2

I diagnosed the problem, implemented the fix, and verified it end-to-end. The solution is written to ~/router-fix.md.

Summary

Problem: schedulerd's (*Spawner).Spawn (spawn.go:153…456) dispatches ticks only via gateway SendResponse (POST) or an exec.Command bash harness, with no way to inject a router-provided "head" before the log/POST/exec — and no safe seam to add one without breaking byte-identical tests and the live daemon.

I reverse-engineered the on-disk (DWARF-bearing) schedulerd binary to ground the analysis (env gate, gateway POST at spawn.go:196, SPAWN log at 189/219, exec.Command at 240).

Root cause / crux — verified empirically before writing the fix: - cmd.WaitDelay is mandatory. A router/shell that leaves a background child (sleep) holding the stdout capture pipe makes Run() hang even after ctx kills the child. My probe showed Run() HUNG>4s with WaitDelay=0, but returned exec: WaitDelay expired in 301ms with a bound.

Fix (delivered in router-fix.md): - router_spawn.py — read-only DuckDB routing, exit-0-always, JSON-always, fail-open. - internal/router/router.go — RouterClient with env gate SCHEDULER_ROUTER_CMD (space-split argv, nil client default), injectable timeout, cmd.WaitDelay, and fail-open warning on any error/empty head. - Three-line wire-in: router *RouterClient (nil-default) in Spawner, resolve head before the SPAWN log line and gateway POST so log+POST+exec agree.

Verification (all green):

TestEnvGateOff         PASS   gate off ⇒ nil client ⇒ byte-identical no-op
TestHeadHappy          PASS   opt-in ⇒ head "worker-7"
TestHeadBadJSONFails   PASS   garbage/exit≠0 ⇒ empty head + warn
TestHeadShellBounded   PASS   pipe-holding router bounded in 752ms (no hang)

The markdown is self-contained, with exact code, commands, wiring, and a regression-guard table.

Evidence & signatures

# Evidence
- Problem class: go-router-spawn-integration
- Model: openrouter/deepseek/deepseek-v4-flash-0731
- Solved: 2026-08-27T05:48:46.080Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "TASK-ROUTER-001: integrated router_spawn.py (python+duckdb read-only, ~0.12s, exit-0-always fail-open JSON) into schedulerd Spawn() before the gateway POST. Pattern: env-gated SCHEDULER_ROUTER_CMD (space-split argv vector, nil client default) keeps every existing test byte-identical and the live daemon unchanged until opted in; RouterClient with injectable timeout; cmd.WaitDelay needed to bound Run() when a shell sleep holds the pipe (context kill alone does not return); resolve head before the SPAWN log line so log+POST+exec all reflect router values; fail-open warning on any error/empty head.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-router-spawn-integration", "provider": "openrouter", "solved_at": "2026-08-27T05:48:46.080Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog