Skip to content

daemon start reports success when the daemon cannot bind its HTTP port #981

Closed

Description

@V3RON

simlock daemon start prints Daemon running and the console URL, and exits 0, when the daemon cannot bind its HTTP port. The daemon logs HTTP frontend failed to start with EADDRINUSE, then stops itself once its startup read finishes.

Seen with the port held by another process (console lane, e2e/console/daemon-start-failure.spec.ts on bug/933-repro):

stdout: Daemon running
        Console: http://127.0.0.1:52605/
exit:   0
daemon.log: "HTTP frontend failed to start" … EADDRINUSE
  • src/cli/environment.ts:115: the CLI's start ends once status.get answers, and it answers while the daemon is still starting.
  • src/daemon/main.ts:777 and :840: the bind failure is logged at once, but the daemon stops only after daemon.start() (convergence) ends. A slow startup read leaves a long window.

The user is told the daemon and console are up when both are about to go away. Found while triaging #933.

Written by an agent.

Activity

  1. added
    bug:newReported. An agent may triage it (rule 8), and nothing more.
    and removed
    bug:newReported. An agent may triage it (rule 8), and nothing more.
    on Oct 24, 2026
  2. self-assigned this
    on Oct 25, 2026
  3. V3RON commented on Oct 25, 2026

    @V3RON
    ContributorAuthor

    Reproduction

    Confirmed; a defect (cli.mdx: a daemon that exits while starting fails the command with DAEMON_STARTUP_FAILED). bug/981-repro, e2e/start-refusal.test.ts, "simlock daemon start prints DAEMON_STARTUP_FAILED naming EADDRINUSE and the port, exit 1, and never Daemon running". Port held, listManaged slowed to 5 s, so no race.

    pnpm test:e2e e2e/start-refusal.test.ts
    AssertionError: expected 'Daemon running\nConsole: http://127.0…' not to contain 'Daemon running'
     ❯ e2e/start-refusal.test.ts:56:33
    

    By hand: the CLI exits 0 in 0.36 s; the daemon logs Daemon failed to start: listen EADDRINUSE 3 s later.

    Root cause

    src/cli/commands/daemon.ts:94-102 returns on the first status.get answer, and the startup probe accepts starting (src/cli/environment.ts:116). Health turns running at src/daemon/server.ts:598 whether or not HTTP listens, and main.ts:828-840 stops only after convergence. So a CLI that waits for running can still print success when convergence beats the bind failure (#933's one start in seven).

    Simplest fix

    1. main.ts: the converge hook (:699 for the worker, :1078 for the gateway) also awaits gatewayStarted. A failed bind then fails the start: health failed, no daemon.started, stop reason stays http-start-failed.
    2. daemon start: after a launch, poll status.get until health is no longer starting. An exit or a lost connection goes through connectAfterExit, which already reports the daemon's own error.

    Alternatives rejected

    • CLI wait only: passes this test but keeps the race when convergence finishes first.
    • Stop as soon as the bind fails: brings back the B6 event-order bug, and the probe can still answer first.

    Risk

    • daemon start now waits for convergence. status-while-starting.test.ts:17 already says it does. The comments at e2e/helpers/env.ts:296 and http-daemon.ts:10 become wrong.
    • docs/internal/flows/startup.md:86 and the src/daemon/AGENTS.md bind-failure line need updating.
    • Through a service there is no child: check a failed start shows as a lost connection, not a 20 s wait.

    Side findings

    Written by an agent.

  4. removed their assignment
    on Oct 25, 2026
  5. V3RON commented on Oct 25, 2026

    @V3RON
    ContributorAuthor

    Decision

    Maintainer accepted the triage report's proposed fix, both halves. The daemon's startup step waits for the HTTP listener (worker and gateway), so a failed bind fails the start with stop reason http-start-failed. After launch, daemon start waits until health leaves starting, and if the daemon exits or the connection drops it reports the daemon's own error through the existing exit path.

    Written by an agent.

  6. added
    bug:readyAn agent may fix it.
    and removed on Oct 25, 2026
  7. self-assigned this
    on Oct 25, 2026
  8. V3RON commented on Oct 25, 2026

    @V3RON
    ContributorAuthor

    Decision

    Tests (relayed from the maintainer at delivery start): e2e/start-refusal.test.ts passes with the CLI half alone, so the fix adds a deterministic test that pins the daemon half: when the HTTP listener fails to bind, startup does not report running and the start fails with stop reason http-start-failed, with no timing race (for example a unit test with the bind failing before or after convergence).

    Out of scope: #985 (warm pool logs "database is not open" on an early stop) is triaged separately.

    Written by an agent.

  9. V3RON commented on Oct 25, 2026

    @V3RON
    ContributorAuthor

    Decision

    daemon start's wait for health to leave starting is bounded, above the daemon's 60 s startup-read cap. On expiry it fails with DAEMON_STARTUP_FAILED saying the daemon is still starting, and leaves the daemon running (decided in delivery, pending the maintainer).

    Tests: a CLI unit test with a fake clock. A daemon that stays starting past the bound fails the start with DAEMON_STARTUP_FAILED.

    Details: PR #990, "Decision taken".

    Written by an agent.

  10. V3RON commented on Oct 25, 2026

    @V3RON
    ContributorAuthor

    Delivery run

    Merged: #990 (#981)
    Parked: none
    Waiting: none
    Decided: #981 — how long does daemon start wait for starting to end? → a bounded 90 s, then DAEMON_STARTUP_FAILED with the daemon left running. #981 — what does the user see when a daemon is already stopping during start? → DAEMON_STARTUP_FAILED with the daemon's own error, replacing #710's DAEMON_STOPPING pass-through. Both are pending the maintainer.

    Written by an agent.

  11. removed their assignment
    on Oct 25, 2026
  12. added
    bug:newReported. An agent may triage it (rule 8), and nothing more.
    on Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug:newReported. An agent may triage it (rule 8), and nothing more.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions