fix(router): allow required flags in handlers - #2283
Conversation
…mplify wrapping handlers
|
Claude Security Review: no high-confidence findings. (run) |
There was a problem hiding this comment.
AgentCore Harness Review
Verdict: Changes requested
The refactor is well-structured: moving parseFlags/parseArguments into an inner withValidation middleware, and letting handlers declare their own middlewares, cleanly solves the "required flag but bare invocation should open the TUI" problem and lets you delete the awkward *Dispatch wrappers in handlers/project/index.ts. A couple of user-facing regressions I think need attention before merging, though:
1. Missing-required-flag error message regressed
Because option.makeOptionMandatory(true) was removed from toOption (src/router/flags.tsx), a missing required flag no longer produces Commander's clean message; it falls through to validateFlag and surfaces Zod internals. The test update in src/handlers/runtime/logs/logs.test.tsx:110 captures the new message:
Invalid value for option '--id': Invalid input: expected string, received undefined
That's a worse UX than the previous required option '--id <id>' not specified, and every handler you later migrate to the "required schema" pattern (e.g. harness get in this PR) inherits it. Options:
- In
validateFlag(src/router/flags.tsx), special-case theundefinedinput for a non-optional schema and throwInputValidationError(\required option '--${flag.name}' not specified`)before callingsafeParse`. - Or teach
formatZodErrorto translate thereceived undefinedissue into a friendlier "required option not specified" phrasing.
Either way, worth doing here rather than leaving the regression in tree.
2. --help no longer marks required options
Same root cause: without makeOptionMandatory, Commander no longer appends (required) next to the option in --help output. Users lose the visual signal of which flags they must provide. If you go with option 1 above, consider also calling option.mandatory = true (or keeping makeOptionMandatory but overriding missingMandatoryOptionValue) purely so the help text stays honest — you just need to ensure the TUI path still runs on a bare invocation.
Minor (not blocking)
src/router/router.test.ts:377comment still says "Commander reject before the handler runs"; it's now Zod insidewithValidationthat rejects. Worth updating so the test's intent stays clear.withLoggingnow logs raw pre-coercion flag values (e.g."7"instead of7for az.coerce.number()), because middleware aroundwithValidationseesnamedFlagsbuilt directly fromoptsWithGlobals(). Debug-only, but a behavior change worth being aware of.- Consider a direct unit test for the new
createHandler({ middlewares })wiring inrouter.test.ts— currently it's only exercised transitively throughproject/index.ts.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## refactor #2283 +/- ##
=========================================
Coverage 97.05% 97.06%
=========================================
Files 566 566
Lines 39228 39223 -5
=========================================
- Hits 38073 38072 -1
+ Misses 1155 1151 -4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Claude Security Review: no high-confidence findings. (run) |
|
Harness reviewer called out two valid consequences of this change that I think we can address as follow-ups:
This is actually good, because we now control the error message, and the error type so we not get
By marking flags as required in commander, any missing required flags will halt the execution before any of our code runs. We can wire this ourselves as part of the description. Note: this is the existing behavior since we didn't have any required flags. |
|
Claude Security Review: no high-confidence findings. (run) |
notgitika
left a comment
There was a problem hiding this comment.
added some comments + I think harness reviewer comments are good as well!
| try { | ||
| await expect(route(["runtime", "logs", "--since", `${SINCE_MS}`])).rejects.toThrow( | ||
| "required option '--id <id>' not specified", | ||
| "Invalid value for option '--id': Invalid input: expected string, received undefined", |
There was a problem hiding this comment.
are we sure we want this error message instead?
There was a problem hiding this comment.
This is the existing error message for when a flag does not match the schema, which this now fallsthrough to instead of hitting the commander error. I was originally going to update this and some other error messages as a follow-up but I can pull it in here.
| const description = | ||
| info.required && !info.boolean ? `${flag.description} (required)` : flag.description; | ||
| const option = new Option(token, description); |
There was a problem hiding this comment.
what do we think about using commander's helpGroup? ref: https://github.com/tj/commander.js/blob/master/examples/help-groups.js
output would look like:
Required Options:
--id <id> the ID of the harness
Options:
-h, --help display help
this would render required flags in a dedicated section while keeping presentation separate from validation. also, we should avoid makeOptionMandatory(), since it rejects missing flags before the TUI middleware can run
There was a problem hiding this comment.
I like that idea! I think @jariy17 is working on something similar about grouping flags, so I think we can revisit this once that lands.
|
Claude Security Review: no high-confidence findings. (run) |
Problem
Flags are not able to be marked as required in the flag schema because it broke the default handler behavior on the TUI.
Ex. if a flag was marked required, the default handler to open the TUI would never trigger, since commander rejected the input first.
Solution
Note: this does change the behavior of custom middleware such it is executed BEFORE flags/args are coerced and validated. Since flags/args are typed as any in middleware, this is unlikely to have a significant effect.
Testing/Verification
Migrated the get harness handler and tested a few cases:
Help page:
note that it now shows required.
Alternatives Considered
Future Work