Skip to content

feat(cli): prettify status output with table, links, and --json flag#490

Merged
Aureliolo merged 9 commits into
mainfrom
feat/cli-status-ux
Mar 16, 2026
Merged

feat(cli): prettify status output with table, links, and --json flag#490
Aureliolo merged 9 commits into
mainfrom
feat/cli-status-ux

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

@Aureliolo Aureliolo commented Mar 16, 2026

Summary

  • Parse docker compose ps JSON into a formatted table with service, state icon, health, image tag, and status columns
  • Add --json flag to synthorg status for raw JSON output (default is human-readable table)
  • Parse health check response into a one-line summary instead of raw JSON dump
  • Add dashboard, API docs, and health check links to status output
  • Fully styled doctor output with colored sections, per-container health status (green/red), clean config display
  • Add synthorg doctor report subcommand — generates diagnostic archive + pre-filled GitHub issue URL with environment table and container summary
  • Add release notes link to update after successful CLI update
  • Refactor all CLI commands to use *ui.UI for consistent styled output
  • Add UI.Table(), UI.Link(), UI.Writer(), UI.Section() methods
  • Fix web + backend Dockerfile healthchecks (localhost127.0.0.1 for IPv6 compatibility)
  • Fix flaky DepartmentFactory test (polyfactory subordinate == supervisor collision)
  • Human-readable uptime formatting (3h 36m instead of 12991s)
  • Pretty-print health JSON in diagnostic report
  • Sanitize table cells with stripControl to prevent terminal injection
  • Handle both JSON array (Compose v2.21+) and NDJSON container output
  • Fix imageTag for registry:port/image (no tag) edge case

Review coverage

Pre-reviewed by 4 agents + 3 external reviewers across multiple rounds:

  • go-reviewer: 9 findings (function length, parse failures, health logic, global var, tests)
  • docs-consistency: no findings
  • infra-reviewer: backend Dockerfile localhost issue
  • Gemini: doctor UI consistency, health icon defaults
  • CodeRabbit: Unicode table alignment, NDJSON format handling
  • Copilot: table sanitization, health status logic

Test plan

  • go vet / golangci-lint — clean
  • go test ./... — all passing
  • pytest — 8214+ passed (flaky test fixed)
  • Table-driven tests for imageTag (6 cases), healthIcon (6 cases), parseContainerJSON, formatUptime (6 cases)
  • UI tests for Table, Link, Writer, TableEmpty, Section
  • Manual: synthorg status — formatted table with icons
  • Manual: synthorg doctor — styled colored output
  • Manual: synthorg doctor report — generates valid GitHub issue URL

release-as: 0.3.0

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 16, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades the user experience of the synthorg CLI by modernizing its output. It introduces a structured, human-readable table format for the status command, alongside a new --json flag for programmatic consumption. The changes also standardize output styling across various commands using a new UI package, provide more concise health check summaries, and integrate helpful links directly into the command-line interface, making diagnostic information and navigation more accessible.

Highlights

  • Enhanced synthorg status Output: The synthorg status command now presents Docker Compose service information in a formatted table, including state icons, health, image tags, and status, significantly improving readability.
  • New --json Flag for status: A --json flag has been added to synthorg status, allowing users to retrieve raw JSON output for programmatic consumption, in addition to the default human-readable table.
  • Concise Health Check Summaries: Health check responses in synthorg status are now parsed and displayed as a concise one-line summary, replacing the previous raw JSON dump.
  • Integrated Informational Links: Dashboard, API documentation, and health check links are now directly included in the output of synthorg status and synthorg doctor, and a release notes link is added to synthorg update after successful CLI updates.
  • UI Package Refactoring: CLI commands like doctor and status have been refactored to consistently use the new *ui.UI package, ensuring a standardized and styled output experience across the application.
  • Extended UI Capabilities: The ui package has been enhanced with new Table(), Link(), and Writer() methods, providing robust tools for structured and styled command-line output.
  • Improved Doctor Bug Report Instructions: The synthorg doctor command now provides clearer instructions for filing bug reports, including steps to attach diagnostic files and a direct link to create a GitHub issue.
Changelog
  • cli/cmd/doctor.go
    • Refactored runDoctor to utilize the new ui.UI for all output.
    • Improved bug report instructions, guiding users to attach diagnostic files and providing a direct link to create a GitHub issue.
    • Added links to the Dashboard and API documentation.
  • cli/cmd/status.go
    • Implemented a --json flag to toggle between human-readable table output and raw JSON for container status and health checks.
    • Introduced containerInfo, imageTag, healthIcon, parseContainerJSON, and renderContainerTable functions to process and display Docker Compose ps output as a formatted table.
    • Refactored printHealthStatus to fetch and render a concise health summary or raw JSON.
    • Added printLinks to display Dashboard, API docs, and Health check URLs.
  • cli/cmd/status_test.go
    • Added a new test file containing unit tests for imageTag, healthIcon, and parseContainerJSON functions.
  • cli/cmd/update.go
    • Appended a release notes link to the output after a successful CLI update.
    • Added a Dashboard link to the output after containers are successfully restarted.
  • cli/internal/diagnostics/collect.go
    • Removed the raw ContainerPS dump from the FormatText method of the diagnostic report to streamline output.
  • cli/internal/diagnostics/collect_test.go
    • Updated the test for ReportFormatText to reflect the change in diagnostic report content, specifically the removal of raw container output and the expectation of a container summary.
  • cli/internal/ui/ui.go
    • Introduced Writer() method to expose the underlying io.Writer.
    • Added Link() method to print labeled URLs with muted styling.
    • Implemented Table() method to render data in a fixed-width, header-separated table format.
  • cli/internal/ui/ui_test.go
    • Added new test cases for the Link(), Table(), TableEmpty(), and Writer() methods to ensure their correct functionality.
Activity
  • The PR was pre-reviewed by two agents (go-reviewer, docs-consistency), leading to 9 findings being addressed.
  • Specific findings addressed include splitting long functions, warning on NDJSON parse failures, conditional display of Persistence key-value, threading --json as a parameter, guarding against zero-value health data, adding tests for new utility functions (imageTag, healthIcon, parseContainerJSON, Table, Link), and removing dual raw/out writer parameters.
  • Comprehensive testing was performed, including go vet, go test ./..., and golangci-lint run, all passing.
  • Table-driven tests were added for imageTag (5 cases), healthIcon (6 cases), and parseContainerJSON (mixed valid/invalid).
  • UI tests were added for Table, Link, Writer, and TableEmpty.
  • Manual testing confirmed synthorg status shows a formatted table with icons and synthorg status --json shows raw JSON.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

- Parse docker compose ps JSON into a formatted table with service name,
  state icon, health, image tag, and status columns
- Add --json flag to status for raw JSON output (default is table)
- Parse health check response into a one-line summary instead of raw JSON
- Add dashboard, API docs, and health check links to status output
- Add dashboard and API docs links to doctor output
- Add release notes link to update output after successful CLI update
- Add dashboard link after successful container restart in update
- Improve doctor bug report instructions (attach diagnostic file)
- Remove raw container JSON dump from doctor FormatText (summary replaces it)
- Add UI.Table(), UI.Link(), UI.Writer() methods to the ui package
- Split printContainerStates and printHealthStatus under 50 lines
- Warn on NDJSON parse failures instead of silent skip
- Only show Persistence on healthy (2xx) responses
- Thread --json flag as parameter instead of global var
- Guard against zero-value health data
- Refactor doctor.go to use *ui.UI for consistent styled output
- Add tests for imageTag, healthIcon, parseContainerJSON, Table, Link
- Remove dual raw/out writer params (use out.Writer())
- Preallocate rows slice
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 16, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 323b4d85-a149-4930-86f1-c4411551429c

📥 Commits

Reviewing files that changed from the base of the PR and between 9c94489 and b84803d.

📒 Files selected for processing (2)
  • cli/cmd/doctor.go
  • cli/cmd/status_test.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Formatted CLI UI: sections, labeled links, fixed-width tables, and writer access; post-diagnostic hints and Links (Dashboard/API) shown.
    • New --json option for status for machine-readable output.
    • Separate diagnostic-report command that saves an archive and prints a pre-filled report URL and save feedback.
    • Status now shows container health, uptime, resource info, and dashboard/API links.
  • Bug Fixes

    • Healthchecks updated to use loopback IP for improved reliability.
  • Tests

    • Added unit tests for status helpers and UI rendering.
  • Chores

    • Updated dependencies and minor Docker healthcheck tweaks.

Walkthrough

Replaces direct CLI stdout with a UI abstraction across commands (doctor, status, update), adds UI rendering APIs (Section, Link, Table, Writer), introduces a doctor report subcommand, improves status health/container parsing with optional JSON output, adjusts diagnostics formatting, updates Docker healthchecks, and adds tests and deps.

Changes

Cohort / File(s) Summary
CLI commands (UI migration & features)
cli/cmd/doctor.go, cli/cmd/status.go, cli/cmd/update.go, cli/cmd/doctor_report.go
Replaced fmt/io.Writer prints with *ui.UI methods (Step, KeyValue, Section, Link, Warn, Success); added --json flag for status; updated runDoctor signature; added doctor report subcommand generating diagnostic archive and pre-filled issue URL; added dashboard/release links after update.
Status helpers & tests
cli/cmd/status_test.go
Added unit tests for imageTag, healthIcon, parseContainerJSON (NDJSON & array), and formatUptime covering edge cases and malformed inputs.
UI package & tests
cli/internal/ui/ui.go, cli/internal/ui/ui_test.go
Added Writer() io.Writer, Section(string), Link(label,url), and Table(headers,rows) with sanitization and runewidth-aware column sizing; corresponding tests for link, table, empty table, and Writer.
Diagnostics collection & tests
cli/internal/diagnostics/collect.go, cli/internal/diagnostics/collect_test.go
Improved health-body handling (pretty-print JSON when possible, conditional truncation), removed textual Containers/Recent Logs from formatted report, and added/used ContainerSummary []ContainerDetail in tests.
Docker healthchecks
docker/backend/Dockerfile, docker/web/Dockerfile
Changed Dockerfile healthcheck probe targets from localhost to 127.0.0.1.
Build / deps
cli/go.mod
Added direct dependency on go-runewidth (adjusted indirect entries).
Other tests
tests/unit/core/conftest.py
Added DepartmentFactory.reporting_lines attribute (empty tuple) for tests.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant UI
  participant Backend
  participant FS

  CLI->>UI: start command (status / doctor / doctor report)
  CLI->>Backend: GET /containers (NDJSON) and GET /health
  Backend-->>CLI: container lines, health JSON
  CLI->>CLI: parseContainerJSON(), fetchHealth(), renderHealthSummary()
  alt doctor_report
    CLI->>FS: save diagnostics archive
    FS-->>CLI: saved path
  end
  CLI->>UI: render Section, Table, Link, Key/Value, Hints (or raw JSON)
  UI-->>User: formatted output / links / JSON
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and concisely describes the main changes: prettifying status output by adding table formatting, links, and a --json flag, which are core features across multiple files in this PR.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering all major modifications including status output improvements, doctor command updates, UI refactoring, Dockerfile fixes, and test coverage details.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/cli-status-ux
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/cli-status-ux
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request significantly refactors the CLI's output and diagnostic commands by introducing a new ui package for standardized user interface elements. The doctor command now uses the new UI for collecting and presenting diagnostics, including generating GitHub issue URLs and displaying relevant links. The status command has been enhanced with a --json flag for raw output, and its display of Docker container states and health checks has been revamped to use structured tables and more detailed summaries, supported by new helper functions and unit tests. Additionally, the update command now provides release notes and dashboard links post-update. Review comments suggest improving consistency in doctor.go by reusing an existing UI instance for error output, refining the default icon for unknown container health states in status.go, enhancing health check error messages for better debugging, and simplifying a conditional check in the health status rendering logic.

Comment thread cli/cmd/doctor.go Outdated
Comment on lines +49 to +50
errOut := ui.NewUI(cmd.ErrOrStderr())
errOut.Warn(fmt.Sprintf("Could not save diagnostic file: %v", err))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

It would be more readable to use the out variable instead of creating a new ui.UI instance. This aligns with the existing pattern in this function and promotes consistency.

Suggested change
errOut := ui.NewUI(cmd.ErrOrStderr())
errOut.Warn(fmt.Sprintf("Could not save diagnostic file: %v", err))
errOut := out
errOut.Warn(fmt.Sprintf("Could not save diagnostic file: %v", err))

Comment thread cli/cmd/status.go
if state == "restarting" {
return ui.IconWarning
}
return ui.IconError
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Returning IconError as a default case might be misleading. Consider returning an empty string or a more descriptive icon to indicate an unknown state.

Suggested change
return ui.IconError
return "" // Or a more appropriate icon for unknown state

Comment thread cli/cmd/status.go
body, statusCode, err := fetchHealth(ctx, state.BackendPort)
if err != nil {
_, _ = fmt.Fprintf(out, " Backend: error creating request (%v)\n", err)
out.Error(err.Error())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Returning after printing the error makes sense, but the error message itself could be improved. Consider including the specific error from err.Error() in the output for better debugging.

Suggested change
out.Error(err.Error())
out.Error(fmt.Sprintf("Health check failed: %v", err))

Comment thread cli/cmd/status.go
Comment on lines +242 to 244
if json.Unmarshal(body, &envelope) != nil || envelope.Data.Status == "" {
out.Warn(fmt.Sprintf("Health: unparseable response (HTTP %d)", statusCode))
return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The condition envelope.Data.Status == "" seems redundant after checking json.Unmarshal(body, &envelope) != nil. If unmarshalling fails, envelope.Data.Status will likely be an empty string anyway. Consider simplifying the condition.

Suggested change
if json.Unmarshal(body, &envelope) != nil || envelope.Data.Status == "" {
out.Warn(fmt.Sprintf("Health: unparseable response (HTTP %d)", statusCode))
return
if json.Unmarshal(body, &envelope) != nil {
out.Warn(fmt.Sprintf("Health: unparseable response (HTTP %d)", statusCode))
return
}

Copy link
Copy Markdown
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/internal/ui/ui.go`:
- Around line 121-161: The table alignment uses len() (byte length) to compute
column widths in UI.Table and then formats with fmt.Fprintf, which mismeasures
multibyte Unicode (e.g., ✓). Replace byte-length math with terminal display
width: import github.com/mattn/go-runewidth and use runewidth.StringWidth(...)
when computing widths (in the headers loop and when scanning rows into widths)
and use runewidth.FillRight(cell, w) (or runewidth.FillLeft/Fill) when emitting
each cell inside printRow so the printed cell occupies the desired display
width; also build the separator using strings.Repeat("─", w) after widths are
computed. Update references: widths slice, UI.Table, printRow, sep.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6ebf4b6c-268f-4a31-abad-ddb01daa57ac

📥 Commits

Reviewing files that changed from the base of the PR and between ba13e04 and 586c93f.

📒 Files selected for processing (8)
  • cli/cmd/doctor.go
  • cli/cmd/status.go
  • cli/cmd/status_test.go
  • cli/cmd/update.go
  • cli/internal/diagnostics/collect.go
  • cli/internal/diagnostics/collect_test.go
  • cli/internal/ui/ui.go
  • cli/internal/ui/ui_test.go
💤 Files with no reviewable changes (1)
  • cli/internal/diagnostics/collect.go
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Lint CLI Go code with golangci-lint and go vet; test with go test -race; check vulnerabilities with govulncheck

Files:

  • cli/internal/ui/ui.go
  • cli/internal/diagnostics/collect_test.go
  • cli/internal/ui/ui_test.go
  • cli/cmd/doctor.go
  • cli/cmd/status_test.go
  • cli/cmd/update.go
  • cli/cmd/status.go
🧠 Learnings (1)
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • cli/cmd/doctor.go
  • cli/cmd/update.go
  • cli/cmd/status.go
🧬 Code graph analysis (5)
cli/internal/diagnostics/collect_test.go (1)
cli/internal/diagnostics/collect.go (1)
  • ContainerDetail (24-29)
cli/internal/ui/ui_test.go (1)
cli/internal/ui/ui.go (1)
  • NewUI (57-69)
cli/cmd/doctor.go (2)
cli/internal/ui/ui.go (1)
  • NewUI (57-69)
cli/internal/diagnostics/collect.go (2)
  • Collect (56-80)
  • Report (32-53)
cli/cmd/status_test.go (1)
cli/internal/ui/ui.go (4)
  • IconSuccess (26-26)
  • IconError (35-35)
  • IconInProgress (29-29)
  • IconWarning (32-32)
cli/cmd/status.go (1)
cli/internal/ui/ui.go (6)
  • NewUI (57-69)
  • UI (43-52)
  • IconSuccess (26-26)
  • IconError (35-35)
  • IconInProgress (29-29)
  • IconWarning (32-32)
🔇 Additional comments (18)
cli/internal/ui/ui.go (2)

71-72: LGTM!

Simple accessor exposing the underlying writer for direct output when needed.


116-119: LGTM!

The Link method properly sanitizes both label and URL using stripControl to prevent terminal escape sequence injection.

cli/internal/ui/ui_test.go (1)

58-105: LGTM!

Good test coverage for the new UI methods. Tests verify:

  • Link renders label and URL correctly
  • Table produces header, rows, and separator
  • Empty headers produce no output
  • Writer() returns the underlying buffer
cli/cmd/update.go (2)

83-85: LGTM!

Release notes URL is correctly constructed. The TrimPrefix ensures consistent tag formatting regardless of whether the version string includes a "v" prefix.


291-291: LGTM!

Dashboard link provides a convenient clickable URL after successful restart.

cli/internal/diagnostics/collect_test.go (1)

43-45: LGTM!

Test correctly updated to use the new ContainerSummary field with ContainerDetail entries, and validates the renamed "Container Summary" section in the formatted output.

Also applies to: 53-53

cli/cmd/status.go (8)

17-17: LGTM!

Clean integration of the UI abstraction and --json flag. The ignored error from GetBool is acceptable since the flag was registered in the same init() function.

Also applies to: 29-29, 36-36, 43-43


91-97: LGTM!

imageTag correctly extracts the tag portion after the last colon, with a sensible fallback returning the full string if no colon is present.


99-114: LGTM!

healthIcon provides clear visual indicators for container states with appropriate icon mappings. The fallback to IconError for unknown states is a safe default.


116-133: LGTM!

Good implementation with failure counting for parse warnings. Empty lines are properly skipped, and the caller receives both parsed containers and failure count to decide whether to warn the user.


135-151: LGTM!

Row slice is preallocated with correct capacity. Health label defaults to "-" when empty for consistent table rendering.


219-236: LGTM!

fetchHealth implements proper context propagation, reasonable timeout (5s), and response body limiting (64KB) to prevent memory exhaustion from large/malicious responses.


238-257: LGTM!

renderHealthSummary correctly guards against zero-value health data by checking both unmarshal success and non-empty status. Persistence is only displayed for healthy 2xx responses as intended.


259-264: LGTM!

Clean link output for dashboard, API docs, and health endpoints.

cli/cmd/status_test.go (1)

9-70: LGTM!

Comprehensive test coverage for the new helper functions:

  • TestImageTag covers various image string formats including edge cases
  • TestHealthIcon validates all state/health combinations
  • TestParseContainerJSON verifies both successful parsing and failure counting
cli/cmd/doctor.go (3)

28-31: LGTM!

Clean refactor to use the UI abstraction. The unused args parameter is properly marked with _.


48-53: LGTM!

Proper separation of stdout/stderr using distinct UI instances. Success messages go to stdout, warnings to stderr.


71-99: LGTM!

Well-implemented URL builder with:

  • Proper URL encoding via url.QueryEscape
  • Conservative URL length guard (3500 chars) with graceful fallback to shorter body
  • Correct label encoding (%%3A produces %3A which decodes to :)

- Fix web Dockerfile healthcheck: localhost → 127.0.0.1 (localhost
  doesn't resolve in Alpine containers, causing permanent "unhealthy")
- Pretty-print health JSON in doctor diagnostic report instead of
  dumping raw single-line JSON
- Format uptime as human-readable "3h 36m" instead of raw "12991s"
- Add formatUptime test (6 cases)
- Use runewidth.StringWidth for Table column alignment (fixes Unicode
  icon misalignment with multi-byte chars like ✓, ✗, ●)
- Handle both JSON array (Compose v2.21+) and NDJSON in container
  parsing — prevents "No containers running" on newer Compose
- Fix imageTag for registry:port/image (no tag) — guard against port
  colon being mistaken for tag separator
- Fix backend Dockerfile healthcheck: localhost → 127.0.0.1 (same
  IPv6 resolution issue as web, now all 3 Dockerfiles consistent)
- Remove unnecessary ui.NewUI allocation in doctor error path
- Restore docker stats error warning (was silently swallowed)
- Add test case for tagless registry image
- Fix DepartmentFactory flaky test: set reporting_lines=() to prevent
  polyfactory from generating random ReportingLine with subordinate ==
  supervisor (confirmed stable over 50 runs)
- Move Recent Logs section to top of doctor diagnostic report so raw
  logs appear before the structured summary sections
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.36%. Comparing base (ba13e04) to head (b84803d).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #490   +/-   ##
=======================================
  Coverage   93.36%   93.36%           
=======================================
  Files         480      480           
  Lines       23202    23202           
  Branches     2215     2215           
=======================================
  Hits        21662    21662           
  Misses       1216     1216           
  Partials      324      324           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- Replace plain-text doctor dump with styled *ui.UI rendering:
  colored section headers, green/red per-container health status,
  clean config key-value display, no raw JSON
- Remove logs from doctor terminal output (use 'synthorg logs')
- Add UI.Section() method for bold section headers
- Add 'synthorg doctor report' subcommand for bug reporting:
  collects diagnostics, saves archive, generates full GitHub issue
  URL with environment table, container summary, persistence/memory
  backend, image tag, and sandbox config
- Fix flaky DepartmentFactory test (polyfactory generating
  subordinate == supervisor collision)
Comment thread cli/cmd/doctor_report.go Dismissed
Copy link
Copy Markdown
Contributor

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/cmd/status.go (1)

43-68: ⚠️ Potential issue | 🔴 Critical

--json mode still writes human-formatted output.

This path always emits version info, Docker/Compose key-values, resource usage, health text, and links through the UI layer. That makes stdout invalid JSON and breaks the new flag’s machine-readable contract.

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

In `@cli/cmd/status.go` around lines 43 - 68, The status command always emits
human-readable UI output which breaks --json; update cmd/status.go to detect
jsonOut and short-circuit to JSON-only output: after determining safeDir and
detecting docker (functions/symbols: safeStateDir, docker.Detect), if jsonOut is
true, do not call ui.NewUI or the human printers (printVersionInfo, out.KeyValue
prints, printContainerStates, printResourceUsage, printHealthStatus,
printLinks); instead gather the same data those functions produce and marshal a
single JSON object to stdout (or invoke existing JSON-capable helpers if
available) and return, ensuring no other human-formatted text is written to
stdout.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/cmd/doctor_report.go`:
- Line 20: The command description in the Cobra command's Long field currently
states it "opens a pre-filled GitHub issue URL in the browser" but the
implementation only prints the URL; update the Long string in
cli/cmd/doctor_report.go (the Cobra command's Long field for this command) to
accurately say it prints or outputs a pre-filled GitHub issue URL (e.g., "prints
a pre-filled GitHub issue URL to the console") so the description matches
behavior.

In `@cli/cmd/doctor.go`:
- Around line 109-119: The code copies c.State into health when c.Health is
empty, causing outputs like "running (running)"; change the logic so health
remains empty if c.Health is empty (do not assign c.State into health), and
update the rendering: keep the existing cases for health == "healthy" and for
unhealthy/exited, but in the default branch when health == "" print only c.State
(e.g., out.Warn(fmt.Sprintf("%-24s %s", c.Name, c.State))) and when health is
non-empty keep the current "%-24s %s (%s)" format; adjust the switch/conditions
around the health variable and the out.Warn/out.Error/out.Success calls
(references: variable health, fields c.Health and c.State, and the
out.Warn/out.Error/out.Success calls).

In `@cli/cmd/status_test.go`:
- Around line 61-69: The test currently indexes containers[0] after a non-fatal
length check; change the length assertion to a fatal assertion (use
t.Fatalf/t.Fatal) or otherwise guard the access so that parseContainerJSON
failure cannot cause a panic when reading containers[0]; update the test around
parseContainerJSON, the len(containers) check and the subsequent access to
containers[0].Service to ensure the test aborts immediately on zero-length
results.

In `@cli/cmd/status.go`:
- Around line 172-175: The jsonOut branch currently prints psOut verbatim which
can be NDJSON on older Compose versions; instead call parseContainerJSON(psOut)
to parse NDJSON (or single JSON) into a canonical Go structure and then
re-encode that structure as a single JSON value before writing to w (replace the
fmt.Fprintln(w, psOut) path). Ensure you handle parse errors from
parseContainerJSON and return or log them appropriately, and keep the
surrounding output logic (the "Containers:" header) consistent with the JSON
output path.
- Around line 255-267: The current logic treats any 2xx HTTP response as healthy
regardless of the health payload; change the success condition to require both a
2xx statusCode and a healthy payload by checking envelope.Data.Status
(hr.Status) instead of only HTTP code. Update the conditional around out.Success
and the Persistence display so it only runs when statusCode is 2xx AND hr.Status
indicates healthy (e.g., strings.EqualFold(hr.Status, "healthy") or the expected
success token used by the backend); otherwise fall through to the existing
else/unhealthy branch. Ensure the earlier unmarshal/presence check still runs
(json.Unmarshal into envelope and verify hr.Status exists) and that Persistence
is only printed from inside the new combined success block (reference variables:
envelope, hr, hr.Status, statusCode, out.Success, out.KeyValue).

In `@cli/internal/ui/ui.go`:
- Around line 127-174: The Table method writes raw header and cell strings
allowing terminal control injection; sanitize all header and cell values with
the existing stripControl function before measuring widths and before printing.
Specifically, inside UI.Table create local sanitized copies (e.g., sanHeaders
and sanRows) by calling stripControl on each header and each cell in rows, use
those sanitized values when computing widths (the widths slice) and in the
printRow closure (and when building sep), and leave the original input slices
unmodified; ensure every place that currently reads headers or row cells uses
the sanitized versions so no control characters are emitted.

---

Outside diff comments:
In `@cli/cmd/status.go`:
- Around line 43-68: The status command always emits human-readable UI output
which breaks --json; update cmd/status.go to detect jsonOut and short-circuit to
JSON-only output: after determining safeDir and detecting docker
(functions/symbols: safeStateDir, docker.Detect), if jsonOut is true, do not
call ui.NewUI or the human printers (printVersionInfo, out.KeyValue prints,
printContainerStates, printResourceUsage, printHealthStatus, printLinks);
instead gather the same data those functions produce and marshal a single JSON
object to stdout (or invoke existing JSON-capable helpers if available) and
return, ensuring no other human-formatted text is written to stdout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d7e65c9d-e8f4-4f4e-8873-c2a0373e9e27

📥 Commits

Reviewing files that changed from the base of the PR and between 586c93f and 2823797.

📒 Files selected for processing (13)
  • cli/cmd/doctor.go
  • cli/cmd/doctor_report.go
  • cli/cmd/status.go
  • cli/cmd/status_test.go
  • cli/cmd/update.go
  • cli/go.mod
  • cli/internal/diagnostics/collect.go
  • cli/internal/diagnostics/collect_test.go
  • cli/internal/ui/ui.go
  • cli/internal/ui/ui_test.go
  • docker/backend/Dockerfile
  • docker/web/Dockerfile
  • tests/unit/core/conftest.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Lint CLI Go code with golangci-lint and go vet; test with go test -race; check vulnerabilities with govulncheck

Files:

  • cli/internal/diagnostics/collect_test.go
  • cli/internal/diagnostics/collect.go
  • cli/internal/ui/ui.go
  • cli/cmd/doctor.go
  • cli/cmd/status.go
  • cli/cmd/update.go
  • cli/internal/ui/ui_test.go
  • cli/cmd/doctor_report.go
  • cli/cmd/status_test.go
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do not use from __future__ import annotations — Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) per PEP 758 — ruff enforces this on Python 3.14
Add type hints to all public functions and classes; enforce mypy strict mode
Use Google-style docstrings on all public classes and functions — required and enforced by ruff D rules
Keep line length to 88 characters (ruff enforced)

Files:

  • tests/unit/core/conftest.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Mark unit tests with @pytest.mark.unit, integration tests with @pytest.mark.integration, e2e tests with @pytest.mark.e2e, and slow tests with @pytest.mark.slow
Use asyncio_mode = "auto" in pytest configuration — no manual @pytest.mark.asyncio needed on async tests
Set 30-second timeout per test
Prefer @pytest.mark.parametrize for testing similar cases
Use Hypothesis for property-based testing in Python (@given + @settings); use ci profile (200 examples, default) and dev profile (1000 examples) controlled via HYPOTHESIS_PROFILE env var
Never skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; for timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic

Files:

  • tests/unit/core/conftest.py
cli/go.mod

📄 CodeRabbit inference engine (CLAUDE.md)

CLI requires Go 1.26+; dependencies in cli/go.mod (Cobra, charmbracelet/huh, charmbracelet/lipgloss)

Files:

  • cli/go.mod
🧠 Learnings (5)
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • cli/internal/ui/ui.go
  • cli/cmd/doctor.go
  • cli/cmd/status.go
  • cli/cmd/update.go
  • cli/go.mod
📚 Learning: 2026-03-16T07:36:16.026Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:36:16.026Z
Learning: Applies to cli/go.mod : CLI requires Go 1.26+; dependencies in `cli/go.mod` (Cobra, charmbracelet/huh, charmbracelet/lipgloss)

Applied to files:

  • cli/cmd/update.go
  • cli/go.mod
📚 Learning: 2026-03-16T07:36:16.026Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:36:16.026Z
Learning: Applies to cli/**/*.go : Lint CLI Go code with golangci-lint and go vet; test with go test -race; check vulnerabilities with govulncheck

Applied to files:

  • cli/cmd/update.go
  • cli/go.mod
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Dependabot: auto-updates Docker image digests and versions daily.

Applied to files:

  • docker/backend/Dockerfile
  • docker/web/Dockerfile
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to docker/{Dockerfile*,compose.yml} : Docker: Backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web uses nginxinc/nginx-unprivileged, Vue 3 SPA with PrimeVue + Tailwind CSS, SPA routing, API/WebSocket proxy to backend.

Applied to files:

  • docker/backend/Dockerfile
  • docker/web/Dockerfile
🧬 Code graph analysis (4)
cli/internal/diagnostics/collect_test.go (1)
cli/internal/diagnostics/collect.go (1)
  • ContainerDetail (24-29)
cli/cmd/update.go (1)
cli/internal/version/version.go (1)
  • RepoURL (5-5)
cli/internal/ui/ui_test.go (1)
cli/internal/ui/ui.go (1)
  • NewUI (58-70)
cli/cmd/doctor_report.go (4)
cli/internal/ui/ui.go (1)
  • NewUI (58-70)
cli/internal/config/state.go (1)
  • State (16-27)
cli/internal/diagnostics/collect.go (2)
  • Collect (56-80)
  • Report (32-53)
cli/internal/version/version.go (1)
  • RepoURL (5-5)
🪛 GitHub Check: CodeQL
cli/cmd/doctor_report.go

[failure] 51-51: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

🔇 Additional comments (10)
docker/web/Dockerfile (1)

38-39: Healthcheck host normalization looks good.

Using 127.0.0.1 here is deterministic and avoids localhost resolver/IPv6 ambiguity in container environments.

docker/backend/Dockerfile (1)

112-113: Backend healthcheck host change is correct.

Switching the probe to 127.0.0.1 keeps the check explicit and consistent with container-local health probing.

cli/go.mod (1)

8-12: Direct dependency promotion looks correct.

These additions are consistent with the new UI/table and update functionality and are cleanly declared as direct requirements.

tests/unit/core/conftest.py (1)

132-132: Factory update is aligned with the Department schema.

Good addition to keep generated test objects structurally complete.

cli/cmd/update.go (1)

83-84: Release-notes and dashboard link output is implemented cleanly.

The new post-update/post-restart guidance is clear and correctly formatted.

Also applies to: 291-291

cli/internal/diagnostics/collect.go (1)

127-137: Health body rendering fallback is robust.

Nice improvement: pretty JSON when possible, with safe truncation fallback when it isn’t.

cli/internal/diagnostics/collect_test.go (1)

43-45: Test expectations correctly track the new report layout.

The updated fixture data and section checks are aligned with the current FormatText output.

Also applies to: 53-53

cli/internal/ui/ui_test.go (1)

58-105: New UI helper tests add good coverage.

This gives solid regression protection for the newly introduced Link, Table, and Writer behavior.

cli/internal/ui/ui.go (1)

72-125: New UI helper methods are clean and cohesive.

Writer, Section, and Link fit the UI abstraction well and keep output routing consistent.

cli/cmd/doctor_report.go (1)

41-51: No action required. The code already implements proper path hardening—safeStateDir() calls config.SecurePath() which enforces canonicalization and absolute-path validation before the file write.

Comment thread cli/cmd/doctor_report.go Outdated
Comment thread cli/cmd/doctor.go Outdated
Comment thread cli/cmd/status_test.go
Comment thread cli/cmd/status.go
Comment thread cli/cmd/status.go
- Fix doctor_report Long field: says "prints" not "opens in browser"
- Fix doctor container display: no more "running (running)" duplication
  when health is empty — show state only without repeating
- Use t.Fatalf for container count assertion to prevent index panic
- Canonicalize --json container output: parse NDJSON/array then
  re-encode as proper JSON array instead of raw dump
- Check both HTTP status code AND hr.Status == "ok" for healthy
- Sanitize table headers and cells with stripControl before measuring
  and printing to prevent terminal control injection
Copy link
Copy Markdown
Contributor

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/cmd/doctor.go`:
- Around line 108-118: The branch handling exited containers prints c.Health
which may be empty, causing a blank status; change the logic in the switch so
the branch that matches exited containers uses a non-empty fallback (e.g., use
c.State when c.Health == "") instead of printing c.Health directly: update the
case handling (the case testing c.State == "exited" / c.Health == "unhealthy")
or compute a status variable from c.Health || c.State and pass that to out.Error
(references: c.Health, c.State, out.Error, out.Warn, out.Step).

In `@cli/cmd/status_test.go`:
- Around line 56-71: Add a new unit test to cover JSON array input for
parseContainerJSON by creating a TestParseContainerJSON_Array (or
TestParseContainerJSON_Array) in cli/cmd/status_test.go that passes a JSON array
string like `[{"Name":"a",...},{"Name":"b",...}]` into parseContainerJSON,
asserts len(containers)==2, failures==0, and verifies containers[0].Service ==
"backend"; this mirrors the existing NDJSON test (TestParseContainerJSON) to
ensure array-format output from Compose v2.21+ is parsed correctly.

In `@cli/cmd/status.go`:
- Around line 276-288: The formatUptime function doesn't handle negative
seconds; detect when seconds < 0, record the sign (neg := seconds < 0), use
math.Abs(seconds) to compute a positive duration for d/h/m calculations, then
format the result the same way and prefix a "-" when neg is true; update
references inside formatUptime (seconds, d, h, m) accordingly and add the math
import if missing.

In `@cli/internal/ui/ui.go`:
- Around line 127-187: The Table function still allows newline and tab
characters through stripControl which can break alignment; add a small sanitizer
(e.g., sanitizeCell) that replaces "\n" and "\t" with a single space and apply
it to every header and cell after stripControl in the Table method (update where
sanHeaders and sanRows are populated) so headers and cells are both stripped of
control chars and normalized for stable column widths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 31268206-3b22-4e80-9bc5-c8c612122761

📥 Commits

Reviewing files that changed from the base of the PR and between 2823797 and 7d00062.

📒 Files selected for processing (5)
  • cli/cmd/doctor.go
  • cli/cmd/doctor_report.go
  • cli/cmd/status.go
  • cli/cmd/status_test.go
  • cli/internal/ui/ui.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: CLI Test (macos-latest)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Lint CLI Go code with golangci-lint and go vet; test with go test -race; check vulnerabilities with govulncheck

Files:

  • cli/cmd/status_test.go
  • cli/internal/ui/ui.go
  • cli/cmd/doctor.go
  • cli/cmd/doctor_report.go
  • cli/cmd/status.go
🧠 Learnings (2)
📚 Learning: 2026-03-16T07:36:16.026Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:36:16.026Z
Learning: Applies to cli/**/*.go : Lint CLI Go code with golangci-lint and go vet; test with go test -race; check vulnerabilities with govulncheck

Applied to files:

  • cli/cmd/status_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • cli/internal/ui/ui.go
  • cli/cmd/doctor.go
  • cli/cmd/status.go
🧬 Code graph analysis (2)
cli/cmd/doctor.go (3)
cli/internal/ui/ui.go (2)
  • NewUI (58-70)
  • UI (44-53)
cli/internal/config/state.go (2)
  • Load (52-90)
  • State (16-27)
cli/internal/diagnostics/collect.go (2)
  • Collect (56-80)
  • Report (32-53)
cli/cmd/doctor_report.go (4)
cli/internal/ui/ui.go (1)
  • NewUI (58-70)
cli/internal/config/state.go (2)
  • Load (52-90)
  • State (16-27)
cli/internal/diagnostics/collect.go (2)
  • Collect (56-80)
  • Report (32-53)
cli/internal/version/version.go (1)
  • RepoURL (5-5)
🔇 Additional comments (12)
cli/internal/ui/ui.go (1)

72-125: LGTM!

The Writer(), Section(), and Link() methods are well-implemented with proper sanitization of user inputs via stripControl().

cli/cmd/doctor.go (2)

160-174: LGTM!

Config rendering properly masks the JWT secret and exposes only non-sensitive configuration values. The helper functions are well-structured and use the UI abstraction consistently.


27-75: LGTM!

The runDoctor function is well-structured with proper error handling, safe file permissions (0o600), and consistent use of the UI wrapper for output.

cli/cmd/doctor_report.go (2)

69-133: LGTM!

The URL builder properly escapes all dynamic values with url.QueryEscape and includes sensible truncation logic for browser URL limits. The generated bug report template is well-structured.


41-53: Path validation is properly implemented through config.SecurePath.

The static analysis flag at line 51 is a false positive. safeDir is secured by config.SecurePath(), which validates that paths are absolute and cleans them. filename is generated from time.Now().Format(), not user input. The code is safe.

cli/cmd/status.go (4)

172-181: LGTM!

The JSON output is now properly canonicalized by parsing NDJSON/array inputs with parseContainerJSON and re-encoding as a proper JSON array with json.MarshalIndent. This addresses the previous review concern about NDJSON on older Compose versions.


255-274: LGTM!

The health check now properly requires both HTTP 2xx status code AND hr.Status == "ok" for healthy determination (Line 264). This addresses the previous review concern about degraded payloads being incorrectly marked as healthy.


91-99: LGTM!

The imageTag function correctly handles registry port edge cases (e.g., registry:5000/image with no tag) by checking if the last colon appears before the last slash.


236-253: LGTM!

The fetchHealth function has good defensive practices: context-aware request, 5-second timeout, and io.LimitReader to cap response body at 64KB preventing memory exhaustion from malicious responses.

cli/cmd/status_test.go (3)

56-71: LGTM!

The test now correctly uses t.Fatalf (Line 63) for the container count assertion, preventing potential panics when accessing containers[0]. This addresses the previous review concern.


9-54: LGTM!

The TestImageTag and TestHealthIcon tests have excellent coverage of edge cases including empty inputs, registry ports, and various state/health combinations.


73-92: LGTM!

The TestFormatUptime test covers all formatting branches (seconds-only, minutes+seconds, hours+minutes) with well-chosen values including edge cases like 0 and 24 hours.

Comment thread cli/cmd/doctor.go
Comment thread cli/cmd/status_test.go
Comment thread cli/cmd/status.go
Comment thread cli/internal/ui/ui.go
- Fix doctor exited container with empty health showing blank status
- Add JSON array test for parseContainerJSON (Compose v2.21+)
- Handle negative uptime in formatUptime (clock skew guard)
- Sanitize table cells: collapse \n and \t to spaces for stable alignment
Copy link
Copy Markdown
Contributor

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/cmd/doctor.go`:
- Around line 131-136: The code currently derives availability by substring
matching strings.Contains(img, "available") which false-positives on
"unavailable" or "not available"; update the logic that processes r.ImageStatus
to use a structured availability flag from diagnostics (e.g., an Available bool
alongside the message) and call out.Success(message) or out.Error(message) based
on that flag, or if changing diagnostics is not possible, match an exact
token/prefix instead of a substring (e.g., parse img into a status token and
message or check for a leading "available" token) to determine success vs error
rather than using strings.Contains; update references to r.ImageStatus,
out.Success and out.Error accordingly.

In `@cli/cmd/status_test.go`:
- Around line 87-105: Add a regression case for negative uptime to
TestFormatUptime: update the tests table in TestFormatUptime to include an entry
with seconds set to a negative value (e.g., -1) and the expected string matching
the function's clamp behavior (e.g., "0s"); locate the table inside
TestFormatUptime and add the new test row so the subtest loop exercises
formatUptime(seconds < 0) and validates the explicit negative-value branch.

In `@cli/cmd/status.go`:
- Around line 43-45: The current status command always writes human-readable
output (via ui.NewUI, printVersionInfo and subsequent UI/print calls) even when
jsonOut is true; change the control flow so every human-readable section
(printVersionInfo, Docker/Compose metadata, resource usage, health output,
container links/arrays and any early-warning/progress branches) is only executed
when !jsonOut, and route any diagnostics or warnings to stderr in JSON mode so
stdout remains a single canonical JSON value; locate uses of jsonOut,
printVersionInfo, ui.NewUI and the early-warning branches in status.go and wrap
or redirect them accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1a2b10c0-4c16-4364-bca9-12e59c9d57ad

📥 Commits

Reviewing files that changed from the base of the PR and between 7d00062 and 9c94489.

📒 Files selected for processing (4)
  • cli/cmd/doctor.go
  • cli/cmd/status.go
  • cli/cmd/status_test.go
  • cli/internal/ui/ui.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Test (Python 3.14)
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Lint CLI Go code with golangci-lint and go vet; test with go test -race; check vulnerabilities with govulncheck

Files:

  • cli/cmd/status.go
  • cli/internal/ui/ui.go
  • cli/cmd/status_test.go
  • cli/cmd/doctor.go
🧠 Learnings (1)
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • cli/cmd/status.go
  • cli/internal/ui/ui.go
  • cli/cmd/doctor.go
🧬 Code graph analysis (2)
cli/cmd/status.go (2)
cli/internal/config/state.go (2)
  • Load (52-90)
  • State (16-27)
cli/internal/ui/ui.go (6)
  • NewUI (58-70)
  • UI (44-53)
  • IconSuccess (27-27)
  • IconError (36-36)
  • IconInProgress (30-30)
  • IconWarning (33-33)
cli/cmd/status_test.go (1)
cli/internal/ui/ui.go (4)
  • IconSuccess (27-27)
  • IconError (36-36)
  • IconInProgress (30-30)
  • IconWarning (33-33)
🔇 Additional comments (1)
cli/internal/ui/ui.go (1)

127-194: UI.Table looks solid.

Stripping control chars, collapsing \n/\t, and sizing with runewidth is the right combination here; it keeps wide glyphs aligned and prevents cell content from breaking the table layout.

Comment thread cli/cmd/doctor.go
Comment thread cli/cmd/status_test.go
Comment on lines +87 to +105
func TestFormatUptime(t *testing.T) {
tests := []struct {
seconds float64
want string
}{
{0, "0s"},
{45, "45s"},
{90, "1m 30s"},
{3600, "1h 0m"},
{12991, "3h 36m"},
{86400, "24h 0m"},
}
for _, tt := range tests {
t.Run(tt.want, func(t *testing.T) {
if got := formatUptime(tt.seconds); got != tt.want {
t.Errorf("formatUptime(%v) = %q, want %q", tt.seconds, got, tt.want)
}
})
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add a regression case for negative uptime values.

formatUptime now has explicit handling for seconds < 0, but this table only exercises non-negative inputs. Adding one clock-skew case here would lock down the new branch and prevent silent regressions.

💚 Suggested test case
 	}{
 		{0, "0s"},
 		{45, "45s"},
+		{-90, "-1m 30s"},
 		{90, "1m 30s"},
 		{3600, "1h 0m"},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/cmd/status_test.go` around lines 87 - 105, Add a regression case for
negative uptime to TestFormatUptime: update the tests table in TestFormatUptime
to include an entry with seconds set to a negative value (e.g., -1) and the
expected string matching the function's clamp behavior (e.g., "0s"); locate the
table inside TestFormatUptime and add the new test row so the subtest loop
exercises formatUptime(seconds < 0) and validates the explicit negative-value
branch.

Comment thread cli/cmd/status.go
Comment on lines +43 to 45
out := ui.NewUI(cmd.OutOrStdout())
printVersionInfo(out, state)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

--json mode still writes human-readable output to stdout.

When jsonOut is true, this path still emits version info, Docker/Compose metadata, resource usage, health output, and links around the container array. That means synthorg status --json cannot be parsed as the single canonical JSON value promised by the flag. Please gate every human-readable section behind !jsonOut, and keep diagnostics off stdout in JSON mode as well.

📦 Suggested fix
 	out := ui.NewUI(cmd.OutOrStdout())
-	printVersionInfo(out, state)
+	if !jsonOut {
+		printVersionInfo(out, state)
+	}
@@
-	out.KeyValue("Docker", info.DockerVersion)
-	out.KeyValue("Compose", info.ComposeVersion)
-	_, _ = fmt.Fprintln(out.Writer())
-
-	printContainerStates(ctx, out, info, safeDir, jsonOut)
-	printResourceUsage(ctx, out, info, safeDir)
-	printHealthStatus(ctx, out, state, jsonOut)
-	printLinks(out, state)
+	if jsonOut {
+		printContainerStates(ctx, out, info, safeDir, true)
+		return nil
+	}
+
+	out.KeyValue("Docker", info.DockerVersion)
+	out.KeyValue("Compose", info.ComposeVersion)
+	_, _ = fmt.Fprintln(out.Writer())
+
+	printContainerStates(ctx, out, info, safeDir, false)
+	printResourceUsage(ctx, out, info, safeDir)
+	printHealthStatus(ctx, out, state, false)
+	printLinks(out, state)

The same rule should also apply to the early warning branches so JSON mode never prints prose to stdout.

Also applies to: 52-68

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

In `@cli/cmd/status.go` around lines 43 - 45, The current status command always
writes human-readable output (via ui.NewUI, printVersionInfo and subsequent
UI/print calls) even when jsonOut is true; change the control flow so every
human-readable section (printVersionInfo, Docker/Compose metadata, resource
usage, health output, container links/arrays and any early-warning/progress
branches) is only executed when !jsonOut, and route any diagnostics or warnings
to stderr in JSON mode so stdout remains a single canonical JSON value; locate
uses of jsonOut, printVersionInfo, ui.NewUI and the early-warning branches in
status.go and wrap or redirect them accordingly.

- Use strings.HasSuffix(": available") instead of Contains("available")
  to prevent false-positive on "unavailable" strings
- Add negative uptime regression test case (-90 → "-1m 30s")
@Aureliolo Aureliolo merged commit 61fa8af into main Mar 16, 2026
37 of 38 checks passed
@Aureliolo Aureliolo deleted the feat/cli-status-ux branch March 16, 2026 16:35
Aureliolo added a commit that referenced this pull request Mar 16, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.0](v0.2.9...v0.3.0)
(2026-03-16)


### Features

* **cli:** prettify status output with table, links, and --json flag
([#490](#490))
([61fa8af](61fa8af))


### Bug Fixes

* **api:** don't require password change after self-service setup
([#488](#488))
([ba13e04](ba13e04))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
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