Repo authors can now drop a `scripthut.yaml` at the root of a git or
path source and have its `env` / `env_groups` automatically applied
to every server-side run of that source's workflows. This replaces
the two awkward sharing patterns the proposal called out:
- Duplicating the same `env_groups` block in every `*.hut.json` —
drifts, painful to maintain.
- Defining repo-specific env in the server config — puts repo-author
concerns in operator-owned infra config.
Now: write the shared env once, in the repo, next to the workflows.
How it works:
- `RunManager._load_source_project_config(source, commit_hash=...)`
reads `<repo>/scripthut.yaml` from the server's local clone via
`git show <sha>:scripthut.yaml` (git sources) or SSH `cat
<path>/scripthut.yaml` over the source's backend (path sources).
Pins to the resolved commit so the env matches the workflow
revision being run. Missing file or unparseable cache → soft skip
returns None; forbidden sections (`backends:` / `sources:` /
`settings:` / `pricing:`) or malformed YAML → ValueError with the
same diagnostic the CLI's `_validate_project_local_yaml`
produces, so a misconfigured repo fails the run loudly rather
than silently dropping the entries.
- `create_run_from_source` calls the helper after resolving
`commit_hash`. The repo's `env` list is prepended to the workflow
doc's env list (so repo rules run first, workflow can react/
override), and its `env_groups` are under-merged so workflow-doc-
inline keys win on name collision. Generated tasks inherit
automatically because `run.doc_env` / `run.doc_env_groups` already
flow through `_handle_generates_source`.
- `/api/v1/sources/{name}/config` exposes the parsed project config
as JSON: `env`, `env_groups`, `stacks`. Soft-fails (200 with
`config_present=False`) when the file is absent or the source
hasn't been synced; 4xx only for "source not found" / "config
invalid". This is the surface the CLI uses to overlay stacks.
- `scripthut stack list / check / install / delete` gain a
`--source <name>` flag. When set, the CLI fetches the source's
project config from the server and overlays its `stacks:` on top
of the operator's local `config.stacks` (source wins on collision,
matching `_merge_configs`). Lets an operator install a stack
defined by a repo without first cloning the repo locally.
`--source` requires `--server` / `SCRIPTHUT_SERVER` /
`settings.cli_server` to be resolvable.
Precedence in the final env resolver becomes:
server config → backend → repo-project → workflow-doc → task
Out of scope for v0.7 (explicit design calls):
- Per-source `config_path` override for monorepos (defer until users
ask). Repo root only for v1.
- Auto-install-on-submit / `stack check` gating from runs. Stacks
remain operator-managed; tasks reference stack paths manually via
env rules. The `--source` flag just makes the definitions visible
to the existing CLI, not a new runtime concept.
- `set -e` in the generated script (separate concern; v0.6.5
already added `set -o pipefail`).
Tests:
- `test_source_project_config.py` — 16 tests across the helper (git +
path read paths, missing file, no cache, forbidden sections,
malformed YAML, empty YAML) and `create_run_from_source` env
merging (env_groups merge, collision behavior, env list ordering,
no-config-is-noop).
- `test_api_v1.py` — `/sources/{name}/config` happy path, missing
file (200 + config_present=False), 404 unknown source, 422
forbidden section.
- `test_cli.py` — `_overlay_source_stacks` covers no-op, server
required, fetch+merge, source-wins-on-collision, missing-config-
no-op, 404 source, 422 broken YAML; `stack list --source` smoke
test confirms the overlay reaches the renderer.
- Agent prompt — the resolver-order assertion now includes the new
repo-project layer; a new paragraph in the prompt's "Editing
scripthut.yaml" section documents the server-side application and
the `--source` stack flag.
482/482 in the broad sweep + 156 in the backend-specific suites pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>