specs/014-step-run-command.md
Rama main · réplica de lectura del repositorio original.
# Spec: Step Run Command
## Status
Implemented. This spec defines command-form `run` behavior.
## Scope
This spec defines command-form `run`.
Command form is:
- A single-line string `run`.
- Each non-empty entry in array-form `run`.
Shared `run` behavior is defined by [Spec 013: Step Run](013-step-run.md).
Script-form `run` is defined by [Spec 015: Step Run Script](015-step-run-script.md).
This spec does not define:
- Multi-line string `run`.
- Direct argv execution through `action: exec`.
- Non-local executor behavior.
## Goal
Workflow authors can run shell command text with predictable shell handoff,
shell-specific behavior, and sequential behavior for array-form `run`.
## Behavior
### Command Text
- Command-form `run` is shell command text.
- Command-form `run` must not contain line breaks.
- Dagu resolves Dagu-owned references before the command starts.
- Value resolution must not reclassify command form as script form.
- If resolved command-form text contains a line break, the step fails before the selected shell starts.
- Dagu treats the resolved command text as the command payload for the selected
shell.
- Shell-specific rules below define how Dagu gives that payload to the selected
shell's command-string carrier.
- Dagu must not split command-form `run` into a user-authored argv list.
- Any command parsing, normalization, or display metadata produced by Dagu must not change the resolved command text payload.
- Shell syntax in the command text is owned by the selected shell.
- Dagu starts the selected shell with the step process working directory.
- Relative paths in command text are interpreted by the selected shell or operating system from that working directory.
- Bare command-name lookup is owned by the selected shell or operating system.
- Dagu must not add the step process working directory to command lookup rules.
### Shell Operators
- Command-form `run` is passed to the selected shell as one command string, so shell operators inside the command text remain inside that shell invocation.
- Dagu accepts shell operator text in command-form `run`, including pipes (`|`), redirects (`>`, `>>`, `<`, `2>`), command chaining (`&&`, `||`, `;`), background execution (`&`), grouping syntax, and shell-specific control syntax.
- The selected shell defines which operator forms are valid. For example, `sh`, Bash, PowerShell, and `cmd` do not share one operator grammar.
- Dagu must not split command-form `run` at shell operators.
- Dagu must not convert shell command chaining inside one command-form entry into array-form `run` entries.
- Array-form sequencing is Dagu-owned; shell operators inside an array-form entry are shell-owned for that entry.
- Dagu determines command-form success or failure from the selected shell
process result, not from individual shell operators inside the command text.
- Pipeline, list, conditional, grouping, and subshell exit behavior is owned by
the selected shell and its configured options.
- Background execution syntax is owned by the selected shell.
- Dagu determines the command-form result from the selected shell process, not
from detached background work that outlives that shell process.
- A command that starts background work must wait for that work itself when the
step result should include that work.
- A command must also wait for background work when stdout, stderr,
`DAGU_OUTPUT_FILE` records, artifacts, or filesystem side effects from that
work must be included deterministically in the step attempt.
- A workflow must not rely on detached background work that outlives the selected
shell process to contribute deterministic stdout, stderr, `DAGU_OUTPUT_FILE`
records, artifacts, or side effects.
### Array Form
- Array-form `run` is an ordered list of command-form entries.
- Each non-empty array entry runs as one command-form invocation.
- Array-form entries must not contain line breaks.
- If a resolved array-form entry contains a line break, the step fails before the selected shell starts.
- Entries run sequentially.
- Each started entry uses its own selected-shell invocation.
- Shell process state does not carry from one array entry to the next.
- State created outside the shell process, such as files written in the working
directory, may be observed by later entries according to the operating system
and selected shell behavior.
- Each entry starts with the same step process working directory selected for
the step, unless external state changes make that directory unusable before a
later entry starts.
- Each entry receives the step attempt environment assembled before command
execution starts.
- Environment changes made inside one entry do not change the environment Dagu
supplies to later entries.
- A workflow that needs shared shell state across multiple lines must use
script-form `run`.
- Dagu captures stdout and stderr from started entries as the step attempt's
stdout and stderr streams in entry order.
- A runtime feature that consumes captured stdout or stderr for the step attempt
observes the aggregate captured stream from all started entries.
- The aggregate captured stream is deterministic only for work completed before
each entry's selected shell process exits.
- All entries in one step attempt share the same `DAGU_OUTPUT_FILE` for that
attempt.
- Dagu parses `DAGU_OUTPUT_FILE` only after all array entries complete
successfully.
- `DAGU_OUTPUT_FILE` records are deterministic only for records written before
the selected shell process for the final successful entry exits.
- If any array entry fails, aborts, or times out, the step attempt publishes no
outputs from `DAGU_OUTPUT_FILE`.
- Execution stops at the first failed entry.
- Later entries must not start after an earlier entry fails.
- The step attempt result is the first failed entry result, or success when every
non-empty entry succeeds.
### Diagnostics And Secrecy
- Dagu-generated diagnostics in normal step errors, run logs, status records,
history records, workflow events, and DAG-run detail responses must not expose
secret-backed values inserted into resolved command text by Dagu value
resolution.
- Dagu-generated diagnostics may include authored command text, command text
with secret-backed values masked, or a summarized command description.
- An explicit inspection or debug surface may show resolved command text only
when workflow authors opt in to that surface.
- Any explicit inspection or debug surface that shows resolved command text must
apply secret masking owned by the environment and secret specs.
- User command stdout and stderr are user-controlled output. Dagu captures them
as defined by the shared `run` spec.
### Shell Construction
#### Common Rules
- Dagu constructs the shell invocation from the selected shell, configured shell
arguments, command payload, and configured shell packages.
- Dagu supplies one command-string payload to the selected shell's command-string carrier.
- Unless a shell-specific rule below defines generated shell-owned wrapping, the
command-string payload is the resolved command text.
- Generated shell-owned wrapping must preserve the resolved command text as the
user-authored command payload and must not split it into a user-authored argv
list.
- Configured shell arguments may provide shell options.
- Configured shell arguments must not provide a separate command-string operand.
- If configured shell arguments include the selected shell's command-string flag, that flag must be the final configured shell argument.
- If a configured command-string flag is followed by another configured shell argument, the step fails before the selected shell starts.
- Command-form shell invocation order is:
1. Selected shell.
2. Configured shell arguments before any configured command-string flag.
3. Dagu-added shell options and shell package arguments.
4. Command-string flag.
5. One command-string payload.
- If configured shell arguments include the command-string flag, Dagu must insert Dagu-added shell options and shell package arguments before that configured flag.
- Configured shell packages are valid only when the selected shell family
defines package behavior.
- If configured shell packages are present and the selected shell family does not
define package behavior, the step fails before the selected shell starts.
- Shell-specific defaults are part of the command-form contract and must be covered by black-box tests before being changed.
- Shell family matching uses the normalized name of the selected shell.
- The normalized shell name is the selected shell executable basename after path components and an optional `.exe` suffix are removed.
- Shell family matching is ASCII case-insensitive.
#### Unix-Like Shells
- For this spec, Unix-like shells have normalized names `sh`, `bash`, `zsh`, `ksh`, `ash`, or `dash`.
- Unix-like shell execution includes `-c`; if configured shell arguments already include `-c`, that existing flag identifies where Dagu supplies the command-string operand.
- The only valid Unix-like command-string carrier token is exactly `-c`.
- A Unix-like configured shell argument that embeds a command string in the
carrier token, such as `-cecho hello`, is invalid.
- A Unix-like configured shell argument that combines `c` with other options,
such as `-lc`, is invalid and the step fails before the selected shell starts.
- Use separate shell arguments for other shell options. For example, use `[-l]`
when login-shell behavior is needed; Dagu adds or uses `-c` separately.
- Unix-like shell execution includes `-e` when the selected shell is Unix-like, no step-level shell is explicitly specified, and configured shell arguments do not already include `-e`.
#### PowerShell
- PowerShell rules apply to normalized shell names `powershell` and `pwsh`.
- PowerShell execution includes `-NoProfile` and `-NonInteractive` unless configured shell arguments already include them.
- PowerShell execution includes `-Command` unless configured shell arguments already include `-Command` or `-C`; an existing command-string flag identifies where Dagu supplies the command-string operand.
- PowerShell command-string carrier tokens are `-Command` and `-C`.
- PowerShell command-string flags are matched ASCII case-insensitively.
- A configured PowerShell command-string carrier with an inline value, such as
`-Command:Write-Host ok`, is invalid.
- PowerShell `-EncodedCommand` is invalid for command-form `run`.
- PowerShell command execution normalizes PowerShell error handling and UTF-8
text encoding before user command text runs.
- Under PowerShell normalization, a PowerShell error written by `Write-Error`
fails the step unless the command handles the error.
- Dagu determines PowerShell command success from the PowerShell process exit
code.
- Native executable error semantics inside the command remain PowerShell-owned
unless Dagu's PowerShell normalization explicitly changes them.
#### Windows cmd
- Windows `cmd` rules apply to normalized shell name `cmd`.
- `cmd` execution includes `/c` unless configured shell arguments already include `/c` or `/C`; an existing command flag identifies where Dagu supplies the command-string operand.
- `cmd` command-string carrier tokens are `/c` and `/C`.
- A configured `cmd` command-string carrier with an inline command body, such as
`/cecho hello`, is invalid.
- `cmd` resolves the configured `COMSPEC` path when the selected shell is `cmd` or `cmd.exe` and `COMSPEC` points to an existing path.
#### Nix Shell
- Nix shell rules apply to normalized shell name `nix-shell`.
- Nix shell execution adds `-p <package>` for each configured shell package.
- Nix shell execution includes `--pure` unless configured shell arguments already include `--pure` or `--impure`.
- Nix shell execution includes `--run` unless configured shell arguments already include it; an existing `--run` identifies where Dagu supplies the command-string operand.
- The only valid Nix shell command-string carrier token is exactly `--run`.
- A configured Nix shell command-string carrier with an inline command body, such
as `--run=echo hello`, is invalid.
- Nix shell command execution includes fail-fast behavior when no step-level
`with.shell` is explicitly specified.
- Under Nix shell fail-fast behavior, `echo before; false; echo after` must not
execute `echo after` unless the command handles the failing command.
#### Other Shells
- Shells not covered by Unix-like, PowerShell, Windows `cmd`, or Nix shell rules use `-c` as the command-string flag.
- For other shells, the only valid command-string carrier token is exactly `-c`.
- For other shells, a configured command-string carrier with an inline command
body, such as `-cecho hello`, is invalid.
- Dagu does not add shell-specific default options for other shells.
- If another shell does not support `-c` command-string execution, the step fails according to the normal runtime error rules.
## Errors
Runtime must fail when:
- The selected shell cannot be started.
- The command cannot be started by the selected shell.
- The selected shell cannot find the command path.
- The resolved command text contains a line break.
- The command exits with a non-zero code.
- An array-form entry fails.
- Configured shell arguments provide a separate command-string operand or use an
invalid command-string carrier.
- Configured shell packages are present for a shell family that does not define
package behavior.
## Examples
Single command:
```yaml
steps:
- id: hello
run: echo hello
```
Shell command syntax remains shell-owned:
```yaml
steps:
- id: list
run: find . -name "*.go" | sort
```
Array form:
```yaml
steps:
- id: checks
run:
- go test ./internal/spec/...
- go test ./internal/runtime/...
```
Array entries do not share shell state:
```yaml
steps:
- id: build_ui
run:
- cd ui && pnpm install
- cd ui && pnpm build
```
Use script form when shell state must carry across lines:
```yaml
steps:
- id: build_ui
run: |
cd ui
pnpm install
pnpm build
```
Wait for background work that belongs to the command result:
```yaml
steps:
- id: background
run: './long-task.sh & task_pid=$!; wait "$task_pid"'
```
Custom shell:
```yaml
steps:
- id: bash_example
run: printf "%s\n" "$BASH_VERSION"
with:
shell: bash
shell_args: [-e, -o, pipefail]
```