Skip to content

smurf-tf-workflow.yml calls a subcommand that does not exist (stf format), and plan_only is ignored at v2.1.6 #447

Closed

Description

@clouddrove-ci

Two separate bugs found while wiring a production environment onto this shared workflow. Both are reproducible; evidence for each is below.

1. smurf stf format is not a real subcommand (on master)

.github/workflows/smurf-tf-workflow.yml line 277:

      - name: 🧹 Terraform Format
        run: |
          smurf stf format

stf format does not exist in any smurf release I can find. The command is stf fmt. smurf stf --help on both v1.1.9 and v1.2.0 lists:

  fmt         Format the Terraform Infrastructure

and no format.

Why it only surfaced now. smurf changed how it handles an unknown subcommand:

smurf smurf stf format files touched
v1.1.9 prints help, exit 0 0
v1.2.0 (2026-09-07) prints usage, exit 1 0

Verified by downloading both darwin-arm64 release binaries and running them against a clean git worktree of a repo that consumes this workflow.

So the step passed for as long as it has existed while formatting and checking nothing. v1.2.0's stricter handling is correct and simply exposed it. Every caller that does not pin smurf_version (it defaults to latest) went red on 2026-09-07 with no change in their own repositories.

Fix: smurf stf format → smurf stf fmt.

Second, smaller point on the same step: it is the only step in the job that runs without --dir. init, validate and the rest all pass --dir=${{ inputs.terraform_directory }}. So format operates on the whole checkout rather than the target root, which is why every root in a matrix fails together rather than just the one at fault. Worth passing --dir for consistency.

2. plan_only is declared and never read at v2.1.6

At tag v2.1.6 (the latest release) the reusable workflow is .github/workflows/tf-smurf.yml. It declares:

      plan_only:
        description: "Set this to `true` to run `smurf stf plan` only"
        required: false
        type: boolean
        default: false

and then never references plan_only again. The apply and destroy jobs gate on inputs.terraform_destroy:

      - name: ⏳ Approval Before Apply
        if: inputs.terraform_destroy != 'true'
      - name: 🚀 Terraform Apply
        if: inputs.terraform_destroy != 'true'

terraform_destroy is not a declared input at that tag — the declared name is destroy. An undeclared input evaluates to empty, so inputs.terraform_destroy != 'true' is always true and the apply path always runs, while terraform-destroy's inputs.terraform_destroy == 'true' is always false and never runs.

A caller passing plan_only: true therefore gets no protection from it. There is a trstringer/manual-approval@v1 gate in front, so this is not an unattended apply, but the input a caller uses to say "never apply" has no effect.

master's smurf-tf-workflow.yml has the correct gate (if: ${{ !inputs.plan_only }} on the apply job), so this is fixed there but not in any release.

3. Consequence for callers: no usable semver tag

CLAUDE.md in our repo requires callers to pin an exact semver tag. That is currently impossible:

  • At v2.1.6 the file is named tf-smurf.yml, not smurf-tf-workflow.yml. A caller naming the latter fails at startup with zero jobs, which no if: can skip.
  • tf-smurf.yml@v2.1.6 has bug 2 above.
  • Only master has the correct plan_only gate, and master is not a pin.

We have had to pin callers to a commit SHA, which is exact but not semver. A release cut from master, after fixing stf format, would let every caller move back to a tag.

Suggested order

  1. stf format → stf fmt on master (one word, unblocks everyone whose smurf_version floats).
  2. Optionally pass --dir to that step for consistency with the others.
  3. Cut a release from master so callers get the plan_only fix and a taggable version.

Activity

  1. clouddrove-ci commented on Sep 9, 2026

    @clouddrove-ci
    MemberAuthor

    One addition, so the fix does not land as a second no-op.

    stf fmt is non-recursive by default and takes --dir:

    Usage:
      smurf stf fmt [flags]
    
    Examples:
    	smurf stf fmt
    	smurf stf fmt --timeout 30s
    	smurf stf fmt --recursive --timeout 2m
    

    The step currently runs bare, with no --dir, so its working directory is the repo root. In a typical consumer that root holds no .tf files at all (they live under something like _infra/), so simply changing format to fmt would give a command that exits 0 while still checking nothing. Green, and still not doing the job.

    Suggested:

          - name: 🧹 Terraform Format
            run: |
              smurf stf fmt --dir=${{ inputs.terraform_directory }}

    which also makes it consistent with init, validate and the other steps, all of which already pass --dir=${{ inputs.terraform_directory }}. That has the useful side effect of scoping a failure to the root that caused it, instead of failing every root in a matrix together.

    Worth a note in the release description too: for callers whose roots have never actually been format-checked, the first run after this fix may legitimately go red. That is the check starting to work, not a regression.

  2. clouddrove-ci commented on Sep 9, 2026

    @clouddrove-ci
    MemberAuthor

    Confirmed all three, thanks. The stf format diagnosis is exactly right, including the cause: cmd/stf/stf.go gained Args: cobra.NoArgs, so what used to fall through to the group's Run and exit 0 now exits 1.

    One correction to point 2 of section 1: passing --dir to that step is not possible today. stf fmt has no --dir flag in any released smurf — it is the only stf subcommand without one, and internal/terraform/format.go hardcodes os.Getwd(). So that needs a smurf change before the workflow can use it.

    PRs:

    That leaves points 2 and 3 for you: master already has the right plan_only gate, so a release cut from master after #448 merges closes both, and gets callers back onto a tag instead of a SHA.

  3. clouddrove-ci commented on Sep 9, 2026

    @clouddrove-ci
    MemberAuthor

    All three points are shipped.

    smurf v1.2.1 — stf fmt gained --dir, and format is now accepted as an alias. Verified against the published darwin-arm64 binary, running the exact call this workflow was making:

    $ ./smurf stf format --dir=root-x
    Formatted 1 file(s).          exit=0
    # other.tf, outside root-x, left untouched
    

    Post-release smoke is green on all published assets, the ghcr image and the action on ubuntu and macOS.

    github-shared-workflows v2.1.7 — https://github.com/clouddrove/github-shared-workflows/releases/tag/v2.1.7

    1. smurf stf format -> smurf stf fmt (fix(smurf-tf): call stf fmt, the subcommand that exists #448).
    2. plan_only is honoured: this is the first tag carrying if: ${{ !inputs.plan_only }}.
    3. There is a pinnable semver tag again, so callers can drop the SHA pins.

    Read the release notes before bumping. Four workflow files were renamed between v2.1.6 and this tag, so the uses: line has to change too:

    v2.1.6 v2.1.7
    tf-smurf.yml smurf-tf-workflow.yml
    docker-smurf.yml smurf-docker.yml
    helm-smurf.yml smurf-helm-deploy.yml
    stf-checks.yml smurf-tf-checks.yml

    Deliberately left out: passing --dir to the Terraform Format step. It now exists in smurf, but adding it to the shared workflow would break any caller pinning smurf_version older than v1.2.1, since they would hit an unknown flag. The step carries a comment for whoever picks it up once v1.2.1 has propagated. Worth a separate issue if you want it tracked.

    Closing. Thanks for the write-up, the reproduction and the version table made this quick.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions