refactor: unify result types with discriminated Result<T, E> union#1125
refactor: unify result types with discriminated Result<T, E> union#1125Hweinstock wants to merge 1 commit into
Conversation
6cde6f8 to
cc14f84
Compare
cc14f84 to
2a57e34
Compare
2a57e34 to
db3248f
Compare
db3248f to
a17bbae
Compare
a17bbae to
9cb5022
Compare
9cb5022 to
6642045
Compare
6642045 to
25dd47a
Compare
25dd47a to
ec8b476
Compare
cd560e1 to
450861e
Compare
450861e to
b6de8aa
Compare
b6de8aa to
352f76a
Compare
352f76a to
29296d0
Compare
29296d0 to
8cc8d41
Compare
Coverage Report
|
Bug Bash Report — refactor: unify result types with discriminated Result<T, E> unionBranch: TL;DRNo in-scope blockers, highs, or mediums found. Refactor is behavior-preserving: every happy/error path exercised produces identical output to In-scope bugsNone. Pre-existing bugs (reproduce identically on
|
| Surface | Tested | Result |
|---|---|---|
| Build | yes | pass |
| CLI happy path (create/validate/status) | yes | pass |
| Error paths (missing project, malformed JSON, bad schema) | yes | pass (behavior identical to base) |
| Schema validation | yes | pass |
Removal paths (remove agent, remove all, nonexistent) |
yes | pass |
| Partition correctness | n/a | PR does not touch ARN/endpoint code |
Refactor LGTM from a black-box behavioral standpoint — every tested surface produces identical stdout/stderr/exit code to main.
Package TarballHow to installnpm install https://github.com/aws/agentcore-cli/releases/download/pr-1125-tarball/aws-agentcore-0.13.1.tgz |
agentcore-cli-automation
left a comment
There was a problem hiding this comment.
Thanks for tackling this — the unified Result<T, E> type and Error-typed error field will pay off in telemetry and instanceof checks.
The bulk of this is a clean mechanical migration, but several of the converted return sites silently dropped fields that used to be part of the success/failure shape (log paths, console URLs, session IDs, exec payloads, warnings). Because many of these are exposed through --json, they're effectively public CLI output contracts. I've left inline comments on the spots that look like real behavior regressions. Once those are addressed (or intentionally accepted), this should be good to go.
A couple of smaller items not worth individual comments:
src/cli/operations/traces/insights-query.tsaround L80:err.message ? new Error(err.message) : new Error(String(error))— this could just betoError(error)for consistency with the rest of the migration.src/cli/commands/deploy/actions.ts:664:if (!tsResult.success && tsResult.error)— with the new discriminated union,tsResult.erroris guaranteed present whenever!tsResult.success, so the second clause is redundant. Justif (!tsResult.success)reads cleaner.
agentcore-cli-automation
left a comment
There was a problem hiding this comment.
Nice work on the unified Result<T, E> refactor — the typed error field is a clear improvement, and on the latest commit the behavioral regressions earlier reviewers flagged (exec-mode payload, sessionId on failures, warnings passthrough, consoleUrl on traces failures, NoProjectError typing) all appear to be addressed.
One systemic issue to flag before approving: there's a cluster of catch blocks that still strip the original error's type by doing new Error(err.message) (or an equivalent string round-trip). This directly undermines the PR's stated goal — downstream instanceof AccessDeniedError / instanceof DependencyCheckError / etc. checks, error-name matching in telemetry, and cause chains all break at these seams, because every one of them reconstructs a plain Error and forgets the original.
toError(err) in src/cli/errors.ts already exists for exactly this purpose; every one of these sites should use it (or, if a wrapping message is really wanted, pass { cause: err } so the original is still reachable via error.cause and instanceof still works on the cause). Inline comments below point out each occurrence I found.
agentcore-cli-automation
left a comment
There was a problem hiding this comment.
Reviewed the latest revision (fc93cd7).
I walked through the diff and cross-checked against the existing review threads. The behavior regressions flagged earlier (status --json missing fields, invoke --exec --json payload, sessionId on invoke failures, traces get consoleUrl, dependency-check warnings, deploy log path on stderr vs stdout, NoProjectError typing in AgentPrimitive) have all been fixed in this revision. The new Error(err.message) type-stripping cluster (primitives remove(), remove-gateway-target, run-batch-evaluation, run-recommendation, handleImport, resource-import, import-gateway, MCP/A2A/AGUI failure paths in invoke/action.ts, insights-query.ts) has also been converted to toError(err) or wrapped with { cause: err }, so instanceof / error.name / cause-chain inspection now survives the seams. Ran grep for the pre-existing patterns to double-check — none remain in files touched by this PR.
No new serious issues found beyond what was already flagged. LGTM — approving.
Introduce a shared Result<T, E> type (inspired by Rust's Result) that
replaces ad-hoc { success: boolean; error?: string } patterns across
the codebase.
Key changes:
- Add src/lib/types.ts with Result<T, E> discriminated union type
- Add toError() helper in src/cli/errors.ts for catch blocks
- Migrate all command, operation, and primitive result types to Result<T>
- Error field is now Error (not string) on the failure branch
- Data fields only exist on the success branch (proper narrowing)
- Update all consumers to narrow before accessing branch-specific fields
- Update test assertions to match new Error objects and add narrowing
|
Will fix security audit as a follow-up since its unrelated to changes here. |
| @@ -205,8 +206,9 @@ describe('handleImportRuntime', () => { | |||
| }); | |||
|
|
|||
| expect(result.success).toBe(false); | |||
There was a problem hiding this comment.
wouldn't the expects in this test file be redundant? if you have assert(!result.success); right after
There was a problem hiding this comment.
Yep, I tried to swap to assert everywhere where we needed the narrowed type, but it looks like I missed removing the existing expect here. It should be harmless to have both, and can also follow-up with a clean-up to remove this.
| await configIO.readDeployedState(); | ||
| } catch (err) { | ||
| return { success: false, error: formatError(err, '.cli/state.json') }; | ||
| return { success: false, error: new Error(formatError(err, '.cli/state.json'), { cause: err }) }; |
There was a problem hiding this comment.
why arent the errors in this file ValidationErrors?
There was a problem hiding this comment.
good question, we technically don't know if its an error from the schema being invalidated, or from issues reading from the filesystem. See implementation:
agentcore-cli/src/lib/schemas/io/config-io.ts
Line 195 in 6dc4b7f
We could have some logic that checks the error thrown and determines if it should be validation or something else, but I left that OOS.
|
Can you update the Agents.MD file i see a lot of stuff in there referencing the old result shapes |
Description
Problem
The codebase uses an inconsistent result-like type that changes depending on the context. The implementations vary slightly, and are not explicit about (usually) sharing a common shape. This common shape also strips error types, and distills their information down to a string, which leads to brittle behavior when catching errors.
As an example, systems that span modules (ex. telemetry) must handle each result type individually, resulting in excessive branching and complexity. Additionally, loss of error information, means loss of this information in telemetry, making it difficult to impossible to tell the type of error, and if it was user/application caused.
Solution
instanceofchecking and error name matching for stronger exception handling and telemetry.Future Work
There are other result types that could be generalized with further work to support spread operators on the error as well. I left this OOS since the PR is already very large. Some other small patterns that this PR continues, but I think should change in the future:
mapResultfunction that allows data transformations with clear error propagation. This could avoid some nesting of try/catches in a few places.Related Issue
Closes #
Documentation PR
N/A — internal refactor, no user-facing changes.
Type of Change
Testing
How have you tested the change?
npm run test:unitandnpm run test:integnpm run typechecknpm run lintsrc/assets/, I rannpm run test:update-snapshotsand committed the updated snapshotsChecklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the
terms of your choice.