Summary
When apcdeploy diff runs from a terminal against an environment with an in-flight deployment, the "Deployment #N is currently DEPLOYING" warning is partially erased by the Targets renderer. With --parallel and multiple targets, warnings from concurrent goroutines interleave and the block's cursor math stays wrong for the rest of the run.
Details
diff.RunOnTarget calls displayDeploymentWarning after tr.Done(...) (internal/diff/executor.go:101,106) while the orchestrator's shared Targets handle is still open — it is closed only by the defer tg.Close() in internal/batch/orchestrator.go:93-94 after all targets finish.
displayDeploymentWarning (internal/diff/display.go:39-50) writes three raw lines directly to os.Stderr. The documented CONTRACT EXCEPTION in .claude/rules/output-contract.md justifies bypassing Reporter.Warn so the notice survives --silent; it does not account for the interaction with the live TTY renderer.
ttyTargets redraws with \033[<N>A\r\033[J (internal/cli/targets_tty.go:91-97), assuming the cursor sits directly below the block. The injected warning lines shift the real cursor, so:
- Single target: the final redraw in
Close() (targets_tty.go:205-228) moves up N rows into the warning text, clears to end of screen, and reprints the row — erasing the warning's last line ("The diff is calculated against the currently deploying version.") and duplicating the done-summary.
- Multiple targets with
--parallel: other targets' SetPhase/Done/animation ticks keep redrawing while the warning lines sit inside the block's assumed region, corrupting the entire block from that point on; concurrent warnings from separate goroutines can also interleave line-by-line (three separate unsynchronized Fprint calls).
Reproduction
From a TTY, start a slow deployment (e.g. a linear strategy), then run apcdeploy diff -c apcdeploy.yml while it is DEPLOYING.
Design considerations
Constraint to preserve: the warning must remain visible under --silent (that is the reason for the Reporter bypass).
- Defer emission until after
tg.Close(): collect per-target warnings during the run (alongside batch.PayloadCollector, or in batch.Summary) and print them from cmd/diff.go after the orchestrator returns. Keeps content identical; changes ordering (warning no longer adjacent to its row during the run) and adds a small collection mechanism.
- Fold the warning into the Targets row itself (e.g. Done summary suffix
— compared against in-flight deployment #N) plus a post-run stderr line for the full sentence. No raw writes while the block is open, but the --silent path still needs the separate post-run line.
- Teach the renderer to cooperate: add a Reporter primitive for "raw line emitted below an active Targets block" that repaints the block after writing. Most general (would also serve any future mid-run notices), highest implementation cost.
Option 1 is the smallest change that fixes both the overwrite and the multi-target interleaving.
Summary
When
apcdeploy diffruns from a terminal against an environment with an in-flight deployment, the "Deployment #N is currently DEPLOYING" warning is partially erased by the Targets renderer. With--paralleland multiple targets, warnings from concurrent goroutines interleave and the block's cursor math stays wrong for the rest of the run.Details
diff.RunOnTargetcallsdisplayDeploymentWarningaftertr.Done(...)(internal/diff/executor.go:101,106) while the orchestrator's shared Targets handle is still open — it is closed only by thedefer tg.Close()ininternal/batch/orchestrator.go:93-94after all targets finish.displayDeploymentWarning(internal/diff/display.go:39-50) writes three raw lines directly toos.Stderr. The documented CONTRACT EXCEPTION in.claude/rules/output-contract.mdjustifies bypassingReporter.Warnso the notice survives--silent; it does not account for the interaction with the live TTY renderer.ttyTargetsredraws with\033[<N>A\r\033[J(internal/cli/targets_tty.go:91-97), assuming the cursor sits directly below the block. The injected warning lines shift the real cursor, so:Close()(targets_tty.go:205-228) moves up N rows into the warning text, clears to end of screen, and reprints the row — erasing the warning's last line ("The diff is calculated against the currently deploying version.") and duplicating the done-summary.--parallel: other targets'SetPhase/Done/animation ticks keep redrawing while the warning lines sit inside the block's assumed region, corrupting the entire block from that point on; concurrent warnings from separate goroutines can also interleave line-by-line (three separate unsynchronizedFprintcalls).Reproduction
From a TTY, start a slow deployment (e.g. a linear strategy), then run
apcdeploy diff -c apcdeploy.ymlwhile it isDEPLOYING.Design considerations
Constraint to preserve: the warning must remain visible under
--silent(that is the reason for the Reporter bypass).tg.Close(): collect per-target warnings during the run (alongsidebatch.PayloadCollector, or inbatch.Summary) and print them fromcmd/diff.goafter the orchestrator returns. Keeps content identical; changes ordering (warning no longer adjacent to its row during the run) and adds a small collection mechanism.— compared against in-flight deployment #N) plus a post-run stderr line for the full sentence. No raw writes while the block is open, but the--silentpath still needs the separate post-run line.Option 1 is the smallest change that fixes both the overwrite and the multi-target interleaving.