fix(toolchain): keep the 'v' prefix in cosign certificate-github-workflow-ref @aknysh (#3210)
## what- Align the bare (non-URL) cosign
--certificate-github-workflow-refvalue with the tool's actual downloaded asset tag, the same way the existing URL correction already aligns--certificate-identity. This unblocks toolchain installs (and CI) for tools whose release tag isv-prefixed (tflint, gum, ...).
why
- Toolchain installs began failing cosign verification on 2026-09-23:
Error: expected GitHub Workflow Ref not found in certificate --certificate-github-workflow-ref refs/tags/0.64.0 (should be refs/tags/v0.64.0) - Trigger: the unpinned upstream
aquaproj/aqua-registrymainadded a--certificate-github-workflow-ref refs/tags/{{.Version}}assertion to the tflint/gum cosign configs that day. Runs before passed, runs after failed (no Atmos code change; not a cold cache). - The aqua template is not wrong.
{{.Version}}is meant to be the actual release tag, and tflint has noversion_prefix(per aqua semantics the tag is used as-is). The failure is that Atmos renders{{.Version}}v-stripped (0.64.0) while the asset it downloaded is atv0.64.0— a divergence in Atmos's version handling (normalizeGitHubVersionstripsvwhen registry metadata is unavailable; the download fallback re-adds it on a 404). The new assertion just made a previously-harmless divergence fatal. renderArgsalready realigned URL args to the downloaded tag viareplaceVersionSegmentInURL(URLs only). The bare workflow-ref isn't a URL, so it stayed v-stripped and mismatched the cert.
scope
This is a targeted, symptomatic fix — it aligns the workflow-ref with the downloaded asset tag so the mismatch can't break verification. It does not infer v from the tool name and does not add any request. It does not attempt to prove which exact resolution/download path produced the v-stripped version in the live run.
The canonical fix — make the aqua registry version_prefix + real tag the single source of truth for {{.Version}}, and remove the v-prefix heuristics — is tracked in #3211 (per @osterman's guidance that the registry is the authority for tag format). Once #3211 lands, this correction becomes a no-op safety net.
changes
pkg/toolchain/verification/checksum.go: addreplaceVersionSegmentInPath(non-URL counterpart ofreplaceVersionSegmentInURL).pkg/toolchain/verification/signature.go:renderArgsapplies it scoped to the--certificate-github-workflow-refvalue only (so an unrelated non-URL option like--key /keys/0.64.0/public.pemis never rewritten). Previous arg tracked in a local var (avoids gosec G602 false positive).- Tests:
TestReplaceVersionSegmentInPath+TestRenderArgsCorrectsCosignWorkflowRef(asserts the workflow-ref gets thev, and a--keypath does not).
references
- Fixes #3209
- Canonical follow-up: #3211
- Unblocks the merge queue (#3200) and example-lint jobs on other PRs (e.g. #3204)
validation
go test ./pkg/toolchain/verification/(incl. reproduction test; fails before, passes after)go build ./...atmos lint --changed(0 issues); package coverage 88.9% (bothreplaceVersionSegment*100%,renderArgs92.9%)
Summary by CodeRabbit
- Bug Fixes
- Fixed signature verification for releases whose version tags include a
vprefix. Cosign workflow references now match the actual release tag, while version-like segments in unrelated key paths remain unchanged.
- Fixed signature verification for releases whose version tags include a
- Documentation
- Added a note describing the signature-verification issue and its resolution, including reproduction details, validation coverage, and related follow-up considerations.
🚀 Enhancements
fix(scaffold): default hook working directory to the scaffold target path @jorrite (#3206)
## what- Scaffold hooks (
before.scaffold.generate/after.scaffold.generate,kind: step/kind: steps) now default a step'sworking_directoryto the scaffold's target/output directory instead of falling through to the process's own cwd. - The scaffold's target path is also exposed to hook templates as
{{ .TargetPath }}, alongside the existing{{ .Answers }}. - Extracted the shared empty/bare/dot/absolute working-directory defaulting convention out of
pkg/hooks/step_engine.gointo an exportedhooks.ApplyDefaultWorkingDirectory(step, anchorDir), reused by both component/stack lifecycle hooks and scaffold hooks instead of duplicating the logic. scaffoldhooks.Runnow takes aRunInputstruct instead of six positional parameters, to stay within revive'sargument-limit.- Documented the new default (and the
working_directory: "."override) next to every existing scaffold-hooks example in the website docs, theatmos-hooksskill, and the scaffold PRD. - Added a
docs/fixes/record.
why
atmos scaffold generate <template> <target>allowstargetto differ from the invoking shell's cwd, but nothing anchored a hook'sworking_directorytotarget. An unset value fell throughShellHandlerintoexec.Cmd.Dir = "", which Go defaults to the process's own cwd — so the documentedterraform fmt -recursiveexample (and any other scaffold hook) silently ran against the wrong directory whenevertarget != cwd.- Component/stack lifecycle hooks already default to the component's own working directory (
pkg/hooks.ComponentPath); scaffold hooks had no equivalent anchor. This brings scaffold hooks in line with that existing convention rather than inventing a new one. - Not a behavior change for the common case. When
targetresolves to the same absolute directory as the invoking shell's cwd (e.g. runningatmos scaffold generate <template> .), the resolved working directory is identical before and after this change — existing hooks that already worked keep working exactly the same way. This fix only changes behavior for the previously-broken case: atargetwhose absolute path differs from cwd. - Hook authors can still opt back into the old cwd-relative behavior with an explicit
working_directory: "."in a hook'swith:block — dot-prefixed and absolute values are left untouched, same convention lifecycle hooks already use.
Testing
- Wrote a regression test first (
TestExecuteWithSetup_HooksDefaultWorkingDirectoryToTargetPathinpkg/generator/ui/ui_test.go) and confirmed it failed against the pre-fix code before implementing the fix. go build ./...andgo test ./pkg/hooks ./pkg/generator/scaffoldhooks/... ./pkg/generator/ui/...all pass.golangci-lintscoped toupstream/main(this fork'sorigin/mainis stale) reports 0 issues.cd website && npm run buildsucceeds after the doc edits.
references
- Closes #3205
Summary by CodeRabbit
- Bug Fixes
- Non-
type: atmosscaffold hook steps now default to the generated project’s target directory. Bare-relative working directories resolve under that target, whileworking_directory: "."retains launch-directory behavior.type: atmossteps use the launch directory when no working directory is set; explicit values are honored.
- Non-
- New Features
- Hook templates can reference the scaffold target directory with
{{ .TargetPath }}.
- Hook templates can reference the scaffold target directory with
- Documentation
- Updated scaffold and initialization guides to explain working-directory defaults and the
type: atmosexception.
- Updated scaffold and initialization guides to explain working-directory defaults and the
fix(provision): global workdir default lives in stack config, not settings @aknysh (#3200)
## what- A workdir global default belongs in the stack configuration under the toolchain section (
terraform.provision,helmfile.provision, ...) - consistent with globalvars,metadata, andsecrets- not inatmos.yamlsettings.provision. - That stack-level default already works today (lowest-precedence merge layer), and is overridden by component-level
provision.workdir(including an explicitenabled: false). - This PR removes the non-functional
settings.provision.workdirconfig surface and adds regression tests + corrected docs for the stack-level default.
why
- Issue #3197 reported that
settings.provision.workdir.enabled(documented inatmos.yaml) was ignored - only component-level worked. - Per maintainer review (@osterman): provisioning is component configuration, so its global default belongs in stack config, not
settings. Wiring upsettings.provisionwas the wrong direction; the correct mechanism (terraform.provision) already exists.
changes
- schema: dropped
WorkdirfromProvisionSettings; deleted the unusedProvisionWorkdirSettingstype; regeneratedpkg/datafetcher/schema/atmos/config/1.0.json(go generate ./pkg/config/schema). Component-levelprovision.workdirin the stack-config/manifest schemas is unchanged. - merge: reverted the provision merge in
stack_processor_merge.goto the inline four-layer merge (GlobalProvisionSection→ base → component → overrides); removed thestack_processor_provision.gosettingsinjection. - docs:
provision/workdir.mdx,provision/index.mdx,provision/backend.mdxnow document the stack-levelterraform.provisiondefault; deletedcli/configuration/settings/provision.mdx(documented the removed, never-implemented global). - tests/fixtures:
tests/fixtures/scenarios/workdir-global-default/declares the default as stack-levelterraform.provision.workdir.enabled: true; regression tests confirm inheritance and the component-levelenabled: falseoverride.
references
validation
go build ./...go test ./internal/exec/ -run TestGlobalWorkdirProvisionDefault -vgo test ./pkg/config/schema/ ./pkg/schema/ ./pkg/provisioner/...atmos lint --changed(0 issues)cd website && npm run build(no broken links)
Summary by CodeRabbit
-
Configuration
- Configure workdir provisioning defaults at the toolchain level in stack manifests.
- Component-level settings, including
enabled: false, override inherited stack defaults. - Global
settings.provision.workdirconfiguration is no longer supported.
-
Documentation
- Updated provisioning guidance for configuration scope and backend defaults.
- Documented workdir
ttlsettings and expired workdir cleanup eligibility.
-
Validation
- Added coverage for stack-level defaults and component-level overrides.