Skip to content

Commit d3b1142

Browse files
committed
Clarify foreign fallback scope and env/vars precedence
1 parent 388e908 commit d3b1142

3 files changed

Lines changed: 22 additions & 11 deletions

File tree

AGENTS.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,10 @@ The Go toolchain version comes from `go.mod` (`go 1.26.3`). Tests run with
136136
wins" so a parent file can override a flattened task by re-declaring it.
137137
See `loadFlatten` and `TestFlattenedTaskRunsFromRootDir`.
138138
- **Foreign fallback** (`fallback.go`) — when no `gogo.yaml` is found, gogo
139-
walks up looking for a `Taskfile.yml`, `mise.toml`, or `Makefile` whose
140-
runner is on `PATH`, and shells out. Order is fixed by `foreignRunners`
139+
looks in the current directory only (no walking up — an untrusted
140+
checkout's ancestors must never be executed; see
141+
`TestFallbackDoesNotWalkUp`) for a `Taskfile.yml`, `mise.toml`, or
142+
`Makefile` whose runner is on `PATH`, and shells out. Order is fixed by `foreignRunners`
141143
and the `make` arm intentionally drops `default` and skips the `--`
142144
separator (since `make` doesn't understand it). Tests stub the package-
143145
level `fallbackLookPath` / `fallbackRun` hooks rather than the real exec.
@@ -166,8 +168,10 @@ The Go toolchain version comes from `go.mod` (`go 1.26.3`). Tests run with
166168
`taskfile.Config` test in `run_test.go` plus a `Parse`-based test in
167169
`parse_test.go` if YAML shape matters.
168170
- **Touching env/var resolution**: respect the existing precedence
169-
(`BaseEnv` < parent task env < task dotenv < task vars < task env) and the
170-
rule that *task dotenv never overrides global dotenv or OS env* (see
171+
(`BaseEnv` < parent task env < task dotenv < task env < secrets) and the
172+
rule that vars and env are separate namespaces — task `vars:` are NOT
173+
exported to the shell environment (see `TestParentVarsDoNotFlowDownAsEnv`) —
174+
and the rule that *task dotenv never overrides global dotenv or OS env* (see
171175
`TestTaskDotenvDoesNotOverrideGlobalDotenv`). Sub-task calls
172176
(`cmds: - task: X`) propagate the parent's resolved env via
173177
`Runner.runSubTask` — deps do NOT (they're prerequisites, not sequenced

docs/features/errors/index.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,19 @@ You ran gogo from a directory that has no `gogo.yaml` and no ancestor with one e
1414

1515
**Fix:** create a `gogo.yaml` at the project root, or `cd` into a directory that has one above it.
1616

17-
When no `gogo.yaml` is found gogo also looks for foreign task files in the
18-
same walk:
17+
When no `gogo.yaml` is found gogo also looks for foreign task files — in the
18+
**current directory only**, never in ancestors (running gogo inside an
19+
untrusted checkout must not execute someone else's task file from a parent
20+
directory):
1921

2022
- `Taskfile.yml` / `taskfile.yml` / `Taskfile.yaml` / `taskfile.yaml` → shells
2123
out to [`task`](https://taskfile.dev).
2224
- `mise.toml` → shells out to [`mise run`](https://mise.jdx.dev).
25+
- `Makefile` / `makefile` / `GNUmakefile` → shells out to `make`.
2326

2427
If the matching binary isn't on `PATH` the runner is silently skipped (so a
25-
stray Taskfile in a parent directory doesn't break unrelated invocations).
28+
stray Taskfile doesn't break unrelated invocations when `task` isn't
29+
installed).
2630

2731
### `no gogo.yaml found in <dir>`
2832

docs/features/variables/index.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,12 +272,15 @@ The environment handed to `/bin/sh` is built up in layers, each one overriding t
272272
1. **`BaseEnv`** — the OS environment plus global `dotenv:` files
273273
2. **Inherited parent env** — only on sub-task calls (`cmds: - task: X`); deps don't inherit
274274
3. **Task `dotenv:`** — never overrides `BaseEnv` (i.e. global dotenv and OS env always win)
275-
4. **Task `vars:`** — promoted into env so commands can read them with `$NAME`
276-
5. **Task `env:`** — with cross-references resolved (see [Cross-References Between Env Entries](#cross-references-between-env-entries))
277-
6. **Task `secrets:`** — **highest precedence**; declaring a secret overrides any same-named placeholder in `env:`
275+
4. **Task `env:`** — with cross-references resolved (see [Cross-References Between Env Entries](#cross-references-between-env-entries))
276+
5. **Task `secrets:`** — **highest precedence**; declaring a secret overrides any same-named placeholder in `env:`
277+
278+
Note that task `vars:` are **not** part of this list: vars and env are separate
279+
namespaces. A `vars:` entry is available to `{{ "{{" }}.VAR}}` templating but is
280+
never exported to the shell, so `$NAME` in a command only sees the layers above.
278281

279282
A few non-obvious consequences:
280283

281284
- A task `dotenv:` that names a key already set in the global dotenv (or in the user's shell) is silently ignored — see [Dotenv › Resolution Rules](../dotenv/#resolution-rules).
282-
- Sub-tasks called from `cmds:` see the parent's resolved env (vars + dotenv + `env:` block). Same task name called from two parents with different env runs twice, because gogo bypasses task-level memoization when extra context is in play. See [Parent-to-child Env Propagation](#parent-to-child-env-propagation).
285+
- Sub-tasks called from `cmds:` see the parent's resolved env (dotenv + `env:` block — not `vars:`). Same task name called from two parents with different env runs twice, because gogo bypasses task-level memoization when extra context is in play. See [Parent-to-child Env Propagation](#parent-to-child-env-propagation).
283286
- A task that declares both `env: { OPENAI_API_KEY: dummy }` and `secrets: [OPENAI_API_KEY]` gets the secret value at run time. Use this pattern to keep a clear placeholder for local dev while still resolving the real value in CI.

0 commit comments

Comments
 (0)