feat(cli): make defaultOnResult the default for buildCliCommands [OS-605]#812
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Greptile SummaryThis PR makes Key changes:
Confidence Score: 4/5Safe to merge — the runtime logic is correct and the breaking change is properly versioned; only documentation gaps remain. The
|
| Filename | Overview |
|---|---|
| packages/cli/src/actions.ts | Core change: buildCliCommands now resolves onResult to defaultOnResult when not provided, converting null to undefined for internal runAction calls. Logic is correct, but the updated TSDoc omits the critical note that custom onResult callbacks are fully responsible for error handling. |
| apps/outfitter/src/cli.ts | Correctly opts out of the new default by passing onResult: null, with a clear inline comment. No issues. |
| .changeset/cli-default-on-result.md | Correctly marks the change as major for @outfitter/cli since it alters default runtime behavior for all existing consumers of buildCliCommands. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["buildCliCommands(source, options)"] --> B{options.onResult}
B -- "=== null" --> C["onResult = undefined\nopt-out: silently discard success"]
B -- "=== undefined not set" --> D["onResult = defaultOnResult\nNEW DEFAULT"]
B -- "custom function" --> E["onResult = custom fn\ncaller responsible for errors"]
C --> F["runAction with onResult"]
D --> F
E --> F
F --> G{onResult truthy}
G -- "Yes" --> H["await onResult and return"]
G -- "No undefined" --> I{result.isErr}
I -- "Yes" --> J["throw result.error"]
I -- "No" --> K["return success discarded"]
H --> L["defaultOnResult:\nthrow if err else output value"]
Comments Outside Diff (2)
-
packages/cli/src/actions.ts, line 48-57 (link)Custom
onResulterror-handling responsibility no longer documentedThe previous TSDoc explicitly warned: "When provided, handler errors are not auto-thrown — the callback is responsible for error handling." This is still the actual runtime behavior (
runActionlines 304–311 return immediately after callingonResult, bypassing the auto-throw), but the new docs only describe the default and thenullopt-out — the custom-callback case is completely silent.A consumer who provides a custom
onResultthat doesn't callthrow ctx.result.errorfor error results will silently swallow errors. SincedefaultOnResultdoes throw, users extending it may not realize they need to replicate that behavior. Consider adding a note like: -
packages/cli/src/actions.ts, line 514-525 (link)defaultOnResultTSDoc is now staleThe function's description says "Pass this to
buildCliCommands({ onResult: defaultOnResult })to get automatic output…" and the@exampleblock shows exactly that pattern. However, the whole point of this PR is that you no longer need to pass it explicitly — it's the new default. This directly contradicts the updatedbuildCliCommandsexample, which removed the explicitonResult: defaultOnResultcall.The TSDoc and example should be updated to reflect the new role of
defaultOnResult:typescript
- // Extend the default with extra logging
- const commands = buildCliCommands(registry, {
- onResult: async (ctx) => {
-
logger.info("action completed", { id: ctx.action.id }); -
await defaultOnResult(ctx); - },
- });
-
*/
Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/cli/src/actions.ts
Line: 48-57
Comment:
**Custom `onResult` error-handling responsibility no longer documented**
The previous TSDoc explicitly warned: *"When provided, handler errors are not auto-thrown — the callback is responsible for error handling."* This is still the actual runtime behavior (`runAction` lines 304–311 return immediately after calling `onResult`, bypassing the auto-throw), but the new docs only describe the default and the `null` opt-out — the custom-callback case is completely silent.
A consumer who provides a custom `onResult` that doesn't call `throw ctx.result.error` for error results will silently swallow errors. Since `defaultOnResult` does throw, users extending it may not realize they need to replicate that behavior. Consider adding a note like:
```suggestion
/**
* Called after each handler returns with `Result.ok` or `Result.err`.
*
* Defaults to {@link defaultOnResult}, which outputs success values
* based on CLI flags (`--output`, `--json`, `--jsonl`) and throws errors.
* Pass `null` to disable and silently discard success values.
*
* When a custom function is provided, it is **fully responsible for error
* handling** — errors are not auto-thrown. The callback receives the raw
* `Result` and must throw (or otherwise surface) errors itself.
*/
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: packages/cli/src/actions.ts
Line: 514-525
Comment:
**`defaultOnResult` TSDoc is now stale**
The function's description says "Pass this to `buildCliCommands({ onResult: defaultOnResult })` to get automatic output…" and the `@example` block shows exactly that pattern. However, the whole point of this PR is that you no longer need to pass it explicitly — it's the new default. This directly contradicts the updated `buildCliCommands` example, which removed the explicit `onResult: defaultOnResult` call.
The TSDoc and example should be updated to reflect the new role of `defaultOnResult`:
```suggestion
/**
* Default `onResult` callback that outputs success values and throws errors.
*
* This is automatically used by {@link buildCliCommands} unless overridden.
* Import and reference it directly when composing a custom callback that
* partially delegates to the default behavior.
*
* @example
* ```typescript
* // Extend the default with extra logging
* const commands = buildCliCommands(registry, {
* onResult: async (ctx) => {
* logger.info("action completed", { id: ctx.action.id });
* await defaultOnResult(ctx);
* },
* });
* ```
*/
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (10): Last reviewed commit: "chore: add changeset" | Re-trigger Greptile
869116c to
b6f62cd
Compare
3defe22 to
64c34ec
Compare
64c34ec to
9286642
Compare
|
@greptileai review |
9286642 to
8cbabff
Compare
b6f62cd to
9584d09
Compare
8cbabff to
7754919
Compare
ebf3344 to
bd832f2
Compare
13e0b46 to
70904d5
Compare
bd832f2 to
58b1d54
Compare
58b1d54 to
1395a41
Compare
70904d5 to
6859c0f
Compare
Merge activity
|
6859c0f to
e3f83a8
Compare
e3f83a8 to
1f90a74
Compare
…6] (#813) ## Summary CI Summary job fails intermittently even when all individual jobs pass. Observed 3-4 times in a single session across PRs #806, #810, #811, #812. **Root cause:** When `gt submit` force-pushes a branch, GitHub Actions cancels the in-flight run. The `ci-summary` job checks `contains(needs.*.result, 'cancelled')`, which catches cancelled jobs from the superseded run — not actual failures. Fixes https://linear.app/outfitter/issue/OS-616/ci-summary-job-fails-spuriously-after-force-push-due-to-cancelled-job ## What changed `.github/workflows/ci.yml`: 1. **Dropped `cancelled` from the failure check** — only `failure` triggers the exit. Cancelled jobs from superseded runs aren't real failures. 2. **Added `concurrency` group** — `ci-${{ github.ref }}` with `cancel-in-progress: true` ensures only one CI run per branch exists at a time, preventing the race structurally. ## Test plan - [x] Workflow YAML is syntactically valid - [x] The concurrency group scopes to the branch ref, so main and PR branches don't cancel each other - [x] `cancel-in-progress: true` only cancels runs on the same ref (safe for stacked PRs on different branches) 🤘🏻 In-collaboration-with: [Claude Code](https://claude.com/claude-code)

Summary
buildCliCommandsnow usesdefaultOnResultby default, so handler results are automatically output based on CLI flags (--output,--json,--jsonl). Previously, success values were silently discarded unless consumers explicitly passedonResult: defaultOnResult— a pattern that was non-obvious enough that consumers kept writing their ownwithOutput()wrappers instead.Fixes https://linear.app/outfitter/issue/OS-605/surface-layer-output-handling-make-buildclicommands-handle-output-by
What changed
packages/cli/src/actions.ts:buildCliCommandsdefaultsonResulttodefaultOnResultinstead ofundefinedonResult: nullexplicitly opts out (silently discards success values, only throws errors)apps/outfitter/src/cli.ts:onResult: nullsince outfitter's actions handle their own output internallyThe principle
Handlers return data. Surfaces present it. This was already the intended design —
defaultOnResultexisted and worked. This change makes it the default rather than an opt-in that nobody discovers.Follow-up needed
Scaffold templates in
plugins/outfitter/shared/templates/still show handlers callingoutput()directly. These should be updated to reflect the new pattern (handlers returnResult, surface handles output). Filed as a follow-up — not blocking sinceonResult: nullprovides the escape hatch for existing consumers.Test plan
@outfitter/cli,outfitter)onResult: null— no double-output🤘🏻 In-collaboration-with: Claude Code