mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-21 00:21:27 +00:00
## What
Per-step environment in `run.prepare.steps[].env` was parsed and then
**dropped** before it reached the resolved run settings, so prepare
steps could never see their declared env. This PR carries that env all
the way through to the executor, resolves prepare-step interpolation at
the run boundary, and fixes an argv-quoting bug.
Three things:
1. **Per-step env is carried through.** `RunPrepareSettings` now holds
`steps: Vec<PreparedStep>` (command plus per-step `env`) instead of a
flat `commands: Vec<String>`. The per-step env reaches `exec_command`,
which already accepts per-command env vars, and is merged on top of the
base sandbox environment.
2. **Interpolation resolves at the run boundary.** Prepare-step
`script`/`command` and per-step `env` values are carried in source form
out of the portable config resolve layer (so `fabro validate` stays
portable and never requires env to be set). Their `{{ env.* }}` tokens
resolve in the process that actually runs the steps, via
`RunPrepareSettings::resolve_step_env` — mirroring the existing MCP
transport env resolution. A missing env var is a **hard error**
(fail-closed); there is no fallback to the unresolved literal.
3. **Argv is shell-quoted.** Argv-style prepare steps were assembled
with `join(" ")`, so an argument containing spaces or quotes was
re-split by the shell. They are now shell-quoted per element with the
shared `shell_quote()` helper. `script` steps stay verbatim because they
are raw shell snippets.
## How
- `RunPrepareSettings.commands: Vec<String>` becomes
`RunPrepareSettings.steps: Vec<PreparedStep>` where `PreparedStep {
command, env }`. The server-side `{{ vars.* }}` substitution pass now
walks each step's command and env.
- New `RunPrepareSettings::resolve_step_env(env_lookup)` resolves `{{
env.* }}` in each step's command and env values, returning a hard error
on a missing var (and a loud `Unavailable` error for reserved
`secrets`/`inputs` tokens).
- The run boundary (`fabro_workflow::operations::start`) gains
`runtime_setup_commands`, the prepare-step counterpart to
`runtime_mcp_server`. `LifecycleOptions` now carries `Vec<SetupCommand>`
(command + env), and the initialize phase passes each step's env to
`exec_command`.
- `resolve_prepare` shell-quotes each argv element and carries per-step
env in source form. The stale lint suppression on the resolved fields is
rewritten to describe the deliberate source preservation that now
resolves at the run boundary.
- The shell-quoting helper moves to a shared `fabro_util::shell` module
(backed by `shlex`); `fabro_sandbox::shell_quote` delegates to it so the
config resolve layer and sandbox code share one audited implementation.
- The OpenAPI `RunPrepareSettings` schema and the generated TypeScript
client are updated to the new `steps`/`PreparedStep` shape.
## Testing
- `cargo build --workspace`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `cargo nextest run` for `fabro-util`, `fabro-types`, `fabro-config`,
`fabro-sandbox`, `fabro-api`, `fabro-workflow`, `fabro-server`,
`fabro-cli` (provider keys stripped) — all green.
- `cd lib/packages/fabro-api-client && bun run typecheck` — clean.
New tests cover: per-step env carried through resolution; script/command
+ env resolved at the run boundary; a missing env var is a hard error
(in both the command and a per-step env value); reserved `secrets`
tokens surface as `Unavailable`; argv elements are shell-quoted (an arg
with spaces/quotes is correctly quoted) while a `script` stays verbatim;
and an end-to-end check that per-step env reaches the executed setup
command (with a negative control proving the success is attributable to
the per-step env).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| approve.rs | ||
| archive.rs | ||
| artifact_cp.rs | ||
| artifact_list.rs | ||
| attach.rs | ||
| auth.rs | ||
| cli_reference.rs | ||
| config.rs | ||
| create.rs | ||
| deny.rs | ||
| diff.rs | ||
| discord.rs | ||
| docs.rs | ||
| doctor.rs | ||
| dump.rs | ||
| events.rs | ||
| exec.rs | ||
| fabro.rs | ||
| fork.rs | ||
| graph.rs | ||
| inspect.rs | ||
| install.rs | ||
| json_global.rs | ||
| logs.rs | ||
| mcp.rs | ||
| mod.rs | ||
| model.rs | ||
| model_list.rs | ||
| model_test.rs | ||
| parent.rs | ||
| parse.rs | ||
| pr.rs | ||
| pr_close.rs | ||
| pr_create.rs | ||
| pr_link.rs | ||
| pr_merge.rs | ||
| pr_unlink.rs | ||
| pr_view.rs | ||
| preflight.rs | ||
| provider.rs | ||
| provider_login.rs | ||
| ps.rs | ||
| render_graph.rs | ||
| repo.rs | ||
| repo_deinit.rs | ||
| repo_init.rs | ||
| resume.rs | ||
| rewind.rs | ||
| rm.rs | ||
| run.rs | ||
| runner.rs | ||
| sandbox_cp.rs | ||
| sandbox_preview.rs | ||
| sandbox_ssh.rs | ||
| secret.rs | ||
| secret_list.rs | ||
| secret_rm.rs | ||
| secret_set.rs | ||
| send_analytics.rs | ||
| send_panic.rs | ||
| server_start.rs | ||
| server_status.rs | ||
| server_stop.rs | ||
| start.rs | ||
| support.rs | ||
| system.rs | ||
| system_df.rs | ||
| system_events.rs | ||
| system_info.rs | ||
| system_prune.rs | ||
| system_repair.rs | ||
| test_panic.rs | ||
| top_level.rs | ||
| unarchive.rs | ||
| uninstall.rs | ||
| upgrade.rs | ||
| validate.rs | ||
| variable.rs | ||
| variable_get.rs | ||
| variable_list.rs | ||
| variable_rm.rs | ||
| variable_set.rs | ||
| version.rs | ||
| wait.rs | ||
| worker_auth.rs | ||
| workflow.rs | ||
| workflow_create.rs | ||
| workflow_list.rs | ||