An action arg bound to an omitted optional input fails the whole call, so a shadow cannot carry the leaf's optional fields #326

Closed
opened 2026-08-27 04:35:29 +00:00 by coilyco-ops · 0 comments
Owner

Found while landing coilyco-flight-deck/agentic-os#1105, and it is the reason that shadow is narrower than the leaf it replaces.

The behaviour

An input without required may legitimately be absent. But the argument bound to it resolves through ResolveArg, which errors:

pkg/stepflow/stepflow.go
    return "", fmt.Errorf("$%s is not set (an optional input that was not supplied)", ref)

buildCallRequest turns that into a UserError and the whole call dies. Observed on a real verb, with no change to anything:

$ aosguard ops forgejo action-run list coilyco-flight-deck umbra
aosguard: call 1 (list): action arg "limit": $limit is not set (an optional input that was not supplied)

So "optional" is only optional if nothing binds it. The moment an args block references it, it is required in practice and the error tells the caller to supply an input the guardfile called optional.

Why it matters beyond the message

A mount shadow replaces the generated leaf on the CLI. So a shadow can only expose fields it declares, and it can only declare them as required. In agentic-os#1105 that meant dropping --assignee, --assignees, --closed, --due_date, --milestone and --ref from issue create, because carrying them would have made every filing pass all six.

That is a real capability loss for every consumer that shadows a leaf to add a guard: the guard costs you the leaf's optional surface.

The fix, and why it does not fail open

Drop the argument from the request instead of failing the call. The obvious worry is that a typo'd $refrence would then silently vanish rather than erroring - but it cannot, because that is already caught earlier:

http/specverb/action_call.go  validateCallArgRef
    "arg %q references $%s, which no `input` declares"

Build-time validation rejects a bare $name that no input declares, so any reference surviving to runtime is a declared input. A $step.field reference is a separate case and keeps failing when its step is unbound.

--dry-run should render the same shape, so a plan never shows a ${placeholder} the live call would drop.

Scope

Spec dialect only. An execverb step takes positional argv tokens, and dropping one would shift every token after it, which could silently weaken an argN guard. That path must stay exactly as it is.

Note

A branch is ready and opens as a PR. agentic-os#1105 restores the full flag set on top of it.

Found while landing `coilyco-flight-deck/agentic-os#1105`, and it is the reason that shadow is narrower than the leaf it replaces. ## The behaviour An `input` without `required` may legitimately be absent. But the argument bound to it resolves through `ResolveArg`, which errors: ```go pkg/stepflow/stepflow.go return "", fmt.Errorf("$%s is not set (an optional input that was not supplied)", ref) ``` `buildCallRequest` turns that into a `UserError` and the whole call dies. Observed on a real verb, with no change to anything: ``` $ aosguard ops forgejo action-run list coilyco-flight-deck umbra aosguard: call 1 (list): action arg "limit": $limit is not set (an optional input that was not supplied) ``` So "optional" is only optional if nothing binds it. The moment an `args` block references it, it is required in practice and the error tells the caller to supply an input the guardfile called optional. ## Why it matters beyond the message A mount shadow **replaces** the generated leaf on the CLI. So a shadow can only expose fields it declares, and it can only declare them as required. In agentic-os#1105 that meant dropping `--assignee`, `--assignees`, `--closed`, `--due_date`, `--milestone` and `--ref` from `issue create`, because carrying them would have made every filing pass all six. That is a real capability loss for every consumer that shadows a leaf to add a guard: the guard costs you the leaf's optional surface. ## The fix, and why it does not fail open Drop the argument from the request instead of failing the call. The obvious worry is that a typo'd `$refrence` would then silently vanish rather than erroring - but it cannot, because that is already caught earlier: ```go http/specverb/action_call.go validateCallArgRef "arg %q references $%s, which no `input` declares" ``` Build-time validation rejects a bare `$name` that no `input` declares, so any reference surviving to runtime is a declared input. A `$step.field` reference is a separate case and keeps failing when its step is unbound. `--dry-run` should render the same shape, so a plan never shows a `${placeholder}` the live call would drop. ## Scope Spec dialect only. An execverb step takes positional argv tokens, and dropping one would shift every token after it, which could silently weaken an `argN` guard. That path must stay exactly as it is. ## Note A branch is ready and opens as a PR. agentic-os#1105 restores the full flag set on top of it.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/umbra#326
No description provided.