feat(errors): add shared user cancellation error - #1986

Merged
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error
Aug 21, 2026
Merged

feat(errors): add shared user cancellation error#1986
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error

Conversation

@aidandaly24

@aidandaly24aidandaly24 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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

  • classify CommanderError in AgentCoreCLIError.fromError, preserving help as exit 0 and mapping parse failures to usage exit 2
  • add an opt-in SilentCLIError category so only intentionally silent errors skip generic root stderr output
  • add a silent, user-sourced UserCancellationError with exit code 130
  • centralize process SIGINT listener lifecycle in withUserCancellation for Runtime invoke, Gateway invoke, dataset get, and dataset update
  • use the shared cancellation error as each operation's AbortSignal.reason, including Project dev
  • preserve Project dev's SIGINT/SIGTERM handling, single Shutting down… message, repeated-signal behavior, and listener cleanup
  • preserve Runtime and Gateway partial-response interruption summaries while propagating the original typed cancellation reason
  • leave TUI-local cancellation and low-level platform AbortError handling unchanged

Verification

  • focused cancellation suites across errors, root handling, Runtime, Gateway, Project dev, and datasets (146 pass, 0 fail)
  • full local source suite (1544 pass, 0 fail)
  • bun run typecheck
  • bun run lint:check
  • Prettier check for every changed file
  • bun run build
  • git diff --check
  • GitHub full unit suites:
    • Linux: 1544 pass, 0 fail
    • Windows: 1544 pass, 0 fail
    • macOS: 1544 pass, 0 fail
  • Linux, Windows, and macOS builds
  • AgentCore E2E CodeBuild
  • bundled CLI smoke checks:
    • nested help: exit 0, help on stdout, no stderr or error log
    • nested unknown option: exit 2, one Commander error line, classified as a user error

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 12, 2026
@codecov-commenter

codecov-commenter commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.13%. Comparing base (33e2d2f) to head (f17de43).

Additional details and impacted files
@@ Coverage Diff @@## refactor #1986 +/- ##
============================================
- Coverage 97.14% 97.13% -0.01% 
============================================
Files 382 382 Lines 22884 22856 -28 ============================================
- Hits 22231 22202 -29 - Misses 653 654 +1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aidandaly24
aidandaly24 marked this pull request as ready for review August 12, 2026 21:19
@aidandaly24
aidandaly24 marked this pull request as draft August 13, 2026 00:17
@aidandaly24
aidandaly24 marked this pull request as ready for review August 13, 2026 00:22

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the approach! few comments, but I think some could be follow-ups I could help pick up.

Comment threadsrc/errors/errors.tsx
export class RuntimeInvokeResponseError extends AgentCoreCLIError {
readonly reported = true;

export class RuntimeInvokeResponseError extends SilentCLIError {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are runtime invoke responses silent? I thought this was the error we get when the stream parsing fails.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, really like how simple this is now!

Comment threadsrc/index.ts
error_name: error.name,
error_source: error.source,
});
if (error.exitCode !== 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. Once we start getting data we can decide if separating into a separate reason makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@aidandaly24
aidandaly24force-pushed the feat/user-cancellation-error branch 2 times, most recently from 5c7c41f to 2e3832fCompareAugust 17, 2026 20:54
Hweinstock
Hweinstock previously approved these changes Aug 18, 2026

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

jariy17 pushed a commit that referenced this pull request Aug 20, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
@aidandaly24
aidandaly24 merged commit 71b0f0a into aws:refactorAug 21, 2026
8 of 11 checks passed
jariy17 pushed a commit that referenced this pull request Aug 24, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
jariy17 added a commit that referenced this pull request Aug 24, 2026
…#2032)
* feat(eval): batch-evaluation simulate — invokeDataset + self-running example classes
* refactor(eval): address Harrison's review feedback
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
* fix(eval): validate assertions/expected_trajectory + require {input} in payload-template
Bug bash (real exploratory-account run) surfaced two defects in the shared invokeDataset path:
1. PredefinedExample blind-cast assertions/expected_trajectory as string[] with no load-time
validation (unlike turns). A non-array value passed load, burned a live paid invoke, then
threw a raw '.map is not a function' mislabeled 'failed to invoke'. Now validated in the
constructor -> clean InputValidationError before any invoke.
2. A --payload-template with no {input} placeholder was silently accepted, wasting a full
~3-min replay on a constant payload. Now rejected up front in invokeDataset.
---------
Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aidandaly24@codecov-commenter@Hweinstock@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(errors): add shared user cancellation error - #1986

Merged
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error
Aug 21, 2026
Merged

feat(errors): add shared user cancellation error#1986
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error

Conversation

@aidandaly24

@aidandaly24aidandaly24 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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

  • classify CommanderError in AgentCoreCLIError.fromError, preserving help as exit 0 and mapping parse failures to usage exit 2
  • add an opt-in SilentCLIError category so only intentionally silent errors skip generic root stderr output
  • add a silent, user-sourced UserCancellationError with exit code 130
  • centralize process SIGINT listener lifecycle in withUserCancellation for Runtime invoke, Gateway invoke, dataset get, and dataset update
  • use the shared cancellation error as each operation's AbortSignal.reason, including Project dev
  • preserve Project dev's SIGINT/SIGTERM handling, single Shutting down… message, repeated-signal behavior, and listener cleanup
  • preserve Runtime and Gateway partial-response interruption summaries while propagating the original typed cancellation reason
  • leave TUI-local cancellation and low-level platform AbortError handling unchanged

Verification

  • focused cancellation suites across errors, root handling, Runtime, Gateway, Project dev, and datasets (146 pass, 0 fail)
  • full local source suite (1544 pass, 0 fail)
  • bun run typecheck
  • bun run lint:check
  • Prettier check for every changed file
  • bun run build
  • git diff --check
  • GitHub full unit suites:
    • Linux: 1544 pass, 0 fail
    • Windows: 1544 pass, 0 fail
    • macOS: 1544 pass, 0 fail
  • Linux, Windows, and macOS builds
  • AgentCore E2E CodeBuild
  • bundled CLI smoke checks:
    • nested help: exit 0, help on stdout, no stderr or error log
    • nested unknown option: exit 2, one Commander error line, classified as a user error

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 12, 2026
@codecov-commenter

codecov-commenter commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.13%. Comparing base (33e2d2f) to head (f17de43).

Additional details and impacted files
@@ Coverage Diff @@## refactor #1986 +/- ##
============================================
- Coverage 97.14% 97.13% -0.01% 
============================================
Files 382 382 Lines 22884 22856 -28 ============================================
- Hits 22231 22202 -29 - Misses 653 654 +1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aidandaly24
aidandaly24 marked this pull request as ready for review August 12, 2026 21:19
@aidandaly24
aidandaly24 marked this pull request as draft August 13, 2026 00:17
@aidandaly24
aidandaly24 marked this pull request as ready for review August 13, 2026 00:22

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the approach! few comments, but I think some could be follow-ups I could help pick up.

Comment threadsrc/errors/errors.tsx
export class RuntimeInvokeResponseError extends AgentCoreCLIError {
readonly reported = true;

export class RuntimeInvokeResponseError extends SilentCLIError {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are runtime invoke responses silent? I thought this was the error we get when the stream parsing fails.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, really like how simple this is now!

Comment threadsrc/index.ts
error_name: error.name,
error_source: error.source,
});
if (error.exitCode !== 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. Once we start getting data we can decide if separating into a separate reason makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@aidandaly24
aidandaly24force-pushed the feat/user-cancellation-error branch 2 times, most recently from 5c7c41f to 2e3832fCompareAugust 17, 2026 20:54
Hweinstock
Hweinstock previously approved these changes Aug 18, 2026

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

jariy17 pushed a commit that referenced this pull request Aug 20, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
@aidandaly24
aidandaly24 merged commit 71b0f0a into aws:refactorAug 21, 2026
8 of 11 checks passed
jariy17 pushed a commit that referenced this pull request Aug 24, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
jariy17 added a commit that referenced this pull request Aug 24, 2026
…#2032)
* feat(eval): batch-evaluation simulate — invokeDataset + self-running example classes
* refactor(eval): address Harrison's review feedback
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
* fix(eval): validate assertions/expected_trajectory + require {input} in payload-template
Bug bash (real exploratory-account run) surfaced two defects in the shared invokeDataset path:
1. PredefinedExample blind-cast assertions/expected_trajectory as string[] with no load-time
validation (unlike turns). A non-array value passed load, burned a live paid invoke, then
threw a raw '.map is not a function' mislabeled 'failed to invoke'. Now validated in the
constructor -> clean InputValidationError before any invoke.
2. A --payload-template with no {input} placeholder was silently accepted, wasting a full
~3-min replay on a constant payload. Now rejected up front in invokeDataset.
---------
Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aidandaly24@codecov-commenter@Hweinstock@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(errors): add shared user cancellation error - #1986

Merged
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error
Aug 21, 2026
Merged

feat(errors): add shared user cancellation error#1986
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error

Conversation

@aidandaly24

@aidandaly24aidandaly24 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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

  • classify CommanderError in AgentCoreCLIError.fromError, preserving help as exit 0 and mapping parse failures to usage exit 2
  • add an opt-in SilentCLIError category so only intentionally silent errors skip generic root stderr output
  • add a silent, user-sourced UserCancellationError with exit code 130
  • centralize process SIGINT listener lifecycle in withUserCancellation for Runtime invoke, Gateway invoke, dataset get, and dataset update
  • use the shared cancellation error as each operation's AbortSignal.reason, including Project dev
  • preserve Project dev's SIGINT/SIGTERM handling, single Shutting down… message, repeated-signal behavior, and listener cleanup
  • preserve Runtime and Gateway partial-response interruption summaries while propagating the original typed cancellation reason
  • leave TUI-local cancellation and low-level platform AbortError handling unchanged

Verification

  • focused cancellation suites across errors, root handling, Runtime, Gateway, Project dev, and datasets (146 pass, 0 fail)
  • full local source suite (1544 pass, 0 fail)
  • bun run typecheck
  • bun run lint:check
  • Prettier check for every changed file
  • bun run build
  • git diff --check
  • GitHub full unit suites:
    • Linux: 1544 pass, 0 fail
    • Windows: 1544 pass, 0 fail
    • macOS: 1544 pass, 0 fail
  • Linux, Windows, and macOS builds
  • AgentCore E2E CodeBuild
  • bundled CLI smoke checks:
    • nested help: exit 0, help on stdout, no stderr or error log
    • nested unknown option: exit 2, one Commander error line, classified as a user error

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 12, 2026
@codecov-commenter

codecov-commenter commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.13%. Comparing base (33e2d2f) to head (f17de43).

Additional details and impacted files
@@ Coverage Diff @@## refactor #1986 +/- ##
============================================
- Coverage 97.14% 97.13% -0.01% 
============================================
Files 382 382 Lines 22884 22856 -28 ============================================
- Hits 22231 22202 -29 - Misses 653 654 +1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aidandaly24
aidandaly24 marked this pull request as ready for review August 12, 2026 21:19
@aidandaly24
aidandaly24 marked this pull request as draft August 13, 2026 00:17
@aidandaly24
aidandaly24 marked this pull request as ready for review August 13, 2026 00:22

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the approach! few comments, but I think some could be follow-ups I could help pick up.

Comment threadsrc/errors/errors.tsx
export class RuntimeInvokeResponseError extends AgentCoreCLIError {
readonly reported = true;

export class RuntimeInvokeResponseError extends SilentCLIError {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are runtime invoke responses silent? I thought this was the error we get when the stream parsing fails.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, really like how simple this is now!

Comment threadsrc/index.ts
error_name: error.name,
error_source: error.source,
});
if (error.exitCode !== 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. Once we start getting data we can decide if separating into a separate reason makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@aidandaly24
aidandaly24force-pushed the feat/user-cancellation-error branch 2 times, most recently from 5c7c41f to 2e3832fCompareAugust 17, 2026 20:54
Hweinstock
Hweinstock previously approved these changes Aug 18, 2026

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

jariy17 pushed a commit that referenced this pull request Aug 20, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
@aidandaly24
aidandaly24 merged commit 71b0f0a into aws:refactorAug 21, 2026
8 of 11 checks passed
jariy17 pushed a commit that referenced this pull request Aug 24, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
jariy17 added a commit that referenced this pull request Aug 24, 2026
…#2032)
* feat(eval): batch-evaluation simulate — invokeDataset + self-running example classes
* refactor(eval): address Harrison's review feedback
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
* fix(eval): validate assertions/expected_trajectory + require {input} in payload-template
Bug bash (real exploratory-account run) surfaced two defects in the shared invokeDataset path:
1. PredefinedExample blind-cast assertions/expected_trajectory as string[] with no load-time
validation (unlike turns). A non-array value passed load, burned a live paid invoke, then
threw a raw '.map is not a function' mislabeled 'failed to invoke'. Now validated in the
constructor -> clean InputValidationError before any invoke.
2. A --payload-template with no {input} placeholder was silently accepted, wasting a full
~3-min replay on a constant payload. Now rejected up front in invokeDataset.
---------
Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aidandaly24@codecov-commenter@Hweinstock@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(errors): add shared user cancellation error - #1986

Merged
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error
Aug 21, 2026
Merged

feat(errors): add shared user cancellation error#1986
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error

Conversation

@aidandaly24

@aidandaly24aidandaly24 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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

  • classify CommanderError in AgentCoreCLIError.fromError, preserving help as exit 0 and mapping parse failures to usage exit 2
  • add an opt-in SilentCLIError category so only intentionally silent errors skip generic root stderr output
  • add a silent, user-sourced UserCancellationError with exit code 130
  • centralize process SIGINT listener lifecycle in withUserCancellation for Runtime invoke, Gateway invoke, dataset get, and dataset update
  • use the shared cancellation error as each operation's AbortSignal.reason, including Project dev
  • preserve Project dev's SIGINT/SIGTERM handling, single Shutting down… message, repeated-signal behavior, and listener cleanup
  • preserve Runtime and Gateway partial-response interruption summaries while propagating the original typed cancellation reason
  • leave TUI-local cancellation and low-level platform AbortError handling unchanged

Verification

  • focused cancellation suites across errors, root handling, Runtime, Gateway, Project dev, and datasets (146 pass, 0 fail)
  • full local source suite (1544 pass, 0 fail)
  • bun run typecheck
  • bun run lint:check
  • Prettier check for every changed file
  • bun run build
  • git diff --check
  • GitHub full unit suites:
    • Linux: 1544 pass, 0 fail
    • Windows: 1544 pass, 0 fail
    • macOS: 1544 pass, 0 fail
  • Linux, Windows, and macOS builds
  • AgentCore E2E CodeBuild
  • bundled CLI smoke checks:
    • nested help: exit 0, help on stdout, no stderr or error log
    • nested unknown option: exit 2, one Commander error line, classified as a user error

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 12, 2026
@codecov-commenter

codecov-commenter commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.13%. Comparing base (33e2d2f) to head (f17de43).

Additional details and impacted files
@@ Coverage Diff @@## refactor #1986 +/- ##
============================================
- Coverage 97.14% 97.13% -0.01% 
============================================
Files 382 382 Lines 22884 22856 -28 ============================================
- Hits 22231 22202 -29 - Misses 653 654 +1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aidandaly24
aidandaly24 marked this pull request as ready for review August 12, 2026 21:19
@aidandaly24
aidandaly24 marked this pull request as draft August 13, 2026 00:17
@aidandaly24
aidandaly24 marked this pull request as ready for review August 13, 2026 00:22

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the approach! few comments, but I think some could be follow-ups I could help pick up.

Comment threadsrc/errors/errors.tsx
export class RuntimeInvokeResponseError extends AgentCoreCLIError {
readonly reported = true;

export class RuntimeInvokeResponseError extends SilentCLIError {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are runtime invoke responses silent? I thought this was the error we get when the stream parsing fails.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, really like how simple this is now!

Comment threadsrc/index.ts
error_name: error.name,
error_source: error.source,
});
if (error.exitCode !== 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. Once we start getting data we can decide if separating into a separate reason makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@aidandaly24
aidandaly24force-pushed the feat/user-cancellation-error branch 2 times, most recently from 5c7c41f to 2e3832fCompareAugust 17, 2026 20:54
Hweinstock
Hweinstock previously approved these changes Aug 18, 2026

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

jariy17 pushed a commit that referenced this pull request Aug 20, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
@aidandaly24
aidandaly24 merged commit 71b0f0a into aws:refactorAug 21, 2026
8 of 11 checks passed
jariy17 pushed a commit that referenced this pull request Aug 24, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
jariy17 added a commit that referenced this pull request Aug 24, 2026
…#2032)
* feat(eval): batch-evaluation simulate — invokeDataset + self-running example classes
* refactor(eval): address Harrison's review feedback
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
* fix(eval): validate assertions/expected_trajectory + require {input} in payload-template
Bug bash (real exploratory-account run) surfaced two defects in the shared invokeDataset path:
1. PredefinedExample blind-cast assertions/expected_trajectory as string[] with no load-time
validation (unlike turns). A non-array value passed load, burned a live paid invoke, then
threw a raw '.map is not a function' mislabeled 'failed to invoke'. Now validated in the
constructor -> clean InputValidationError before any invoke.
2. A --payload-template with no {input} placeholder was silently accepted, wasting a full
~3-min replay on a constant payload. Now rejected up front in invokeDataset.
---------
Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aidandaly24@codecov-commenter@Hweinstock@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(errors): add shared user cancellation error - #1986

Merged
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error
Aug 21, 2026
Merged

feat(errors): add shared user cancellation error#1986
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error

Conversation

@aidandaly24

@aidandaly24aidandaly24 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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

  • classify CommanderError in AgentCoreCLIError.fromError, preserving help as exit 0 and mapping parse failures to usage exit 2
  • add an opt-in SilentCLIError category so only intentionally silent errors skip generic root stderr output
  • add a silent, user-sourced UserCancellationError with exit code 130
  • centralize process SIGINT listener lifecycle in withUserCancellation for Runtime invoke, Gateway invoke, dataset get, and dataset update
  • use the shared cancellation error as each operation's AbortSignal.reason, including Project dev
  • preserve Project dev's SIGINT/SIGTERM handling, single Shutting down… message, repeated-signal behavior, and listener cleanup
  • preserve Runtime and Gateway partial-response interruption summaries while propagating the original typed cancellation reason
  • leave TUI-local cancellation and low-level platform AbortError handling unchanged

Verification

  • focused cancellation suites across errors, root handling, Runtime, Gateway, Project dev, and datasets (146 pass, 0 fail)
  • full local source suite (1544 pass, 0 fail)
  • bun run typecheck
  • bun run lint:check
  • Prettier check for every changed file
  • bun run build
  • git diff --check
  • GitHub full unit suites:
    • Linux: 1544 pass, 0 fail
    • Windows: 1544 pass, 0 fail
    • macOS: 1544 pass, 0 fail
  • Linux, Windows, and macOS builds
  • AgentCore E2E CodeBuild
  • bundled CLI smoke checks:
    • nested help: exit 0, help on stdout, no stderr or error log
    • nested unknown option: exit 2, one Commander error line, classified as a user error

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 12, 2026
@codecov-commenter

codecov-commenter commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.13%. Comparing base (33e2d2f) to head (f17de43).

Additional details and impacted files
@@ Coverage Diff @@## refactor #1986 +/- ##
============================================
- Coverage 97.14% 97.13% -0.01% 
============================================
Files 382 382 Lines 22884 22856 -28 ============================================
- Hits 22231 22202 -29 - Misses 653 654 +1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aidandaly24
aidandaly24 marked this pull request as ready for review August 12, 2026 21:19
@aidandaly24
aidandaly24 marked this pull request as draft August 13, 2026 00:17
@aidandaly24
aidandaly24 marked this pull request as ready for review August 13, 2026 00:22

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the approach! few comments, but I think some could be follow-ups I could help pick up.

Comment threadsrc/errors/errors.tsx
export class RuntimeInvokeResponseError extends AgentCoreCLIError {
readonly reported = true;

export class RuntimeInvokeResponseError extends SilentCLIError {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are runtime invoke responses silent? I thought this was the error we get when the stream parsing fails.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, really like how simple this is now!

Comment threadsrc/index.ts
error_name: error.name,
error_source: error.source,
});
if (error.exitCode !== 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. Once we start getting data we can decide if separating into a separate reason makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@aidandaly24
aidandaly24force-pushed the feat/user-cancellation-error branch 2 times, most recently from 5c7c41f to 2e3832fCompareAugust 17, 2026 20:54
Hweinstock
Hweinstock previously approved these changes Aug 18, 2026

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

jariy17 pushed a commit that referenced this pull request Aug 20, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
@aidandaly24
aidandaly24 merged commit 71b0f0a into aws:refactorAug 21, 2026
8 of 11 checks passed
jariy17 pushed a commit that referenced this pull request Aug 24, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
jariy17 added a commit that referenced this pull request Aug 24, 2026
…#2032)
* feat(eval): batch-evaluation simulate — invokeDataset + self-running example classes
* refactor(eval): address Harrison's review feedback
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
* fix(eval): validate assertions/expected_trajectory + require {input} in payload-template
Bug bash (real exploratory-account run) surfaced two defects in the shared invokeDataset path:
1. PredefinedExample blind-cast assertions/expected_trajectory as string[] with no load-time
validation (unlike turns). A non-array value passed load, burned a live paid invoke, then
threw a raw '.map is not a function' mislabeled 'failed to invoke'. Now validated in the
constructor -> clean InputValidationError before any invoke.
2. A --payload-template with no {input} placeholder was silently accepted, wasting a full
~3-min replay on a constant payload. Now rejected up front in invokeDataset.
---------
Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aidandaly24@codecov-commenter@Hweinstock@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(errors): add shared user cancellation error - #1986

Merged
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error
Aug 21, 2026
Merged

feat(errors): add shared user cancellation error#1986
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error

Conversation

@aidandaly24

@aidandaly24aidandaly24 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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

  • classify CommanderError in AgentCoreCLIError.fromError, preserving help as exit 0 and mapping parse failures to usage exit 2
  • add an opt-in SilentCLIError category so only intentionally silent errors skip generic root stderr output
  • add a silent, user-sourced UserCancellationError with exit code 130
  • centralize process SIGINT listener lifecycle in withUserCancellation for Runtime invoke, Gateway invoke, dataset get, and dataset update
  • use the shared cancellation error as each operation's AbortSignal.reason, including Project dev
  • preserve Project dev's SIGINT/SIGTERM handling, single Shutting down… message, repeated-signal behavior, and listener cleanup
  • preserve Runtime and Gateway partial-response interruption summaries while propagating the original typed cancellation reason
  • leave TUI-local cancellation and low-level platform AbortError handling unchanged

Verification

  • focused cancellation suites across errors, root handling, Runtime, Gateway, Project dev, and datasets (146 pass, 0 fail)
  • full local source suite (1544 pass, 0 fail)
  • bun run typecheck
  • bun run lint:check
  • Prettier check for every changed file
  • bun run build
  • git diff --check
  • GitHub full unit suites:
    • Linux: 1544 pass, 0 fail
    • Windows: 1544 pass, 0 fail
    • macOS: 1544 pass, 0 fail
  • Linux, Windows, and macOS builds
  • AgentCore E2E CodeBuild
  • bundled CLI smoke checks:
    • nested help: exit 0, help on stdout, no stderr or error log
    • nested unknown option: exit 2, one Commander error line, classified as a user error

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 12, 2026
@codecov-commenter

codecov-commenter commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.13%. Comparing base (33e2d2f) to head (f17de43).

Additional details and impacted files
@@ Coverage Diff @@## refactor #1986 +/- ##
============================================
- Coverage 97.14% 97.13% -0.01% 
============================================
Files 382 382 Lines 22884 22856 -28 ============================================
- Hits 22231 22202 -29 - Misses 653 654 +1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aidandaly24
aidandaly24 marked this pull request as ready for review August 12, 2026 21:19
@aidandaly24
aidandaly24 marked this pull request as draft August 13, 2026 00:17
@aidandaly24
aidandaly24 marked this pull request as ready for review August 13, 2026 00:22

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the approach! few comments, but I think some could be follow-ups I could help pick up.

Comment threadsrc/errors/errors.tsx
export class RuntimeInvokeResponseError extends AgentCoreCLIError {
readonly reported = true;

export class RuntimeInvokeResponseError extends SilentCLIError {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are runtime invoke responses silent? I thought this was the error we get when the stream parsing fails.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, really like how simple this is now!

Comment threadsrc/index.ts
error_name: error.name,
error_source: error.source,
});
if (error.exitCode !== 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. Once we start getting data we can decide if separating into a separate reason makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@aidandaly24
aidandaly24force-pushed the feat/user-cancellation-error branch 2 times, most recently from 5c7c41f to 2e3832fCompareAugust 17, 2026 20:54
Hweinstock
Hweinstock previously approved these changes Aug 18, 2026

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

jariy17 pushed a commit that referenced this pull request Aug 20, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
@aidandaly24
aidandaly24 merged commit 71b0f0a into aws:refactorAug 21, 2026
8 of 11 checks passed
jariy17 pushed a commit that referenced this pull request Aug 24, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
jariy17 added a commit that referenced this pull request Aug 24, 2026
…#2032)
* feat(eval): batch-evaluation simulate — invokeDataset + self-running example classes
* refactor(eval): address Harrison's review feedback
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
* fix(eval): validate assertions/expected_trajectory + require {input} in payload-template
Bug bash (real exploratory-account run) surfaced two defects in the shared invokeDataset path:
1. PredefinedExample blind-cast assertions/expected_trajectory as string[] with no load-time
validation (unlike turns). A non-array value passed load, burned a live paid invoke, then
threw a raw '.map is not a function' mislabeled 'failed to invoke'. Now validated in the
constructor -> clean InputValidationError before any invoke.
2. A --payload-template with no {input} placeholder was silently accepted, wasting a full
~3-min replay on a constant payload. Now rejected up front in invokeDataset.
---------
Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aidandaly24@codecov-commenter@Hweinstock@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(errors): add shared user cancellation error - #1986

Merged
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error
Aug 21, 2026
Merged

feat(errors): add shared user cancellation error#1986
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error

Conversation

@aidandaly24

@aidandaly24aidandaly24 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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

  • classify CommanderError in AgentCoreCLIError.fromError, preserving help as exit 0 and mapping parse failures to usage exit 2
  • add an opt-in SilentCLIError category so only intentionally silent errors skip generic root stderr output
  • add a silent, user-sourced UserCancellationError with exit code 130
  • centralize process SIGINT listener lifecycle in withUserCancellation for Runtime invoke, Gateway invoke, dataset get, and dataset update
  • use the shared cancellation error as each operation's AbortSignal.reason, including Project dev
  • preserve Project dev's SIGINT/SIGTERM handling, single Shutting down… message, repeated-signal behavior, and listener cleanup
  • preserve Runtime and Gateway partial-response interruption summaries while propagating the original typed cancellation reason
  • leave TUI-local cancellation and low-level platform AbortError handling unchanged

Verification

  • focused cancellation suites across errors, root handling, Runtime, Gateway, Project dev, and datasets (146 pass, 0 fail)
  • full local source suite (1544 pass, 0 fail)
  • bun run typecheck
  • bun run lint:check
  • Prettier check for every changed file
  • bun run build
  • git diff --check
  • GitHub full unit suites:
    • Linux: 1544 pass, 0 fail
    • Windows: 1544 pass, 0 fail
    • macOS: 1544 pass, 0 fail
  • Linux, Windows, and macOS builds
  • AgentCore E2E CodeBuild
  • bundled CLI smoke checks:
    • nested help: exit 0, help on stdout, no stderr or error log
    • nested unknown option: exit 2, one Commander error line, classified as a user error

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 12, 2026
@codecov-commenter

codecov-commenter commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.13%. Comparing base (33e2d2f) to head (f17de43).

Additional details and impacted files
@@ Coverage Diff @@## refactor #1986 +/- ##
============================================
- Coverage 97.14% 97.13% -0.01% 
============================================
Files 382 382 Lines 22884 22856 -28 ============================================
- Hits 22231 22202 -29 - Misses 653 654 +1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aidandaly24
aidandaly24 marked this pull request as ready for review August 12, 2026 21:19
@aidandaly24
aidandaly24 marked this pull request as draft August 13, 2026 00:17
@aidandaly24
aidandaly24 marked this pull request as ready for review August 13, 2026 00:22

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the approach! few comments, but I think some could be follow-ups I could help pick up.

Comment threadsrc/errors/errors.tsx
export class RuntimeInvokeResponseError extends AgentCoreCLIError {
readonly reported = true;

export class RuntimeInvokeResponseError extends SilentCLIError {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are runtime invoke responses silent? I thought this was the error we get when the stream parsing fails.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, really like how simple this is now!

Comment threadsrc/index.ts
error_name: error.name,
error_source: error.source,
});
if (error.exitCode !== 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. Once we start getting data we can decide if separating into a separate reason makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@aidandaly24
aidandaly24force-pushed the feat/user-cancellation-error branch 2 times, most recently from 5c7c41f to 2e3832fCompareAugust 17, 2026 20:54
Hweinstock
Hweinstock previously approved these changes Aug 18, 2026

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

jariy17 pushed a commit that referenced this pull request Aug 20, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
@aidandaly24
aidandaly24 merged commit 71b0f0a into aws:refactorAug 21, 2026
8 of 11 checks passed
jariy17 pushed a commit that referenced this pull request Aug 24, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
jariy17 added a commit that referenced this pull request Aug 24, 2026
…#2032)
* feat(eval): batch-evaluation simulate — invokeDataset + self-running example classes
* refactor(eval): address Harrison's review feedback
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
* fix(eval): validate assertions/expected_trajectory + require {input} in payload-template
Bug bash (real exploratory-account run) surfaced two defects in the shared invokeDataset path:
1. PredefinedExample blind-cast assertions/expected_trajectory as string[] with no load-time
validation (unlike turns). A non-array value passed load, burned a live paid invoke, then
threw a raw '.map is not a function' mislabeled 'failed to invoke'. Now validated in the
constructor -> clean InputValidationError before any invoke.
2. A --payload-template with no {input} placeholder was silently accepted, wasting a full
~3-min replay on a constant payload. Now rejected up front in invokeDataset.
---------
Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aidandaly24@codecov-commenter@Hweinstock@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(errors): add shared user cancellation error - #1986

Merged
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error
Aug 21, 2026
Merged

feat(errors): add shared user cancellation error#1986
aidandaly24 merged 6 commits into
aws:refactorfrom
aidandaly24:feat/user-cancellation-error

Conversation

@aidandaly24

@aidandaly24aidandaly24 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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

  • classify CommanderError in AgentCoreCLIError.fromError, preserving help as exit 0 and mapping parse failures to usage exit 2
  • add an opt-in SilentCLIError category so only intentionally silent errors skip generic root stderr output
  • add a silent, user-sourced UserCancellationError with exit code 130
  • centralize process SIGINT listener lifecycle in withUserCancellation for Runtime invoke, Gateway invoke, dataset get, and dataset update
  • use the shared cancellation error as each operation's AbortSignal.reason, including Project dev
  • preserve Project dev's SIGINT/SIGTERM handling, single Shutting down… message, repeated-signal behavior, and listener cleanup
  • preserve Runtime and Gateway partial-response interruption summaries while propagating the original typed cancellation reason
  • leave TUI-local cancellation and low-level platform AbortError handling unchanged

Verification

  • focused cancellation suites across errors, root handling, Runtime, Gateway, Project dev, and datasets (146 pass, 0 fail)
  • full local source suite (1544 pass, 0 fail)
  • bun run typecheck
  • bun run lint:check
  • Prettier check for every changed file
  • bun run build
  • git diff --check
  • GitHub full unit suites:
    • Linux: 1544 pass, 0 fail
    • Windows: 1544 pass, 0 fail
    • macOS: 1544 pass, 0 fail
  • Linux, Windows, and macOS builds
  • AgentCore E2E CodeBuild
  • bundled CLI smoke checks:
    • nested help: exit 0, help on stdout, no stderr or error log
    • nested unknown option: exit 2, one Commander error line, classified as a user error

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 12, 2026
@codecov-commenter

codecov-commenter commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.13%. Comparing base (33e2d2f) to head (f17de43).

Additional details and impacted files
@@ Coverage Diff @@## refactor #1986 +/- ##
============================================
- Coverage 97.14% 97.13% -0.01% 
============================================
Files 382 382 Lines 22884 22856 -28 ============================================
- Hits 22231 22202 -29 - Misses 653 654 +1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aidandaly24
aidandaly24 marked this pull request as ready for review August 12, 2026 21:19
@aidandaly24
aidandaly24 marked this pull request as draft August 13, 2026 00:17
@aidandaly24
aidandaly24 marked this pull request as ready for review August 13, 2026 00:22

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the approach! few comments, but I think some could be follow-ups I could help pick up.

Comment threadsrc/errors/errors.tsx
export class RuntimeInvokeResponseError extends AgentCoreCLIError {
readonly reported = true;

export class RuntimeInvokeResponseError extends SilentCLIError {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are runtime invoke responses silent? I thought this was the error we get when the stream parsing fails.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, really like how simple this is now!

Comment threadsrc/index.ts
error_name: error.name,
error_source: error.source,
});
if (error.exitCode !== 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. Once we start getting data we can decide if separating into a separate reason makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@aidandaly24
aidandaly24force-pushed the feat/user-cancellation-error branch 2 times, most recently from 5c7c41f to 2e3832fCompareAugust 17, 2026 20:54
Hweinstock
Hweinstock previously approved these changes Aug 18, 2026

@HweinstockHweinstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

jariy17 pushed a commit that referenced this pull request Aug 20, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
@aidandaly24
aidandaly24 merged commit 71b0f0a into aws:refactorAug 21, 2026
8 of 11 checks passed
jariy17 pushed a commit that referenced this pull request Aug 24, 2026
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
jariy17 added a commit that referenced this pull request Aug 24, 2026
…#2032)
* feat(eval): batch-evaluation simulate — invokeDataset + self-running example classes
* refactor(eval): address Harrison's review feedback
- move renderJsonTemplate out of shared src/io into core/eval/invokeDataset
- invokeRuntime: raw TypeError/Error -> InputValidationError/RuntimeInvokeResponseError
- wire error causes in template + dataset JSON parse
- invokeDataset: enrich per-example invoke failure instead of log+rethrow
- simulate: bubble Ctrl-C cancellation (telemetry) instead of quiet return; clarify --description help; TODO(#1986) shared abort helper
- drop type-guaranteed 'no leak' test; keep AbortSignal wiring in composition test
- trim stale/redundant comments (runtime.tsx, invokeRuntime DTO note, TurnResult)
* fix(eval): validate assertions/expected_trajectory + require {input} in payload-template
Bug bash (real exploratory-account run) surfaced two defects in the shared invokeDataset path:
1. PredefinedExample blind-cast assertions/expected_trajectory as string[] with no load-time
validation (unlike turns). A non-array value passed load, burned a live paid invoke, then
threw a raw '.map is not a function' mislabeled 'failed to invoke'. Now validated in the
constructor -> clean InputValidationError before any invoke.
2. A --payload-template with no {input} placeholder was silently accepted, wasting a full
~3-min replay on a constant payload. Now rejected up front in invokeDataset.
---------
Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aidandaly24@codecov-commenter@Hweinstock@nborges-aws