From 5842b093cc502b46076423e4f8efdbbc568f86aa Mon Sep 17 00:00:00 2001 From: Igor Soarez Date: Thu, 5 Nov 2026 19:21:43 +0200 Subject: pi: a failed run says so: a 'failed' alert with the error, and the error in red until seen --- tests/e2e.sh | 30 ++++++++++++++++++++++++++++++ tests/fake-model.py | 11 ++++++++++- tests/pi-live.sh | 25 +++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/e2e.sh b/tests/e2e.sh index 52367d2..d9b4381 100755 --- a/tests/e2e.sh +++ b/tests/e2e.sh @@ -1207,6 +1207,36 @@ expect "one sound, the finished one's" "cat $SOUNDS" done expect "the menu lists it idle" "mb --print | grep -F 'can · c2' | cut -f1" "○ idle" t kill-session -t can +# --- 28. a failed run --------------------------------------------------------- +echo "28. failed runs" +error_report() { # pane error + python3 - "$HOOK" "$1" "$2" <<'PY' +import json, socket, sys +params = {"pane_id": sys.argv[2], "agent": "pi", "state": "idle", "error": sys.argv[3]} +s = socket.socket(socket.AF_UNIX) +s.connect(sys.argv[1]) +s.sendall((json.dumps({"method": "pane.report_agent", "params": params}) + "\n").encode()) +s.close() +PY +} +t new-session -d -s fl -n f1 -c "$DIR" "$DIR/bin/pi" +F1=$(t display -p -t fl:f1 '#{pane_id}') +report "$F1" working "" +expect "working" "opt $F1 @agent_state" working 50 +sleep 3.5; : >"$SOUNDS"; : >"$NOTES" +error_report "$F1" "401: invalid x-api-key" +expect "failed: done" "opt $F1 @agent_state" done 50 +expect "with why" "opt $F1 @dingger_error" "401: invalid x-api-key" +expect "a 'failed' notification, with why" "grep -c '^pi failed|fl:f1 (pi): 401: invalid x-api-key$' $NOTES" 1 60 +expect "the 'needs attention' sound" "cat $SOUNDS" request +expect "dingger status: failed, and why" "$DINGGER status | grep -F 'fl · f1' | grep -oE '^failed .* · 401: invalid x-api-key$' | wc -l | tr -d ' '" 1 +report "$F1" working "" +expect "a new run: the failure goes" "opt $F1 @dingger_error" "" 50 +report "$F1" idle "" +expect "finished: done" "opt $F1 @agent_state" done 50 +expect "with no failure" "opt $F1 @dingger_error" "" +t kill-session -t fl + # Every daemon this run started must stop with it. kill "$DAEMON_PID" "$DAEMON2_PID" 2>/dev/null expect "the test daemons stop" "ps -o pid= -p $DAEMON_PID,$DAEMON2_PID | wc -l | tr -d ' '" 0 diff --git a/tests/fake-model.py b/tests/fake-model.py index a02b98c..98a61db 100755 --- a/tests/fake-model.py +++ b/tests/fake-model.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 """A fake OpenAI-compatible model for tests/pi-live.sh: streams a short reply to /v1/chat/completions, after a delay taken from the last user message -("slow 3" waits 3 seconds). Prints its port, then serves until killed.""" +("slow 3" waits 3 seconds), or fails as a provider does ("fail 401": an +authentication error). Prints its port, then serves until killed.""" import json import re import sys @@ -34,6 +35,14 @@ class Handler(BaseHTTPRequestHandler): text = last_user_text(body) match = re.search(r"slow (\d+(?:\.\d+)?)", text) time.sleep(float(match.group(1)) if match else 0.2) + failure = re.search(r"fail (\d{3})", text) + if failure: + error = json.dumps({"type": "error", "error": {"type": "authentication_error", "message": "invalid x-api-key"}}) + self.send_response(int(failure.group(1))) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(error.encode()) + return self.send_response(200) self.send_header("Content-Type", "text/event-stream") self.send_header("Cache-Control", "no-cache") diff --git a/tests/pi-live.sh b/tests/pi-live.sh index 53115ec..04ef04a 100755 --- a/tests/pi-live.sh +++ b/tests/pi-live.sh @@ -277,6 +277,31 @@ same_cost() { python3 -c "import sys; a,b=sys.argv[1:]; print('same' if a and b expect "cost: compaction counted, as pi's footer counts" same_cost same 30 t kill-window -t main:ctx +# A failed run (the provider refuses the key): done, and why. +t new-window -d -t main: -n fail -c "$D/work3" +t send-keys -t main:fail "pi $NO_MCP" Enter +expect "failures: an agent of its own" "st fail" idle 150 +type_line fail "fail 401 now" +expect "a failed run: done" "st fail" done 80 +expect "with why, as one readable line" "t show -pv -t main:fail @dingger_error 2>/dev/null" "401: invalid x-api-key" 30 +expect "the menu says it failed" "\"$MENU\" --print | grep -F 'main · fail' | cut -f3 | grep -oE 'failed [^·]*· 401: invalid x-api-key'" "failed now · 401: invalid x-api-key" +t set -p -t main:fail @dingger_ack 1; "$DINGGER" poke +expect "seen: plain idle" "st fail" idle 50 +expect "and the failure is no longer shown" "t show -pv -t main:fail @dingger_error 2>/dev/null" "" 30 +type_line fail "fail 401 again" +expect "failed again" "t show -pv -t main:fail @dingger_error 2>/dev/null" "401: invalid x-api-key" 80 +# A run cancelled after a failure is not a failure. +type_line fail "slow 8 cancel me" +expect "a new run: working" "st fail" working 50 +expect "the failure goes with it" "t show -pv -t main:fail @dingger_error 2>/dev/null" "" 30 +t send-keys -t main:fail Escape +expect "cancelled: idle" "st fail" idle 50 +expect "with no failure" "t show -pv -t main:fail @dingger_error 2>/dev/null" "" +type_line fail "slow 1 fine" +expect "a run that works: done" "st fail" done 80 +expect "with no failure" "t show -pv -t main:fail @dingger_error 2>/dev/null" "" +t kill-window -t main:fail + echo "2. screen detection (pi without the extension)" t send-keys -t main:plain "PI_CODING_AGENT_DIR=$D/agent-plain pi $NO_MCP" Enter expect "identified as pi" "t show -pv -t main:plain @agent 2>/dev/null" pi 150 -- cgit v1.2.3