Skip to content

Show container start guidance on more commands#168

Open
gtsiolis wants to merge 1 commit intomainfrom
george/des-187
Open

Show container start guidance on more commands#168
gtsiolis wants to merge 1 commit intomainfrom
george/des-187

Conversation

@gtsiolis
Copy link
Member

Show actionable Docker errors consistently across all commands

When Docker isn't available, status, stop, and logs now all show a clear, styled error message with actionable next steps — regardless of whether the terminal is interactive or not.

Previously the error guidance was only shown in non-interactive mode (piped output), so running lstk status in a terminal with Docker stopped produced no visible output. The plain formatter also rendered errors as unstyled text even when colors were available.

BEFORE AFTER
Screenshot 2026-03-25 at 01 45 05 Screenshot 2026-03-25 at 01 42 28

@coderabbitai
Copy link

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

The PR adds early runtime health checks to the logs, stop, and status commands to fail immediately if Docker is unavailable, and refactors error output formatting to use styled icons (✗ marker) and blockquote-style summaries instead of plain text prefixes.

Changes

Cohort / File(s) Summary
Runtime Health Checks in Commands
cmd/logs.go, cmd/stop.go
Added checkRuntimeHealth() calls after Docker runtime creation and before config loading to enable early failure detection.
Health Check Helper & Status Command Error Handling
cmd/root.go, cmd/status.go
root.go introduces new checkRuntimeHealth() helper that invokes rt.IsHealthy() and emits errors via output sink, returning a silent error wrapper. status.go adds user-facing error emission for Docker runtime creation failures with actionable links.
Error Output Formatting & Tests
internal/output/plain_format.go, internal/output/plain_format_test.go, internal/output/plain_sink_test.go
Changed error event rendering from plain-text Error: prefixes to styled icons (bold red ✗), blockquote-style summaries (> ...), and revised action rendering with Lipgloss styling; corresponding test assertions updated to match new output format.

Sequence Diagram

sequenceDiagram
    participant Cmd as Command (logs/stop)
    participant RT as Docker Runtime
    participant HC as Health Checker
    participant Out as Output Sink
    participant Err as Error Handler

    Cmd->>RT: NewDockerRuntime(cfg)
    RT-->>Cmd: runtime instance
    
    Cmd->>HC: checkRuntimeHealth(ctx, rt, cfg)
    HC->>RT: IsHealthy(ctx)
    
    alt Runtime is Unhealthy
        RT-->>HC: error
        HC->>RT: EmitUnhealthyError(error)
        RT->>Out: emit styled error event
        Out-->>Out: render with ✗ icon & blockquote
        HC-->>Cmd: SilentError(wrapped)
        Cmd->>Err: abort early
    else Runtime is Healthy
        RT-->>HC: nil
        HC-->>Cmd: nil
        Cmd->>Cmd: proceed to load config
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • carole-lavillonniere
  • silv-io
  • anisaoshafi
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Show container start guidance on more commands' directly reflects the PR's main objective of displaying Docker error guidance consistently across additional commands (status, stop, logs).
Description check ✅ Passed The description clearly explains the change: showing actionable Docker errors consistently across commands with before/after comparison and visual examples of improved error formatting.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch george/des-187

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
cmd/root.go (1)

188-197: Comment is misleading and cfg parameter is unused.

The comment states errors are emitted "if not in interactive mode," but the implementation always emits to PlainSink(os.Stdout) regardless of mode—which aligns with the PR's goal of showing errors consistently. However, this makes the cfg parameter unused and the comment inaccurate.

Consider either:

  1. Removing cfg from the signature and updating the comment to reflect actual behavior, or
  2. If future differentiation is planned, add a TODO comment.
✏️ Suggested comment fix (if cfg removal is desired)
-// checkRuntimeHealth checks if the runtime is healthy and emits an error
-// through the sink if not in interactive mode. Returns a SilentError to
-// suppress duplicate error printing.
-func checkRuntimeHealth(ctx context.Context, rt runtime.Runtime, cfg *env.Env) error {
+// checkRuntimeHealth checks if the runtime is healthy and emits an error
+// through a plain sink. Returns a SilentError to suppress duplicate error printing.
+func checkRuntimeHealth(ctx context.Context, rt runtime.Runtime) error {
 	if err := rt.IsHealthy(ctx); err != nil {
 		rt.EmitUnhealthyError(output.NewPlainSink(os.Stdout), err)
 		return output.NewSilentError(err)
 	}
 	return nil
 }

This would also require updating call sites in logs.go, stop.go, and status.go.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/root.go` around lines 188 - 197, The function checkRuntimeHealth has an
unused cfg parameter and an inaccurate comment; remove the cfg *env.Env*
parameter from checkRuntimeHealth's signature, update its comment to state it
always emits the runtime unhealthy error to a PlainSink(os.Stdout) and returns a
SilentError, and then update every call site of checkRuntimeHealth to stop
passing cfg (i.e., adjust callers to call checkRuntimeHealth(ctx, rt)); run
tests/build to ensure no remaining references.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/status.go`:
- Around line 26-40: The NewDockerRuntime failure handling in status.go is
inconsistent with logs.go and stop.go and omits the OS-specific action provided
by EmitUnhealthyError; extract a shared helper (e.g., initAndCheckRuntime or
EnsureHealthyRuntime) that encapsulates runtime.NewDockerRuntime(cfg.DockerHost)
plus the health-check via checkRuntimeHealth and when NewDockerRuntime returns
an error calls the same styled EmitUnhealthyError/EmitError path used elsewhere
(mirroring EmitUnhealthyError's actions and message), then replace the
NewDockerRuntime + checkRuntimeHealth logic in status.go, logs.go, and stop.go
with calls to this helper so all commands present identical, OS-aware error
output.

In `@internal/output/plain_format.go`:
- Around line 153-164: The test suite lacks assertions verifying visual styling
differences between the first and subsequent actions; update
TestErrorDisplay_MultiActionRenders to assert that the rendered output contains
styling markers (or ANSI sequences) produced by errorActionStyle and
errorValueStyle for the first action and that subsequent actions include the
errorMutedStyle output (e.g., by checking for the specific ANSI color/bold
sequences or unique style markers applied by errorActionStyle/errorValueStyle vs
errorMutedStyle in the output string derived from rendering e.Actions); locate
rendering logic around e.Actions in plain_format.go (errorActionStyle,
errorValueStyle, errorMutedStyle) and enhance the test to explicitly assert the
first action uses the action+value styles while later actions use the muted
style.

---

Nitpick comments:
In `@cmd/root.go`:
- Around line 188-197: The function checkRuntimeHealth has an unused cfg
parameter and an inaccurate comment; remove the cfg *env.Env* parameter from
checkRuntimeHealth's signature, update its comment to state it always emits the
runtime unhealthy error to a PlainSink(os.Stdout) and returns a SilentError, and
then update every call site of checkRuntimeHealth to stop passing cfg (i.e.,
adjust callers to call checkRuntimeHealth(ctx, rt)); run tests/build to ensure
no remaining references.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 04e4a18d-8ab3-4aaa-bfd5-9c04aa757e7b

📥 Commits

Reviewing files that changed from the base of the PR and between 273738e and 6e9c0d7.

📒 Files selected for processing (7)
  • cmd/logs.go
  • cmd/root.go
  • cmd/status.go
  • cmd/stop.go
  • internal/output/plain_format.go
  • internal/output/plain_format_test.go
  • internal/output/plain_sink_test.go

Comment on lines 26 to 40
rt, err := runtime.NewDockerRuntime(cfg.DockerHost)
if err != nil {
output.EmitError(output.NewPlainSink(os.Stdout), output.ErrorEvent{
Title: "Docker is not available",
Summary: err.Error(),
Actions: []output.ErrorAction{
{Label: "See help:", Value: "lstk -h"},
{Label: "Install Docker:", Value: "https://docs.docker.com/get-docker/"},
},
})
return output.NewSilentError(err)
}
if err := checkRuntimeHealth(cmd.Context(), rt, cfg); err != nil {
return err
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent error handling between commands and duplicate logic.

status.go now handles NewDockerRuntime failures with a styled error, but logs.go and stop.go return the raw error for the same failure case. This creates inconsistent UX—users get a styled error from lstk status but a plain error from lstk logs when Docker client initialization fails.

Additionally, the error emitted here (lines 28-35) differs from EmitUnhealthyError (used by checkRuntimeHealth): this version omits the OS-specific "Start Docker Desktop" action that EmitUnhealthyError provides.

Consider:

  1. Applying the same NewDockerRuntime error handling to logs.go and stop.go, or
  2. Extracting a shared helper that handles both runtime creation and health check consistently.
🔧 One possible approach: reuse EmitUnhealthyError pattern
 		rt, err := runtime.NewDockerRuntime(cfg.DockerHost)
 		if err != nil {
-			output.EmitError(output.NewPlainSink(os.Stdout), output.ErrorEvent{
-				Title:   "Docker is not available",
-				Summary: err.Error(),
-				Actions: []output.ErrorAction{
-					{Label: "See help:", Value: "lstk -h"},
-					{Label: "Install Docker:", Value: "https://docs.docker.com/get-docker/"},
-				},
-			})
-			return output.NewSilentError(err)
+			// Create a minimal runtime just for error emission, or inline the error event
+			sink := output.NewPlainSink(os.Stdout)
+			output.EmitError(sink, output.ErrorEvent{
+				Title:   "Docker is not available",
+				Summary: fmt.Sprintf("cannot initialize Docker client: %v", err),
+				Actions: dockerInstallActions(), // shared helper with OS-specific actions
+			})
+			return output.NewSilentError(err)
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/status.go` around lines 26 - 40, The NewDockerRuntime failure handling in
status.go is inconsistent with logs.go and stop.go and omits the OS-specific
action provided by EmitUnhealthyError; extract a shared helper (e.g.,
initAndCheckRuntime or EnsureHealthyRuntime) that encapsulates
runtime.NewDockerRuntime(cfg.DockerHost) plus the health-check via
checkRuntimeHealth and when NewDockerRuntime returns an error calls the same
styled EmitUnhealthyError/EmitError path used elsewhere (mirroring
EmitUnhealthyError's actions and message), then replace the NewDockerRuntime +
checkRuntimeHealth logic in status.go, logs.go, and stop.go with calls to this
helper so all commands present identical, OS-aware error output.

Comment on lines +153 to 164
if len(e.Actions) > 0 {
sb.WriteString("\n")
for i, action := range e.Actions {
sb.WriteString("\n")
if i > 0 {
sb.WriteString(errorMutedStyle.Render(ErrorActionPrefix + action.Label + " " + action.Value))
} else {
sb.WriteString(errorActionStyle.Render(ErrorActionPrefix+action.Label+" "))
sb.WriteString(errorValueStyle.Render(action.Value))
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there are any tests covering multiple ErrorActions
rg -n 'Actions:.*ErrorAction.*ErrorAction' --type go
# Also check for tests with Actions array having more than one element
rg -n 'Actions: \[\]output\.ErrorAction\{' -A5 --type go | head -50

Repository: localstack/lstk

Length of output: 1684


🏁 Script executed:

cat -n internal/ui/components/error_display_test.go | head -100

Repository: localstack/lstk

Length of output: 3811


🏁 Script executed:

cat -n internal/output/plain_format.go | sed -n '153,164p'

Repository: localstack/lstk

Length of output: 524


🏁 Script executed:

rg -n 'errorActionStyle\|errorValueStyle\|errorMutedStyle' --type go -B2 -A2

Repository: localstack/lstk

Length of output: 41


🏁 Script executed:

rg -n 'errorMutedStyle|errorActionStyle|errorValueStyle' -B3 -A3

Repository: localstack/lstk

Length of output: 1930


🏁 Script executed:

fd 'error_display.go|plain_format.go' --type f

Repository: localstack/lstk

Length of output: 130


🏁 Script executed:

cat -n internal/output/plain_format.go | sed -n '1,50p'

Repository: localstack/lstk

Length of output: 1700


Add test coverage to validate the visual styling differentiation for multiple actions.

The code implements distinct styling: the first action renders with errorActionStyle (color) and errorValueStyle (bold), while subsequent actions use errorMutedStyle (muted grey). The existing test TestErrorDisplay_MultiActionRenders covers multiple actions but only validates content presence via strings.Contains(), not the styling output.

Add a test assertion that validates the rendered output distinguishes the first action visually from subsequent ones to document this intentional design.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/output/plain_format.go` around lines 153 - 164, The test suite lacks
assertions verifying visual styling differences between the first and subsequent
actions; update TestErrorDisplay_MultiActionRenders to assert that the rendered
output contains styling markers (or ANSI sequences) produced by errorActionStyle
and errorValueStyle for the first action and that subsequent actions include the
errorMutedStyle output (e.g., by checking for the specific ANSI color/bold
sequences or unique style markers applied by errorActionStyle/errorValueStyle vs
errorMutedStyle in the output string derived from rendering e.Actions); locate
rendering logic around e.Actions in plain_format.go (errorActionStyle,
errorValueStyle, errorMutedStyle) and enhance the test to explicitly assert the
first action uses the action+value styles while later actions use the muted
style.

}
return nil
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: the cfg *env.Env param can be removed since it is unused.

},
})
return output.NewSilentError(err)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: why this special "docker not available error" handling only for the status command?

errorSecondaryStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
errorActionStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("69"))
errorValueStyle = lipgloss.NewStyle().Bold(true)
errorMutedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: do we really mean to redeclare all these, since they already exist in internal/ui/styles/styles.go?

}
if err := checkRuntimeHealth(cmd.Context(), rt, cfg); err != nil {
return err
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: it looks like the check is missing for the start command

}
if err := checkRuntimeHealth(cmd.Context(), rt, cfg); err != nil {
return err
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: we should add a new integration test case for each command to test the behavior when docker is not healthy.

sb.WriteString(errorActionStyle.Render(ErrorActionPrefix+action.Label+" "))
sb.WriteString(errorValueStyle.Render(action.Value))
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I would like to hear what @silv-io says here. We were previously not doing any styling in non-interactive mode.

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.

2 participants