Skip to content

feat: built-in --dry-run support#78

Open
aulneau wants to merge 1 commit intowevm:mainfrom
aulneau:feat/dry-run
Open

feat: built-in --dry-run support#78
aulneau wants to merge 1 commit intowevm:mainfrom
aulneau:feat/dry-run

Conversation

@aulneau
Copy link
Copy Markdown

@aulneau aulneau commented Mar 16, 2026

built-in --dry-run

incur gives you --help, --schema, --verbose, --format on every command for free. dry-run was missing. if you wanted it, you had to add dryRun to your options schema and check it in run() yourself. agents had no standard way to preview before executing.

i wanted this to work like help. always there, zero boilerplate.

design

i considered a few approaches. a separate dryRun() handler forces duplication for commands that interleave reads and writes. a full effect system with c.effect() turns incur into a different thing. a schema-derived preview that skips run() entirely is safe but useless — it just echoes your own inputs back at you.

the simplest thing that works: --dry-run is a builtin flag, c.dryRun is on the run context, run() is always called. the framework owns the flag across all transports, puts it on the context, and tags the output. commands that have side effects check c.dryRun and return a preview instead of executing.

what this adds

  • --dry-run builtin flag (CLI), X-Dry-Run: true header (HTTP), _meta.dryRun (MCP)
  • c.dryRun: boolean on both the run context and middleware context
  • output envelope tagged with meta.dryRun: true
cli.command('sync-repos', {
  args: z.object({ org: z.string() }),
  async run(c) {
    const plan = await buildPlan(c.args.org)
    if (c.dryRun) return plan
    return executePlan(plan)
  },
})

Sign up for free to 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.

1 participant