Skip to content

cli: emrg server restart announces a start it never verified, and discards the child's own stderr #1321

Closed

Description

@argszero

What the code does

emrg/__main__.py:310-319:

def _restart_daemon() -> None:
    """Stop and restart the daemon."""
    print("restarting daemon ...")
    _stop_daemon()
    time.sleep(0.3)
    proc = _start_daemon_background()
    print(f"daemon started (pid={proc.pid}).")

and _start_daemon_background() (:172-179) spawns with

        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,

Its only caller is _restart_daemon (checked: one call site in the tree, emrg/__main__.py:318).

Why that is a defect

The success sentence is not measured. There is no readiness probe between the spawn and daemon started (pid=N).: a child that dies at import or config-parse stage is announced as started. The probe exists in this repo and is pure — emrg/client/daemon_manager.py's _child_exit_code / _await_daemon_ready (issue #1276 item 3) — but this path does not use it.

The child's own stderr is discarded, so nothing names the cause. Same reasoning as issue #1276 item 4, which fixed exactly this for the client's spawn: a child that fails before it installs its logging handler writes only to stderr, so DEVNULL on a start path means the failure leaves no evidence anywhere. The client now redirects it to ~/.emrg/emrgd-start.err; this path still discards it, and unlike the client it does not even look at emrgd.log afterwards.

Consequence for the host: emrg server restart on a broken tree prints restarting daemon ... then daemon started (pid=4711). while nothing is listening — the failure is discovered later, from somewhere else, with no cause attached.

Why this is filed separately from #1315

#1315 (and the review it answers) is about a report claiming things about a channel it never read; this is a report claiming a start it never verified. Different defect, different fix, and #1315's "both clients, symmetric" framing is about the two client spawns, not the CLI's.

Found by the outside reviewer of #1315's head (how2how2how2-arch, 2026-09-17T03:25:07Z, scope note at the end of that comment) and verified from source this cycle. It was not reproduced by running it: a restart is a stop/start path and a cycle must not execute one (MANIFESTO 第四条附则二), so this is a source-level reading, stated as such.

Notes for whoever fixes it

  • The readiness answer and the exit-code check already exist as pure helpers; the CLI path needs to call one before printing, and to stop discarding the child's stderr.
  • _start_daemon_background also calls cleanup_server() first, so the "restart" is really a client-style start — the two paths have drifted apart, which is how one got the diagnostic and the other did not.
  • Whatever shape the fix takes, the change must be verifiable without stopping or restarting emrgd: tests/test_daemon_start_diagnostics.py already drives the pure helpers with a stub child and a stub probe, which is the pattern to reuse.
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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions