Ctrl K
ComunidadGuías…

specs/015-step-run-script.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Spec: Step Run Script

## Status

Implemented.

## Scope

This spec defines local script-form `run`.

Script form is a multi-line string `run`.

Shared `run` behavior is defined by [Spec 013: Step Run](013-step-run.md).
Command-form `run` is defined by [Spec 014: Step Run Command](014-step-run-command.md).

This spec does not define:

- Single-line string `run`.
- Array-form `run`.
- Direct argv execution through `action: exec`.
- Non-local executor behavior.
- The implementation mechanism used to prepare a script.
- The path, filename, extension, mode bits, or lifetime details of any internal
  script preparation resource beyond the observable guarantees below.

## Goal

Workflow authors can write multi-line shell scripts inline in a workflow and get
predictable value resolution, line preservation, interpreter selection, shell
argument behavior, secrecy, and failure behavior.

## Behavior

### Script Text

- Script-form `run` is shell script text.

- Script-form `run` must contain one or more line breaks.

- A line break is any line-break sequence preserved by YAML decoding.

- Dagu resolves Dagu-owned references before the script starts.

- Dagu preserves the resolved script text as script content.

- Dagu preserves line breaks, leading whitespace, trailing whitespace, leading
  line breaks, and trailing line breaks in the resolved script content.

- Dagu must run the resolved script text as one script.

- Dagu must not split script-form `run` into multiple command-form invocations.

- Value resolution must not reclassify script form as command form.

- If resolved script-form text no longer contains a line break, it is still
  script form.

- The selected shell or shebang interpreter receives the script content through
  a script input prepared by Dagu.

- The script input identity exposed to the interpreter, such as a script path,
  script name, or `$0` value, is not a stable workflow API.

- A workflow must not depend on the script input identity, file extension, or
  directory. Use `working_dir`, `DAG_RUN_WORK_DIR`, artifacts, or explicit files
  when a stable path is required.

### Shell Operators

- Script-form `run` is passed to the selected shell or shebang interpreter as one script.

- Dagu accepts shell operator text in script-form `run`, including pipes (`|`), redirects (`>`, `>>`, `<`, `2>`), command chaining (`&&`, `||`, `;`), background execution (`&`), grouping syntax, and shell-specific control syntax.

- The selected shell or shebang interpreter defines which operator forms are valid and what they do.

- Dagu must not split script-form `run` at shell operators.

- Shell state and control flow remain inside the script process according to the selected shell or shebang interpreter.

- Background execution syntax is owned by the selected shell or shebang
  interpreter.

- Dagu determines the step result from the selected shell or shebang interpreter
  process, not from detached background work.

- A script that starts background work must wait for that work itself when the
  step result should include that work.

### Script Preparation

- Dagu must prepare the resolved script text before user script code starts.

- If Dagu cannot prepare the script, the step fails before user script code starts.

- Dagu must prepare the script so the selected shell or shebang interpreter can
  consume it as one script.

- The script process runs from the step process working directory.

- Dagu must not require workflow authors to make the workflow file directory
  writable for script preparation.

- Dagu must not require workflow authors to make the script input path stable or
  addressable from the script process working directory.

- If the step process working directory cannot be used as a process working
  directory, the step fails before user script code starts.

- Script preparation fails when Dagu cannot make the resolved script text available to the selected shell or shebang interpreter.

- After script preparation succeeds, Dagu must attempt to remove script
  preparation resources when the step attempt no longer needs the prepared
  script input.

- Cleanup must be attempted after selected shell or shebang interpreter start
  failure, successful script exit, non-zero script exit, abort, and timeout.

- Cleanup failure must not replace the original step result.

- Cleanup failure may be reported as a diagnostic, but it must not be reported
  as the step's execution error when the script process succeeded.

- Dagu-generated diagnostics in normal step errors, run logs, status records,
  history records, workflow events, and DAG-run detail responses must not
  include the full resolved script text.

- An explicit inspection or debug surface may show resolved script text only
  when workflow authors opt in to that surface.

- Dagu-generated diagnostics must not expose values that are secret-backed under
  the environment and secret specs.

- Any explicit inspection or debug surface that shows resolved script text must
  apply secret masking owned by the environment and secret specs.

- User script stdout and stderr are user-controlled output. Dagu captures them
  as defined by the shared `run` spec.

### Shebang

- Shebang detection reads only the first line of the resolved script content.

- The first line starts at the first byte or character of the resolved script
  content.

- Dagu must not skip leading whitespace, leading line breaks, or a byte order
  mark before checking for `#!`.

- If the first line of the resolved script content starts with `#!` and no
  step-level `with.shell` is explicitly specified, Dagu must invoke the shebang
  interpreter.

- Dagu trims ASCII spaces and tabs after `#!` on the shebang line.

- The interpreter command is the first shebang word after that trim.

- Shebang words are separated by ASCII spaces and tabs outside quoted spans.

- Single-quoted spans group text. Dagu removes the surrounding single quotes and
  keeps the contained characters literally. A single quote cannot appear inside
  a single-quoted span.

- Double-quoted spans group text. Dagu removes the surrounding double quotes.
  Inside a double-quoted span, a backslash escapes the following character, and
  the escaped character is copied literally.

- Outside quoted spans, a backslash escapes the following character, and the
  escaped character is copied literally.

- Backslashes are ordinary characters inside single-quoted spans.

- An empty quoted span produces an empty shebang word.

- Dagu does not perform shell expansion, environment expansion, glob expansion,
  command substitution, pipe parsing, or redirect parsing while parsing a
  shebang line.

- Remaining shebang words on the first line are passed as interpreter
  arguments.

- If a shebang line is present but cannot be parsed into a non-empty interpreter
  command, the step fails before user script code starts.

- If a shebang line contains an unterminated single quote, an unterminated double
  quote, or a trailing unpaired backslash outside single quotes, the step fails
  before user script code starts.

- If the interpreter command contains a path separator, Dagu uses that command
  path as parsed.

- If the interpreter command does not contain a path separator, Dagu resolves it
  through the step process `PATH`.

- A root shell does not count as an explicitly specified step-level shell for shebang suppression.

- A runtime default shell does not count as an explicitly specified step-level
  shell for shebang suppression.

- When a shebang interpreter is used, root shell settings, runtime default shell
  settings, and shell packages do not wrap the script.

- If step-level `with.shell` is explicitly specified, Dagu must execute the script through that shell and must not use the shebang interpreter directly.

- Shebang behavior for an existing script file named inside command-form `run` is outside this spec.

### Shell Behavior

#### Common Rules

- Shell construction uses the selected shell, configured shell arguments, the
  prepared script input, and configured shell packages where the selected shell
  supports packages.

- Shell-specific defaults are part of the script-form contract and must be
  covered by black-box tests before being changed.

- Configured shell arguments are passed to the selected shell before the script
  input unless a shell-specific rule below defines a different position.

- If configured shell arguments conflict with the script carrier required by the
  selected shell, Dagu must fail before user script code starts.

- When Dagu needs to embed the prepared script input identity in shell-owned
  command text, Dagu must pass that identity so its characters are treated as
  data, not shell syntax.

- Shell metacharacters in the prepared script input identity must not trigger
  extra commands, redirection, expansion, globbing, or argument splitting.

- For a selected shell that is not identified below, Dagu treats the selected
  shell as a script interpreter and passes the prepared script input as an
  argument after configured shell arguments.

#### Unix-Like Shells

- Unix-like script execution passes the prepared script input to the selected
  shell as a script file argument.

- Unix-like script execution must reject configured shell arguments that select
  command-string or stdin script carriers, including `-c` and `-s`.

- Unix-like script 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`.

- Step-level `with.shell` suppresses Dagu's automatic `-e` addition. Workflow
  authors must include `-e` in `with.shell_args` when they want that behavior
  with an explicit step shell.

#### PowerShell

- PowerShell script execution passes the prepared script input through
  PowerShell file execution.

- PowerShell script execution includes `-ExecutionPolicy Bypass` unless
  configured shell arguments already specify an execution policy.

- PowerShell script execution includes `-File` unless configured shell
  arguments already specify the same file-execution carrier.

- PowerShell file-execution carriers are `-File` and `-F`.

- If configured shell arguments include a PowerShell file-execution carrier,
  that carrier must be the final configured shell argument.

- When configured shell arguments include a PowerShell file-execution carrier,
  Dagu appends the prepared script input immediately after that carrier.

- If any configured shell argument appears after `-File` or `-F`, Dagu must fail
  before user script code starts because that argument would be interpreted as an
  authored file path or script argument.

- PowerShell script execution must reject configured shell arguments that select
  command-string carriers, including `-Command`, `-C`, and `-EncodedCommand`.

- PowerShell script execution includes `-NoProfile` and `-NonInteractive` unless configured shell arguments already include them.

- PowerShell script execution normalizes PowerShell error handling and UTF-8
  text encoding before user script code starts.

- PowerShell script input is written as UTF-8 with a byte order mark, so
  Windows PowerShell decodes the script text as UTF-8 regardless of the console
  code page and a multi-line script runs the same whatever characters its lines
  hold.

- Under PowerShell normalization, a PowerShell error written by `Write-Error`
  fails the step unless the script handles the error.

- Dagu determines PowerShell script success from the PowerShell process exit
  code.

- Native executable error semantics inside the script remain PowerShell-owned
  unless Dagu's PowerShell normalization explicitly changes them.

#### Windows cmd

- `cmd` script execution includes `/c` unless configured shell arguments already include `/c` or `/C`.

- `cmd` script execution must reject configured shell arguments that provide an
  authored command body after `/c` or `/C`.

- `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 script execution adds `-p <package>` for each configured shell package.

- Nix shell script execution includes `--pure` unless configured shell arguments already include `--pure` or `--impure`.

- Nix shell script execution includes `--run` unless configured shell arguments already include it.

- Nix shell script execution must reject configured shell arguments that provide
  an authored command body after `--run`.

- Nix shell script execution runs the prepared script input inside the Nix shell
  command environment.

- Nix shell script 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 script handles the failing command.

## Errors

Runtime must fail when:

- Dagu cannot prepare the script before user script code starts.

- Shebang detection fails.

- A shebang line is present but has no interpreter command.

- The selected shell or shebang interpreter cannot be started.

- The selected shell or shebang interpreter cannot find the command path.

- The selected `working_dir` value cannot be used as a process working directory.

- Configured shell arguments conflict with the script carrier required by the
  selected shell.

- The script exits with a non-zero code.

- The script process is terminated by abort or timeout.

- The script emits invalid declared outputs.

## Examples

Multi-line script:

```yaml
steps:
  - id: build
    run: |
      set -eu
      ./scripts/build.sh
      ./scripts/test.sh
```

Script with shebang:

```yaml
steps:
  - id: python_script
    run: |
      #!/usr/bin/env python3
      print("hello")
```

Step-level shell overrides shebang selection:

```yaml
steps:
  - id: force_sh
    run: |
      #!/usr/bin/env bash
      echo "$0"
    with:
      shell: sh
```

Explicit step shell with fail-fast Unix behavior:

```yaml
steps:
  - id: bash_script
    run: |
      echo before
      false
      echo after
    with:
      shell: bash
      shell_args: [-e]
```

Do not rely on the prepared script path:

```yaml
steps:
  - id: stable_paths
    run: |
      printf 'work dir: %s\n' "$PWD"
      printf 'run work dir: %s\n' "$DAG_RUN_WORK_DIR"
```

Wait for background work that belongs to the step result:

```yaml
steps:
  - id: background
    run: |
      ./long-task.sh &
      task_pid=$!
      wait "$task_pid"
```