fix(pickled-iac): use real 'tofu' binary name; opentofu was never an executable - #31
Merged
Merged
Conversation
…executable
The IaC oracle stored '"opentofu"' in _IAC_BIN when only OpenTofu was on
PATH and handed that string straight to subprocess.run. OpenTofu installs
as the 'tofu' binary, never 'opentofu' (per opentofu.org/docs/intro/install),
so every pickled-iac draft / validate / plan / validate_terraform_dir MCP
call raised FileNotFoundError: [Errno 2] No such file or directory:
'opentofu' on hosts that had only OpenTofu installed.
The miss-detection slipped past CI because test_oracle.py only runs when
'terraform' or 'tofu' is on PATH, and the Terraform-only path was never
broken.
Split the two concerns:
* iac_binary() now returns the actual executable name ('terraform' or
'tofu') and feeds subprocess.run.
* iac_format() returns the human-facing format label ('terraform' or
'opentofu') used in ValidateResult / PlanResult / IaCArtifact.
drafter.py + oracle.py now call iac_format() for the label; the
'"opentofu" if binary == "opentofu" else "terraform"' branches that
silently encoded the bug are gone.
Regression coverage:
* test_oracle_binary.py — five tests that exercise the resolution logic
without requiring terraform/tofu on PATH, including an end-to-end
validate() against a Python-shebang fake 'tofu' binary plus a
source-level guard against re-introducing '_IAC_BIN = "opentofu"'.
* test_drafter.py / test_llm_sanitize.py — extended to patch iac_format
alongside iac_binary so both the binary and label paths are pinned.
Full workspace suite: 459 passed, 4 skipped.
mypy + ruff clean for the touched files.
Co-authored-by: Bartłomiej Rosa <bartrosa@users.noreply.github.com>
bartrosa
marked this pull request as ready for review
June 9, 2026 15:16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug & impact
pickled-iac draft <story>, thevalidate_terraform_dirMCP tool, andplan()were completely broken for every user who had OpenTofu installed but not Terraform — i.e. anyone who installedbrew install opentofu,apt install tofu,winget install OpenTofu.tofu, or the upstreaminstall-opentofu.shscript. The pickled-iac README explicitly advertises both: "requiresterraformortofuon PATH for validate/plan/draft."Reproduction (before the fix), with only
tofuonPATH:For drafting that means the user pays for three LLM completions and then sees a hard crash on the very first attempt — every validate retry fails the same way. For the MCP tool the failure is silent from the LLM-host's perspective (subprocess error surfaces as a tool error).
Root cause
oracle.pyconflated two distinct strings:_IAC_BINwas then handed straight tosubprocess.run([_IAC_BIN, "init", ...]). OpenTofu installs as thetofuexecutable —opentofudoes not exist on any supported install path (verified via OpenTofu docs: https://opentofu.org/docs/intro/install/standalone/). The miss-detection slipped past CI becausetest_oracle.pyonly runs whenterraformortofuis onPATH, and the Terraform-only path was never broken.IaCDrafter.draft_module()mirrored the same mistake (fmt = "opentofu" if binary == "opentofu" else "terraform").Fix
Split the executable-name concern from the format-label concern:
iac_binary() -> Literal["terraform", "tofu"]— actual executable name; whatsubprocess.runneeds.iac_format() -> Literal["terraform", "opentofu"]— human-facing label used inValidateResult.format,PlanResult.format,IaCArtifact.format.validate(),plan(), andIaCDrafter.draft_module()all now calliac_format()for the label andiac_binary()for the subprocess; the brittle"opentofu" if binary == "opentofu" else "terraform"branches that silently encoded the bug are gone.Validation
tofuon PATH, noterraform):validate()raisesFileNotFoundError: 'opentofu'. After:validate()returnsValidateResult(valid=True, format='opentofu')and spawns the realtofuexecutable.packages/pickled-iac/tests/test_oracle_binary.py(5 tests, run on every CI host because they don't require terraform/tofu installed):iac_binary()resolves to"tofu"andiac_format()to"opentofu"when only OpenTofu is present.iac_binary()/iac_format()both return"terraform"when Terraform is present.iac_binary()raisesIaCToolMissingErrorwhen neither is installed.validate()end-to-end against a Python-shebang faketofubinary — pre-flighted with apytest.raises(FileNotFoundError)on the bogusopentofuname to keep the regression honest._IAC_BIN = "opentofu"is never re-introduced, so a future refactor cannot silently bring the bug back on terraform-only CI hosts.test_drafter.pyandtest_llm_sanitize.pynow patchiac_formatalongsideiac_binaryand gain a coverage case for the"opentofu"label.uv run pytest→ 459 passed, 4 skipped.mypy packages/pickled-iac/src/: clean (10 source files).ruff check: clean for all touched files.