Skip to content

Live board rows on up/down get stuck on Creating/Removing/Pulling when the closing branch never fires #1347

Description

@Jaro-c

What's wrong

A board row opened with crate::ui::progress::start(...) has no guarantee of a matching progress_line(...) (which is what closes it via Board::finish, internal/ui/progress/board.rs:142). Several call sites across up and down return, or fall through, on a code path that never closes the row it opened — so that row sits on the Creating/Removing/Pulling/Stopping spinner forever, even though the underlying operation is done (successfully reused, successfully skipped, or genuinely failed).

This isn't one bug in one command — it's the same missing-close shape recurring at six sites across the two commands the live board covers most:

Command File:line Row opened as Branch that never closes it
up internal/engine/network/mod.rs:70-85 Network … Creating Err(e) if e.is_already_exists() => {}
up internal/engine/volume/mod.rs:68-82 Volume … Creating Err(e) if e.is_already_exists() => {}
up internal/engine/build/pull.rs:256-303 Image … Pulling Some(e) => Err(...) on the failed-pull path
down internal/engine/lifecycle/mod.rs:653-661 Network … Removing Ok(false) (nothing to remove) and Err(e) (removal failed — only tracing::warn!)
down internal/engine/lifecycle/mod.rs:694-702 Volume … Removing same two arms as the network case above
down internal/engine/lifecycle/parallel.rs:348-395 Container … Stopping Err(e) if e.is_status(404) (already gone) and the genuine-failure Err(e) arm

The container-create side of up (internal/engine/lifecycle/mod.rs:486-528) already gets this right — its "already exists" / "up to date" branches call progress_line("Container", ..., "Exists") or "Running" before returning, so that row always resolves. That's the pattern the other six sites are missing.

Why it matters

Most of these are the common path, not an edge case: any redeploy that reuses an existing network or volume hits it every time, and down hits it on a container/network/volume that's already gone (partial teardown, re-running down after a failure). The board ends up looking hung exactly when nothing is wrong, and — worse — the genuine-failure arms (down's network/volume removal, container stop/remove) also leave the row spinning while the error is only a tracing::warn! line or the final process error, with nothing on the board itself pointing at which resource failed.

Repro (generic)

Any compose file where two services share a network, run up once to create it, then up again:

==> Starting
 ⠋ Network   app_shared_net   Creating   0.0s
 ✔ Container app_web          Running

The network row never reaches a checkmark. Same shape on down when a network/volume/container from a previous partial run is already gone.

What's worth deciding before fixing

Patching each of the six sites with an honest closing verb ("Exists", "Absent", "Failed", …) fixes what's observed today, but the pattern will recur — progress::start still has no enforced matching close, so a seventh call site can reintroduce this the same way. Worth deciding whether the fix is per-site (fast, matches the existing "Exists"/"Running" precedent) or structural — something that makes an orphaned Working row impossible by construction (e.g. a guard returned by start() that force-closes on drop with a fallback verb if nothing closed it explicitly). Leaving that call for whoever picks this up rather than presupposing it here.

Impact

Cosmetic on the success/already-there arms — the operations themselves are correct. Worse than cosmetic on the failure arms in down, where the board actively hides that something went wrong.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions