Skip to content

config-replace: guarded config:set across a pipeline stage - #2

Merged
tarzan merged 1 commit into
mainfrom
maarten/config-set-all-apps
Aug 14, 2026
Merged

config-replace: guarded config:set across a pipeline stage#2
tarzan merged 1 commit into
mainfrom
maarten/config-set-all-apps

Conversation

@tarzan

@tarzan tarzan commented Aug 14, 2026

Copy link
Copy Markdown
Member

Adds a new subcommand for the "update every app that has VAR=old to VAR=new, ignore the rest" workflow:

heroku-scripts config-replace <pipeline> <stage> <VAR> <old-value> <new-value> \
  [--concurrency=N] [--dry-run] [-a|--all] [--table|--csv] [--no-stream]

Per app in the stage:

  • current value equals <old-value>config:set <VAR>=<new-value> (its output is the record)
  • var not set → skipped, with a count on stderr (-a/--all emits a skipped: <VAR> not set record instead)
  • value differs → left untouched but reported (skipped: <VAR> is "..." (expected "...")) so drift stays visible
  • config:get itself fails (no access, app gone) → error: ... record, never a write
  • --dry-run reads every current value and reports what would change without setting anything

Implementation

The chunked-parallel / streaming / table-CSV / skip-summary skeleton of pipeline-cmd is extracted into a shared run_pipeline_workers helper; pipeline-cmd and config-replace now differ only in their per-app worker. No behavior change to pipeline-cmd (all pre-existing tests pass unchanged).

Two hazards handled explicitly, both pinned by regression tests:

  • a failed config:get exit status is checked, so its error text is never compared against <old-value> (worst case, a matching message would have authorized a write to an app whose value was never read)
  • a failed config:set is captured before strip_heroku_noise, because under pipefail the pipeline status would make the worker return non-zero and silently misreport the failed write as a "var not set" skip

Known limitation (documented in README and usage()): config:get prints the same empty line for an unset var and one set to the empty string, so a var set to "" is treated as not set.

Testing

  • shellcheck bin/heroku-scripts install.sh clean
  • bats test: 37/37 pass (27 pre-existing + 10 new config-replace tests)

🤖 Generated with Claude Code

Sets <VAR> to <new-value> on every app in a stage, but only where the
current value is exactly <old-value>. Apps without the var are skipped
(counted on stderr, or recorded with -a/--all); apps whose value differs
are left untouched but reported, so drift stays visible. --dry-run
previews the writes.

The chunked-parallel/streaming skeleton of pipeline-cmd moves into a
shared run_pipeline_workers helper; both commands now differ only in
their per-app worker. Two hazards are handled explicitly: a failed
config:get is surfaced as an error record instead of having its error
text compared against <old-value>, and a failed config:set is captured
before strip_heroku_noise so pipefail can't turn it into a bogus
"var not set" skip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tarzan
tarzan merged commit 761e312 into main Aug 14, 2026
2 checks passed
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.

1 participant