Skip to content

feat: configurable "default" context for tasks without one (#85) - #129

Merged
trntv merged 7 commits into
mainfrom
default-context-env
Jul 19, 2026
Merged

feat: configurable "default" context for tasks without one (#85)#129
trntv merged 7 commits into
mainfrom
default-context-env

Conversation

@trntv

@trntv trntv commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Overview

  • Adds a supported way to give all tasks a common env (and variables, working dir, executable, and lifecycle hooks): a context named default now backs any task that declares no context:. Resolves discussion Global variables #85.

Goal

  • Discussion #85 asked for "a common env: for all tasks." Previously env could 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 configured contexts: map.

Decisions

  • Modeled the shared baseline through the existing context mechanism instead of a new top-level env: key. It's the smaller change (only contextForTask), needs no new config schema, and reuses the whole context feature set — env, variables, dir, executable, and up/down/before/after hooks all apply to default-context tasks for free.
  • Precedence is default context < task: a task's own env/variables override the default context's for the same key (falls out of the existing merge order).
  • Contexts stay selectable — a task with an explicit context: uses that one and does not inherit default.
  • Backward compatible: when no default context is defined, context-less tasks still run in an empty implicit context.

Usage

contexts:
  default:
    env:
      GREETING: hello-global
tasks:
  show:      { command: 'echo "$GREETING"' }                              # hello-global
  override:  { env: { GREETING: hello-task }, command: 'echo "$GREETING"' } # hello-task

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings July 19, 2026 15:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 default context 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 default context.

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.

Comment thread runner/runner.go Outdated
Comment thread runner/runner.go
Comment thread runner/runner.go
Comment thread runner/runner.go
Comment thread docs/example.yaml Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 19, 2026 16:01
trntv and others added 2 commits July 19, 2026 19:01
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the default context (via name = defaultContextName), but the template variable .Context.Name is still populated from t.Context (see runner.go:189), so it remains empty for these tasks even when the default context is actually used. This makes the new behavior hard to observe in templates and contradicts the README statement that .Context is 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
	}

Copilot AI review requested due to automatic review settings July 19, 2026 16:03
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • contextForTask now resolves an implicit context name (default when t.Context is empty), but the resolved name is not surfaced anywhere. In particular, .Context.Name is currently populated from t.Context (see runner/runner.go:189), so tasks that implicitly use the default context 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
	}

Comment thread docs/example.yaml Outdated
Copilot AI review requested due to automatic review settings July 19, 2026 16:06
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@trntv
trntv merged commit e77ca83 into main Jul 19, 2026
8 checks passed
@trntv
trntv deleted the default-context-env branch July 19, 2026 16:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • contextForTask now resolves an implicit context name (falls back to "default"), but callers that expose the resolved context to templates still use t.Context rather than the resolved name (see vars.Set("Context", contextInfo{Name: t.Context, ...}) at runner/runner.go:195). As a result, a task that omits context: can run under the configured default context but still see an empty .Context.Name in templates, contradicting the README’s definition of .Context as 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
	}

Comment thread runner/runner_test.go
Comment on lines +219 to +237
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
}
Copilot AI review requested due to automatic review settings July 19, 2026 16:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread runner/runner.go
Comment on lines +27 to +29
// 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.
Comment thread runner/runner_test.go
Comment on lines +202 to +203
func TestTaskRunner_DefaultContext(t *testing.T) {
defaultCtx := NewExecutionContext(nil, "", variables.FromMap(map[string]string{
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.

2 participants