Skip to content

feat(runner): pre/post commands with separate env - #39

Open
tsvikas wants to merge 8 commits into
mainfrom
pre-post-commands
Open

feat(runner): pre/post commands with separate env#39
tsvikas wants to merge 8 commits into
mainfrom
pre-post-commands

Conversation

@tsvikas

@tsvikas tsvikas commented Apr 29, 2026

Copy link
Copy Markdown
Collaborator

Stacked on top of #38 — review/merge that one first.

Summary

Add Runner.pre_commands and Runner.post_commands for auxiliary subprocesses run alongside the main command. Use cases: build steps before training, environment snapshots (uv pip list) for reproducibility, post-run summaries.

New Command(name, command, env={}) dataclass:

  • name shows up in log filenames and the W&B artifact key.
  • command is split via shlex (str) or used as-is (list).
  • env is the command's own env (None to unset). Aux commands do not inherit Runner.env / Runner.secret_env — they get a clean env from os.environ plus per-command overrides. This avoids CUDA_VISIBLE_DEVICES etc. leaking into uv pip list-style snapshots.
  • $output is interpolated in both command tokens and env values.

Behavior:

  • Output is streamed live to the terminal and saved to <phase>_<name>.log (combined), <phase>_<name>_stdout.log, and <phase>_<name>_stderr.log in the run's output directory; uploaded to backends as text artifacts.
  • Commands run sequentially in declared list order.
  • Pre-command non-zero exit → main + post are skipped, backends still finalized cleanly with the failing exit code.
  • Post-command non-zero exit → warning, run outcome unchanged (consistent with existing never-fail post-run philosophy).
  • Skipped in --dry-run (logged but not executed).

Also extracted Runner._build_run_env() from execute() for clarity (still used by main command).

examples/run_example.py updated with pre uv sync -v and post uv pip list.

Test plan

  • uv run pytest (228 tests; 9 new for aux commands: pre runs/writes logs, pre failure skips main, post runs after main, post failure doesn't abort, post skipped when pre fails, name validation, $output interpolation in tokens + env, separate env from main, list-order execution)
  • uv run ruff check . && uv run mypy

🤖 Generated with Claude Code

tsvikas and others added 7 commits April 28, 2026 17:01
Extra env vars passed via Runner.env were logged to stderr but never
written to run_info.json (or W&B config), so reproducing a run from the
JSON alone was incomplete. Add meta/env as a dict; None values (vars
explicitly unset for the subprocess) survive as JSON null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reproducibility additions to the recorded run config:
- meta/cwd, meta/user — capture working directory and invoking user
- param_source/<k> — mirrors Runner.param_sources so the JSON shows
  whether each param value came from cli/default/fixed/prompt/override

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
For env vars (tokens, API keys) the subprocess needs but that should
never appear in logs or recorded config: secret_env values pass through
to the subprocess unchanged, but render as *** in the Env: log line and
are stored as "***" in meta/env so the key list is still recorded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…table

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New Command dataclass for auxiliary subprocesses run alongside the main
command. Output is streamed to the terminal and saved to per-command log
files (<phase>_<name>{,_stdout,_stderr}.log), which are also uploaded to
backends as text artifacts so the recorded run shows what setup or
teardown actually did.

- pre_commands run after the code snapshot but before the main subprocess.
  A non-zero exit aborts the run; main and post commands are skipped, but
  the backends are still finalized cleanly with the failing exit code.
- post_commands run after the main subprocess. Failures are warned but
  never change the run outcome (consistent with the never-fail post-run
  philosophy). Skipped only if a pre-command failed.

Also extracts _build_run_env() so aux commands inherit the same env
(including secret_env) as the main subprocess.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Command gains an env field (dict[str, str | None]); aux commands now
  build their own env from os.environ + cmd.env. They no longer inherit
  Runner.env / Runner.secret_env, so e.g. CUDA_VISIBLE_DEVICES set for the
  main subprocess doesn't leak into uv-pip-list-style snapshots.
- \$output is interpolated in command tokens and env values (same syntax
  as Param value=).
- Pre/post commands run in declared list order (already true; documented).
- Updated examples/run_example.py with pre 'uv sync -v' and post
  'uv pip list' to demonstrate the canonical use case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Base automatically changed from log-more-stuff to main April 29, 2026 13:51
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