Skip to content

muse-proxy: signal-killed runs report 'muse exec exited with code null', and #673's empty-stderr terminal-state fallback is untested #674

Closed

Description

@dezren39

Finding

PR #673 (fixing #672) reworked bin/muse-proxy.mjs's failure handling so failure is judged by the authoritative terminal state, and — when stderr is empty — the error detail names that terminal state instead of the misleading muse exec exited with code 0. The fallback expression it left behind (current main, bin/muse-proxy.mjs:404-409) has two remaining gaps in the same spot:

const failed = terminalState ? terminalState !== 'completed' : code !== 0;
if (failed) {
  const stderrLine = stderrTail.trim().split('\n').pop();
  const fallback = terminalState && terminalState !== 'completed'
    ? `muse run terminated with state '${terminalState}'`
    : `muse exec exited with code ${code}`;

1. Signal-killed runs report muse exec exited with code null

The close handler is child.on('close', (code, signal) => …) (line 367), but signal is never used. When the child dies by signal (OOM SIGKILL, SIGTERM, …), Node reports code === null, the run correctly counts as failed (code !== 0), but with empty stderr the detail becomes the literal string muse exec exited with code null — the same class of misleading detail #673 set out to eliminate for the terminal-state case.

Reproduced on current main (4a91edc3, merge of #673) with a stub muse that kills itself and prints nothing:

#!/bin/sh
kill -9 $$
  • Non-stream: 500 {"error":{"message":"Muse execution failed: muse exec exited with code null"}}
  • Stream: SSE error event with the same code null message, then data: [DONE]

2. The empty-stderr terminal-state fallback added by #673 has no test coverage

Both failure stubs added by #671/#673 emit a stderr line (stub muse: run failed midway, stub muse: crashed midway), and the #672 test only asserts /Muse execution failed: stub muse:/ — so the stderrLine is always what satisfies the assertion and the new fallback string is never exercised.

Verified by ablation in a scratch copy: reverting only the fallback-detail lines to the pre-#673 form (stderrTail… || \muse exec exited with code ${code}`), while keeping #673's failure predicate, still passes the full suite 4/4 (node --test bin/muse-proxy.test.mjs). The behaviour itself works today — a stub emitting run.terminal.failedwith **no** stderr returnsMuse execution failed: muse run terminated with state 'failed'in both modes — but nothing pins it, so a future refactor can silently regress it back toexited with code 0`.

Evidence

Why it matters

This proxy is the OpenAI-compatible surface other tools drive muse exec through; when a run dies, the error detail is often the only diagnostic a client (or a human reading its logs) gets. exited with code null sends debugging in the wrong direction (a signal kill — e.g. OOM — looks like a corrupt message), and the one fallback #673 deliberately improved can regress unnoticed because no test reaches it.

Acceptance criteria

  • A run terminated by a signal with empty stderr and no terminal event produces an error detail that names the signal (e.g. muse exec terminated by signal SIGKILL) instead of exited with code null, in both stream (SSE error frame + [DONE]) and non-stream (500) modes.
  • A stub mode that emits a non-completed terminal event with empty stderr is added to bin/muse-proxy.test.mjs, asserting the detail names the terminal state (e.g. terminated with state 'failed') in both modes.
  • A stub mode whose child dies by signal (e.g. kill -9 $$) with empty stderr is added, asserting the signal-naming detail in both modes.
  • node --test bin/muse-proxy.test.mjs passes, and ablating either fallback branch makes exactly the new assertions fail.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions