fix: stop the daily KPI run hanging when no manual activities are given - #33
Merged
Merged
Conversation
promptUser only settled from readline's answer callback, which never fires when stdin is at EOF. Dispatching daily-kpi with the optional activities input left blank sets MANUAL_ACTIVITIES='', taking the interactive path on a runner that has no keyboard, so the job stalled and exited 0 without saving a KPI record or raising an alert. Closes #32 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CAM6ikRvg1YcErFvkFLMML
DamengRandom
marked this pull request as ready for review
August 2, 2026 10:30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #32
The bug
promptUser(src/tools/manual-kpi.tool.ts) only resolved fromrl.question's answer callback. On a stdin that is already at EOF, readline emitscloseand that callback never fires, so the promise never settles.Dispatching
daily-kpiwith its optional activities input left blank setsMANUAL_ACTIVITIES=''— falsy — socollectActivitiestakes the interactive readline path on a runner with no keyboard.src/agent/index.ts:132awaits it, so the job stops there: no KPI record, and no alert either, since a hang is not an exception. Node then drains the event loop and exits 0, so the workflow goes green having saved nothing.The change
Resolve on readline's
closeevent with an empty answer — which the existing loop already treats as "done". The answer is resolved beforerl.close(), so the first settle always wins and the interactive path is unchanged.Observed vs reasoned
Observed (run from the repo root with
npx tsx, stdin from/dev/null,MANUAL_ACTIVITIES=''):beforeExit, and the process exits with code 0 — full probe output in Daily KPI run hangs then exits 0 when the activities input is left blank — nothing is saved, no alert #32.RESOLVED: {"activities":[],...}.Reasoned, not observed: that a real GitHub Actions runner gives the step a
/dev/nullstdin. I reproduced that shape locally rather than on a runner. The rest of the pipeline consequence (thatrunDailyJobstherefore never saves a record and never alerts) follows fromsrc/agent/index.ts:132and thetry/catchat:41-47; I could not execute it here as it needs a database and an LLM key.Tests
src/tools/manual-kpi.test.tsruns the tool in a child process — readline writes its prompt to stdout, which corrupts the test runner's own reporting channel if run in-process. This mirrors the existingsrc/fatal-error.test.tspattern.Noted, out of scope
With a piped (non-TTY) stdin, only the first line is ever collected: each prompt builds a fresh readline interface, and closing the first one discards whatever it had already buffered from the pipe. That is pre-existing and unchanged by this PR — before it, the second prompt hung instead. Interactive TTY use and the
MANUAL_ACTIVITIESpath are unaffected.Checks
pnpm test(79 pass),pnpm tsc,pnpm format:checkall pass.Generated by Claude Code