Description of feature
FlagSet() pre-reads the recipe so it can register a flag per declared input, and that read is coupled to two process globals rather than to the command.
preloadRecipeForFlags already takes argv []string; only its three callers reach for the global, at commands/apply.go:111, commands/plan.go:106 and commands/validate.go:89. The fix mirrors the Ctx field #503 added for the run context: an exported Argv []string on ApplyCommand, PlanCommand and ValidateCommand, populated from main.go, falling back to os.Args when nil. ExportCommand needs nothing, having no recipe to preload. Eight test files currently save and restore os.Args around this and can stop.
The working directory is the second coupling and the less obvious one. When argv carries no --tasks, resolveTaskFileFromArgs probes defaultTaskFileCandidates (commands/task_file.go:39) in the current directory, and the commands write their output relative to it as well. Six test files call t.Chdir for that reason, which is what makes them permanently unparallelizable rather than merely awkward: t.Chdir panics in a parallel test. A base-directory parameter alongside argv is the difference between freeing those files and leaving them serial forever, and it is arguably a user-facing feature in its own right (--chdir).
Two smaller seams belong in the same change. chmodVarsFile (commands/export.go:381) is a swappable package var of the same shape as the old SetExecRunner, swapped by one test. The stdinRecipe memo (commands/task_file.go:305) is an unguarded package struct with a resetStdinRecipe() test hook, and its own comment says "the command path is single-goroutine" - precisely the assumption being removed.
This is a prerequisite for the commands/ half of #502. It is not sufficient on its own: the mask registry (#501) and the stub fixture map (the sibling issue) are the other two blockers.
Description of feature
FlagSet()pre-reads the recipe so it can register a flag per declared input, and that read is coupled to two process globals rather than to the command.preloadRecipeForFlagsalready takesargv []string; only its three callers reach for the global, atcommands/apply.go:111,commands/plan.go:106andcommands/validate.go:89. The fix mirrors theCtxfield #503 added for the run context: an exportedArgv []stringonApplyCommand,PlanCommandandValidateCommand, populated frommain.go, falling back toos.Argswhen nil.ExportCommandneeds nothing, having no recipe to preload. Eight test files currently save and restoreos.Argsaround this and can stop.The working directory is the second coupling and the less obvious one. When argv carries no
--tasks,resolveTaskFileFromArgsprobesdefaultTaskFileCandidates(commands/task_file.go:39) in the current directory, and the commands write their output relative to it as well. Six test files callt.Chdirfor that reason, which is what makes them permanently unparallelizable rather than merely awkward:t.Chdirpanics in a parallel test. A base-directory parameter alongside argv is the difference between freeing those files and leaving them serial forever, and it is arguably a user-facing feature in its own right (--chdir).Two smaller seams belong in the same change.
chmodVarsFile(commands/export.go:381) is a swappable package var of the same shape as the oldSetExecRunner, swapped by one test. ThestdinRecipememo (commands/task_file.go:305) is an unguarded package struct with aresetStdinRecipe()test hook, and its own comment says "the command path is single-goroutine" - precisely the assumption being removed.This is a prerequisite for the
commands/half of #502. It is not sufficient on its own: the mask registry (#501) and the stub fixture map (the sibling issue) are the other two blockers.