feat(errors): add shared user cancellation error - #1986
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## refactor #1986 +/- ##
============================================
- Coverage 97.06% 97.05% -0.01%
============================================
Files 374 374
Lines 22542 22514 -28
============================================
- Hits 21880 21852 -28
Misses 662 662 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Hweinstock
left a comment
There was a problem hiding this comment.
i like the approach! few comments, but I think some could be follow-ups I could help pick up.
| export class RuntimeInvokeResponseError extends AgentCoreCLIError { | ||
| readonly reported = true; | ||
|
|
||
| export class RuntimeInvokeResponseError extends SilentCLIError { |
There was a problem hiding this comment.
why are runtime invoke responses silent? I thought this was the error we get when the stream parsing fails.
There was a problem hiding this comment.
Yeah, this is the error we get when response streaming fails. By the time it reaches the root, writeStreamingResponse has already written the sanitized incomplete response summary to stderr. Making this error silent just prevents a second generic Error: response stream failed line. It still goes through structured logging and telemetry.
| if ((error as Error)?.name === "AbortError") return ExitCode.INTERRUPTED; | ||
| if (caught instanceof AgentCoreCLIError) return caught.exitCode; | ||
| return ExitCode.FAILURE; | ||
| const error = AgentCoreCLIError.fromError(caught); |
There was a problem hiding this comment.
nice, really like how simple this is now!
| error_name: error.name, | ||
| error_source: error.source, | ||
| }); | ||
| if (error.exitCode !== 0) { |
There was a problem hiding this comment.
i wonder if it makes sense to expand the exit_reason attribute to accept a cancelled value. That way we still get telemetry for these cancellations.
could be a follow-up since we'll need to adjust the backend schema to accommodate.
There was a problem hiding this comment.
Yeah, I think cancelled would be clearer. Right now cancellation still emits telemetry as failure with error_source: user. I kept the new exit reason out of this PR since it also requires a backend schema change, so I think that should be a follow-up.
There was a problem hiding this comment.
do we need the same controller.signal.throwIfAborted(); check here?
Also wondering if it makes sense to build an abstraction for this since it seems there's already a few consumers?
Something like:
async function withCancellation<T>(fn: (signal: AbortSignal) => Promise<T>): Promise<T> {
const controller = new AbortController();
const interrupt = () => controller.abort(new UserCancellationError());
process.once("SIGINT", interrupt);
try {
return await fn(controller.signal);
} catch (error) {
controller.signal.throwIfAborted();
throw error;
} finally {
controller.abort();
process.off("SIGINT", interrupt);
}
}
There was a problem hiding this comment.
Agreed. After rebasing, Gateway invoke and dataset update introduced two more consumers, so the abstraction makes sense now. I added withUserCancellation under src/runnable and moved Runtime invoke, Gateway invoke, dataset get, and dataset update onto it. It owns the SIGINT listener, cleanup, and throwIfAborted() normalization, while TUI-local cancellation stays separate. I'll do the same with dev when its merged.
5e13303 to
128e4f1
Compare
5c7c41f to
2e3832f
Compare
Problem
Commander exits and user cancellations bypass the shared CLI error model. Runtime and Gateway invoke define resource-specific interruption errors, while Project dev defines a separate command interruption type for the same user cancellation outcome.
Solution
CommanderErrorinAgentCoreCLIError.fromError, preserving help as exit0and mapping parse failures to usage exit2SilentCLIErrorcategory so only intentionally silent errors skip generic root stderr outputUserCancellationErrorwith exit code130withUserCancellationfor Runtime invoke, Gateway invoke, dataset get, and dataset updateAbortSignal.reason, including Project devShutting down…message, repeated-signal behavior, and listener cleanupAbortErrorhandling unchangedVerification
146 pass, 0 fail)1501 pass, 0 fail)bun run typecheckbun run lint:checkbun run buildgit diff --check1501 pass, 0 fail1501 pass, 0 fail1501 pass, 0 fail0, help on stdout, no stderr or error log2, one Commander error line, classified as a user error