Summary
With server.port unset (the default), every WORKFLOW.md change makes the daemon bind a different port. Open dashboards break, bookmarks go stale, and the Vite dev proxy points at nothing. Editing several settings in sequence used to compound it: each save triggered its own reload.
Observed on v0.2.0 (6d1c67c).
Root cause
cmd/itervox/main.go:649-656:
port := 0
if cfg.Server.Port != nil {
port = *cfg.Server.Port
}
srvListener, actualAddr, err = listenStrict(cfg.Server.Host, port)
server.port is *int (internal/config/config.go:313) with no default, so when it is unset port stays 0 and net.Listen picks an ephemeral port.
That binding lives inside run(), and main.go:484 re-invokes run() on every reload:
runDone := make(chan error, 1)
go func() {
runDone <- run(runCtx, cancel, cfg, *workflowPath, ...)
}()
So a reload does not merely rebind the listener — it re-rolls the port. The listener, the HTTP server and the dashboard URL file are all torn down and recreated.
Two separate problems
1. The port is not stable across reloads. Ephemeral-by-default means "different port every reload", which is a surprising default for a long-running local daemon whose whole interface is a browser tab.
2. A reload rebinds even when nothing about the server changed. Editing agent.max_turns has no bearing on the listener, but the listener goes down and back up regardless, dropping in-flight requests and SSE streams.
Proposed fix
- Hoist the listener above the reload loop. Bind once at startup; on reload, reuse the existing listener unless the resolved port actually changed. Keeps the port stable and stops dropping connections on unrelated config edits.
- Give
server.port a fixed default instead of ephemeral, so a fresh install is predictable.
Note this interacts with an existing documented workflow. listenStrict's own error hint (main.go:658-659) currently says:
to run two itervox daemons in parallel, set server.port: 0 in WORKFLOW.md to let the OS pick a free port, or set a distinct explicit port per repo
A fixed default makes the multi-daemon case require an explicit per-repo server.port (or an explicit 0). That is a real ergonomic cost for anyone running itervox against several repositories, so the address-in-use error must stay loud and keep pointing at that knob.
Partially addressed already
The compounding half is fixed: internal/workflow/watcher.go now debounces, waiting for WORKFLOW.md to stop changing for 2s before firing onChange, so a burst of saves coalesces into one reload instead of one per save. Each further change restarts the quiet period.
That reduces the frequency but not the underlying behaviour — a single genuine config change still re-rolls the port. This issue tracks the remaining half.
Summary
With
server.portunset (the default), every WORKFLOW.md change makes the daemon bind a different port. Open dashboards break, bookmarks go stale, and the Vite dev proxy points at nothing. Editing several settings in sequence used to compound it: each save triggered its own reload.Observed on
v0.2.0(6d1c67c).Root cause
cmd/itervox/main.go:649-656:server.portis*int(internal/config/config.go:313) with no default, so when it is unsetportstays0andnet.Listenpicks an ephemeral port.That binding lives inside
run(), andmain.go:484re-invokesrun()on every reload:So a reload does not merely rebind the listener — it re-rolls the port. The listener, the HTTP server and the dashboard URL file are all torn down and recreated.
Two separate problems
1. The port is not stable across reloads. Ephemeral-by-default means "different port every reload", which is a surprising default for a long-running local daemon whose whole interface is a browser tab.
2. A reload rebinds even when nothing about the server changed. Editing
agent.max_turnshas no bearing on the listener, but the listener goes down and back up regardless, dropping in-flight requests and SSE streams.Proposed fix
server.porta fixed default instead of ephemeral, so a fresh install is predictable.Note this interacts with an existing documented workflow.
listenStrict's own error hint (main.go:658-659) currently says:A fixed default makes the multi-daemon case require an explicit per-repo
server.port(or an explicit0). That is a real ergonomic cost for anyone running itervox against several repositories, so the address-in-use error must stay loud and keep pointing at that knob.Partially addressed already
The compounding half is fixed:
internal/workflow/watcher.gonow debounces, waiting for WORKFLOW.md to stop changing for 2s before firingonChange, so a burst of saves coalesces into one reload instead of one per save. Each further change restarts the quiet period.That reduces the frequency but not the underlying behaviour — a single genuine config change still re-rolls the port. This issue tracks the remaining half.