feat: configurable "default" context for tasks without one (#85) - #129
Conversation
Discussion #85 asked for a common env shared by all tasks. A task that declares no context: now resolves a context named `default` from config, so its env, variables, dir, executable and lifecycle hooks apply to every such task (precedence: default context < task). When no `default` context is defined, context-less tasks keep running in an empty implicit context. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a “default” execution context fallback so tasks that omit context: can still inherit shared execution settings (env/vars/dir/executable/hooks), enabling a common baseline environment across tasks via existing context configuration.
Changes:
- Update task execution to resolve context-less tasks against a configurable
defaultcontext when present. - Add runner tests covering default-context inheritance, task override precedence, explicit-context isolation, and backward compatibility when no default context exists.
- Document the new behavior in the README and update the example configuration to reference the
defaultcontext.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| runner/runner.go | Implements default-context fallback during context resolution for tasks without an explicit context. |
| runner/runner_test.go | Adds coverage for default context inheritance/override behavior and the no-default fallback path. |
| README.md | Documents how default context applies to tasks without context: and clarifies .Context.Executable semantics. |
| docs/example.yaml | Updates the example to use default as the implicit context for context-less tasks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
runner/runner.go:326
- Tasks without an explicit
context:now resolve to thedefaultcontext (vianame = defaultContextName), but the template variable.Context.Nameis still populated fromt.Context(seerunner.go:189), so it remains empty for these tasks even when thedefaultcontext is actually used. This makes the new behavior hard to observe in templates and contradicts the README statement that.Contextis the resolved execution context.
Consider returning the resolved context name from contextForTask (or otherwise threading it through) so .Context.Name reflects the selected context (e.g. "default" when falling back).
name := t.Context
if name == "" {
name = defaultContextName
}
Use `c, ok =` instead of `:=` in contextForTask so the assignment to the named return `c` is explicit, matching the original idiom. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
runner/runner.go:325
contextForTasknow resolves an implicit context name (defaultwhent.Contextis empty), but the resolved name is not surfaced anywhere. In particular,.Context.Nameis currently populated fromt.Context(see runner/runner.go:189), so tasks that implicitly use thedefaultcontext will still see an empty.Context.Name, which contradicts the new documented behavior and makes templates/diagnostics ambiguous.
name := t.Context
if name == "" {
name = defaultContextName
}
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
runner/runner.go:332
contextForTasknow resolves an implicit context name (falls back to "default"), but callers that expose the resolved context to templates still uset.Contextrather than the resolved name (seevars.Set("Context", contextInfo{Name: t.Context, ...})at runner/runner.go:195). As a result, a task that omitscontext:can run under the configureddefaultcontext but still see an empty.Context.Namein templates, contradicting the README’s definition of.Contextas the resolved execution context.
func (r *TaskRunner) contextForTask(ctx context.Context, t *task.Task) (c *ExecutionContext, err error) {
name := t.Context
if name == "" {
name = defaultContextName
}
| inherit := taskpkg.FromCommands(`printf "[%s]" "$GLOBAL_ENV"`) | ||
| inherit.Name = "inherit" | ||
|
|
||
| override := taskpkg.FromCommands(`printf "[%s]" "$SHARED_ENV"`) | ||
| override.Name = "override" | ||
| override.Env = variables.FromMap(map[string]string{"SHARED_ENV": "task-wins"}) | ||
|
|
||
| explicit := taskpkg.FromCommands(`printf "[%s]" "${GLOBAL_ENV:-unset}"`) | ||
| explicit.Name = "explicit" | ||
| explicit.Context = "other" | ||
|
|
||
| cases := []struct { | ||
| t *taskpkg.Task | ||
| want string | ||
| }{ | ||
| {t: inherit, want: "[from-default]"}, // no context -> inherits the default context env | ||
| {t: override, want: "[task-wins]"}, // task env overrides the default context env | ||
| {t: explicit, want: "[unset]"}, // an explicit context does not see the default context env | ||
| } |
| // defaultContextName is the context a task falls back to when it declares no | ||
| // context: if the config defines a context by this name it is used (so its env, | ||
| // variables and hooks apply to every such task), otherwise an empty context is used. |
| func TestTaskRunner_DefaultContext(t *testing.T) { | ||
| defaultCtx := NewExecutionContext(nil, "", variables.FromMap(map[string]string{ |
Overview
env(and variables, working dir, executable, and lifecycle hooks): a context nameddefaultnow backs any task that declares nocontext:. Resolves discussion Global variables #85.Goal
env:for all tasks." Previouslyenvcould only be set per-task, per-context, or per-stage — there was no shared baseline. The empty-context case built a throwaway empty context and never consulted the configuredcontexts:map.Decisions
env:key. It's the smaller change (onlycontextForTask), needs no new config schema, and reuses the whole context feature set —env,variables,dir,executable, andup/down/before/afterhooks all apply to default-context tasks for free.default context < task: a task's ownenv/variablesoverride the default context's for the same key (falls out of the existing merge order).context:uses that one and does not inheritdefault.defaultcontext is defined, context-less tasks still run in an empty implicit context.Usage
🤖 Generated with Claude Code