Finding
src/run.ts wraps event dispatch in one try/catch whose catch is the action's last line of defence: core.setFailed(error instanceof Error ? error.message : String(error)) (line 40). Every registered handler swallows its own rejections (runEventHandlers collects them, handleCronJobs and handleIssueComment .catch per job/command), so nothing in the bundle suite ever reaches that catch.
Evidence, main @ 3fc21f2:
- Unit:
npx vitest run --coverage → run.ts 100 % lines / 100 % branches; __tests__/run.test.ts:108-125 spy on core.setFailed for both the Error and the non-Error arm.
- E2E:
npm run test:coverage:e2e → run.ts 88.88 % lines, line 40: 0 hits. __tests__/bundle/bundle.test.ts:63 drives the sibling not yet supported arm (line 34) but nothing throws out of a handler.
The catch is reachable from dist/index.js. handleIssueComment tests the comment body against every configured command (commandForms(command).some(form => hasCommand(form, commentBody))) outside the per-command .catch(normalizeError), and hasCommand → findCommandArgs → commandLines → splitLines calls body.replace(...) unguarded. An issue_comment payload with no comment object, or with comment.body: null, therefore throws a TypeError straight up to run(). Reproduced against the fake GitHub server with prow-commands: /assign /lgtm:
exit 1 ::error::Cannot read properties of undefined (reading 'replace') (no API calls)
exit 1 ::error::Cannot read properties of null (reading 'replace') (no API calls)
That is the behaviour a consumer sees today for a malformed/truncated event payload: the run fails with an opaque property-access message rather than naming the missing comment body. The test pins the current contract (fails once, zero writes); whether the message should be friendlier is a product question outside this lane.
Of the 41 open hold-gated test(bundle) PRs, none drives a body-less comment: #343 covers the missing issue/pull number guards (body present), #324/#357 drive commandLines fences and alias canonicalization with a body. This cluster — src/run.ts:40 via handleIssueComment's unguarded body match — is disjoint.
Recommendation
Priority
- Impact: medium (covered by unit tests, not by e2e; the one arm that turns an unexpected throw into a red run)
- Effort: low
Filed by quality agent (hold-gated mode)
🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 3fc21f2
— hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88
Finding
src/run.tswraps event dispatch in onetry/catchwhosecatchis the action's last line of defence:core.setFailed(error instanceof Error ? error.message : String(error))(line 40). Every registered handler swallows its own rejections (runEventHandlerscollects them,handleCronJobsandhandleIssueComment.catchper job/command), so nothing in the bundle suite ever reaches that catch.Evidence,
main@ 3fc21f2:npx vitest run --coverage→run.ts100 % lines / 100 % branches;__tests__/run.test.ts:108-125spy oncore.setFailedfor both theErrorand the non-Errorarm.npm run test:coverage:e2e→run.ts88.88 % lines, line 40: 0 hits.__tests__/bundle/bundle.test.ts:63drives the siblingnot yet supportedarm (line 34) but nothing throws out of a handler.The catch is reachable from
dist/index.js.handleIssueCommenttests the comment body against every configured command (commandForms(command).some(form => hasCommand(form, commentBody))) outside the per-command.catch(normalizeError), andhasCommand → findCommandArgs → commandLines → splitLinescallsbody.replace(...)unguarded. Anissue_commentpayload with nocommentobject, or withcomment.body: null, therefore throws aTypeErrorstraight up torun(). Reproduced against the fake GitHub server withprow-commands: /assign /lgtm:That is the behaviour a consumer sees today for a malformed/truncated event payload: the run fails with an opaque property-access message rather than naming the missing comment body. The test pins the current contract (fails once, zero writes); whether the message should be friendlier is a product question outside this lane.
Of the 41 open hold-gated
test(bundle)PRs, none drives a body-less comment: #343 covers the missing issue/pull number guards (body present), #324/#357 drivecommandLinesfences and alias canonicalization with a body. This cluster —src/run.ts:40viahandleIssueComment's unguarded body match — is disjoint.Recommendation
__tests__/bundle/runTopLevelCatch.test.ts: rundist/index.jsonissue_commentwith (a) nocommentobject and (b)comment.body: null, assert exit 1, exactly one::error::line, and no API requests. Bringsrun.tse2e lines to 100 %; the remainingString(error)branch is unreachable from the bundle (every throw here is anError) and stays unit-only.Priority
Filed by quality agent (hold-gated mode)
🐝 Hive Agent:
quality| Instance:hosted-available-lke648397-260827-5q9t| SHA:3fc21f2— hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88