fix(cli): surface input schema issues and undeclared API response bodies - #43
Merged
Conversation
Rationale: Generated commands collapsed every non-response failure into a
generic "does not match the public API contract" message, hiding the Effect
Schema issue paths (e.g. a missing path.id) and the JSON input envelope
contract; undeclared API statuses (e.g. 501) dropped the server's error body
entirely. Input failures now carry structured issue paths (the generated
executor prefixes each envelope-section decode with its key via
SchemaIssue.Pointer and decodes absent path sections as {} so required
fields are named), render an actionable message plus a runnable --input
example derived from the command registry, and undeclared-status failures
retain the raw response body (truncated to 2000 chars) plus an extracted
server message.
Tested: bun test (153 pass), bun run build, bun run generate:check, and
manual "bun src/bin/akua.ts workspaces list-members" naming path.id with a
runnable next step.
Rationale: Review of the input/undeclared-status error work surfaced nine
confirmed gaps. Input examples now follow the operation's real body
contract: generate-commands.ts projects requestBody presence/requirement
plus placeholder examples for required top-level fields (one-level $ref
resolution, enum-first/typed placeholders) into the registry, replacing the
HTTP-method heuristic that suggested guaranteed-to-fail envelopes. Mode
detection treats an unknown isTTY (undefined on piped stdout in Node/Bun)
as non-interactive so piped consumers get structured output in any
environment, not only when agent/CI env vars leak in. Undeclared-status
bodies are decoded against ApiErrorResponse first (structured AKUA_API_<n>
codes), message extraction runs before truncation and prefers
errors[0].message with a top-level-message fallback, raw bodies render
wrapped ({raw}) and newline-flattened so agent output stays line-oriented
and the JSON response field stays object-typed. Stream failures reuse the
same effectful classification; HttpClientErrors after a 2xx/3xx are
transport, not api. Unknown internal failures classify as a new internal
reason instead of masquerading as user input errors, and the failure
classifier is single-sourced (describeGeneratedFailure enriches the mapped
result instead of mirroring its conditions). The generator's optional-body
decoder template is deduplicated.
Tested: bun test (160 pass) in both the normal shell and
env -u CLAUDECODE -u CLAUDE_CODE -u CI -u AGENT; bun run build;
bun run generate:check; manual proofs that the suggested examples pass
input decode for workspaces.listMembers, orderDrafts.create (bodyless
POST), and workspaces.addMember (required body), and that an undeclared
501 carrying an ApiErrorResponse envelope renders AKUA_API_9001 with the
server message.
Not-tested: operations whose OpenAPI paths embed ":action" suffixes (for
example /offers/{id}:archive) fail URL compilation inside the upstream
HttpApiClient before any request; reproduced on main, pre-existing and out
of scope here.
Member
Author
|
Pushed
All gates: Pre-existing issue found while proving, out of scope here: ops whose paths embed |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two error-reporting fixes for generated public API commands.
1. Input schema decode failures now name the offending fields
Before, any input that failed the generated Effect Schema decode rendered only:
mapGeneratedFailurediscarded theSchema.SchemaError, and nothing told the user about the{"path":{...},"query":{...},"headers":{...},"body":{...}}input envelope.Now:
GeneratedCommandFailurecarries structuredissues(path segments + message) produced by Effect'sSchemaIssue.makeFormatterStandardSchemaV1()formatter, plus the command name and a runnable--inputexample derived from the generated command registry's required parameters.SchemaIssue.Pointer(atEnvelopeKey) so issue paths are prefixed with the envelope key (path.id,body.secret, ...), and decodes an absentpathsection as{}so required path params surface as preciseMissing keyissues instead of a bareExpected object. Parse options do not enablereportInput, so request values stay redacted from errors.2. Undeclared API statuses no longer drop the server's error body
When the API returns a status not declared in the OpenAPI contract (real case: 501 from
GET /workspaces/{id}/members), the client fails with anHttpClientErrorand the CLI printed onlyAKUA_API_ERROR: The public API rejected the request.with no server detail.Now the failure path reads the undecoded response body (truncated to 2000 chars), exposes it in the error
responsefield, and — when amessagecan be safely extracted from the body JSON (errors[0].messageor top-levelmessage, validated via Schema) — uses it as the error message.Notes
test/cli.test.ts's "usage exit code" test was failing onmain(it expected the old Effect CLI help output); this branch updates it to the structuredusage_errorassertions, matching the identical edit already onfeat/pkg-subcommand(feat(release): ship the embedded package runtime in every archive #42) so the eventual rebase stays trivial. The other feat(release): ship the embedded package runtime in every archive #42 touchpoint,src/runtime/errors.ts, is edited only in theinput/apibranches ofgeneratedCommandError, away from feat(release): ship the embedded package runtime in every archive #42's addedpackageCommandError.Testing
bun test— 153 pass, 0 fail (6 new tests covering issue details, excess-property naming, rendered next steps, undeclared-status JSON body extraction, non-JSON truncation, and rendering)bun run build— tsc --noEmit + bundle cleanbun run generate:check— regenerated executor is in syncbun src/bin/akua.ts workspaces list-membersoutput shown above