fix(e2e): prevent wizard FIFO startup races (#158605)

* fix(e2e): prevent wizard FIFO startup races

Anchor the FIFO before reader startup, close the inherited anchor in the child, and restore a write-only parent descriptor. This prevents a fast reader exit from stranding open while preserving EOF and EPIPE behavior.

* test(e2e): bound wizard EOF regression process
This commit is contained in:
Peter Steinberger 2026-09-25 20:21:36 -07:00 • committed by GitHub
parent 5b3cb698b6
commit 50df80460a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 46 additions and 2 deletions

View file

@ -164,9 +164,15 @@ run_wizard_cmd() {
local log_path="$OPENCLAW_E2E_LOG_DIR/${case_name}.log"
WIZARD_LOG_PATH="$log_path"
export WIZARD_LOG_PATH
# Run under script to keep an interactive TTY for clack prompts.
openclaw_e2e_run_script_with_pty "$command" "$log_path" <"$input_fifo" >/dev/null 2>&1 &
# Anchor the FIFO before forking so a fast-exiting reader cannot strand open().
if ! exec 3<>"$input_fifo"; then
cleanup_wizard_case
return 1
fi
# Open stdin before dropping the inherited anchor; only the driver keeps a writer.
openclaw_e2e_run_script_with_pty "$command" "$log_path" <"$input_fifo" 3>&- >/dev/null 2>&1 &
wizard_pid=$!
# Restore write-only semantics so an exited wizard still produces EPIPE.
if ! exec 3>"$input_fifo"; then
cleanup_wizard_case
return 1

View file

@ -336,6 +336,44 @@ run_wizard_cmd failing-wizard fake-state "node fake-wizard" send_noop false
}
});
it("delivers wizard input and EOF without retaining an inherited writer", async () => {
const tempRoot = tempDirs.make("openclaw-onboard-fifo-eof-");
const fixturePath = path.join(tempRoot, "wizard-eof.sh");
await writeFile(
fixturePath,
`#!/usr/bin/env bash
set -euo pipefail
export OPENCLAW_ONBOARD_SCENARIO_SOURCE_ONLY=1
export OPENCLAW_ONBOARD_E2E_TMPDIR=${JSON.stringify(tempRoot)}
OPENCLAW_ENTRY=node
openclaw_test_state_create() { :; }
source scripts/e2e/lib/onboard/scenario.sh
openclaw_e2e_run_script_with_pty() { cat >"$2"; }
send_and_close() {
printf 'wizard input\\n' >&3
exec 3>&-
}
run_wizard_cmd eof-wizard fake-state cat send_and_close false
printf 'recorded input:'
cat "$WIZARD_LOG_PATH"
test -z "$(find "$ONBOARD_TMP_DIR" -name '*.fifo.*')"
cleanup_onboard_artifacts
test ! -e "$ONBOARD_TMP_DIR"
`,
);
const result = spawnSync("bash", [fixturePath], {
cwd: process.cwd(),
encoding: "utf8",
timeout: 10_000,
});
expect(result.error).toBeUndefined();
expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(0);
expect(result.stdout).toContain("recorded input:wizard input\n");
});
it("does not wait for a skills prompt after the ready state renders", async () => {
const tempRoot = tempDirs.make("openclaw-onboard-skills-ready-");
const fixturePath = path.join(tempRoot, "skills-ready.sh");