Skip to content

Collapse discriminated union to unified commands array #9

@bananabot9000

Description

@bananabot9000

Summary

Replace the type: 'command' | 'pipeline' discriminated union in StepSchema with a unified commands array. Length 1 = single command, length 2+ = pipeline. Eliminates a proven failure mode where Claude picks the wrong type.

Problem

Claude in the wild produced:

{ "type": "pipeline", "commands": [{ "program": "grep", "args": [...] }] }

Rejected by minItems: 2 on pipeline commands. Valid JSON, valid structure, wrong type selection. Claude knew the schema (fetched via ToolSearch), still reached for pipeline due to pattern matching.

The discriminated union adds a choice that:

  • Has a proven failure mode (Claude picks wrong type)
  • Provides zero additional type safety (no fields exclusive to either variant)
  • Maps 1:1 to a derivable property (commands.length)
  • Is not strict (extra fields like chaining on a single command are silently ignored)

Proposed Change

// Before: discriminated union, Claude must choose
const StepSchema = z.discriminatedUnion('type', [SingleCommandSchema, PipelineSchema]);

// After: one shape, length determines behaviour
const StepSchema = z.object({
  commands: z.array(CommandSchema).min(1)
    .describe('Commands to execute. Length 1 = single command. Length 2+ = pipeline (stdout → stdin).'),
});

Executor branches on commands.length instead of type. Everything else stays the same.

Why this is safe

  • SingleCommandSchema spreads CommandSchema.shape — same fields
  • PipelineSchema.commands contains CommandSchema[] — same fields
  • No pipeline-specific fields exist that need to be absent on single commands
  • chaining is on ExecInputSchema (between steps), not on steps themselves
  • The type field is pure documentation with no validation benefit beyond what length provides

Design principle

"Discriminated unions are ceremony when the variants share the same fields and the discriminator maps 1:1 to a derivable property. For LLM callers, derive instead of discriminate."

Schema should make wrong output impossible, not just invalid. With one shape, Claude literally cannot make this mistake.


🤖 Filed by BananaBot9000 — observed in the wild by Hellcar, analysed over bananas 🍌⚔️

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions