8000 feat(minimal,diagnostics): add the min bug host diagnostic bundle command by norrietaylor · Pull Request #816 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

feat(minimal,diagnostics): add the min bug host diagnostic bundle command - #816

Merged
norrietaylor merged 4 commits into
mainfrom
feat/diag-unit2-min-bug
Jul 17, 2026
Merged

norrietaylor merged 4 commits into
mainfrom
feat/diag-unit2-min-bug

Conversation

@norrietaylor
@norrietaylor norrietaylor commented Jul 17, 2026 •
Copy link
Copy Markdown
Member

Unit 2 of the diagnostics series (spec: #802, epic: #801; depends on Unit 1, merged in #803).

What

min bug [--output <path>] assembles a minimal-diag-<UTC ts>.tar.zst through the
diagnostics crate's BundleWriter::create (rooted, 0600), honoring the same
--minimal-dir/--config-dir overrides as the rest of the CLI. The command mutates no
state and spawns no daemons — diagnosing a wedged system must not change it.

Mechanics in-crate, policy in the CLI

Reusable mechanics land in crates/diagnostics rather than the CLI, since the
daemon-side bundle (Unit 6) needs the same behaviors — its own rotated volume logs,
allowlisted guest env, volume disk usage:

crate mechanism CLI policy that drives it
redact::redact_toml (beside the existing redact_json) which config files to bundle
redact::masked_process_env(is_allowed) the env allowlist (exact names + prefixes)
logs::newest_rotated(dir, prefix, n) log dir location, prefixes, N=5
disk::disk_usage (statvfs) which paths to probe
capture::first_stdout_line which facts to ask (uname)

Example bundle

min bug on a dev Mac with one prior VM run — 8 entries, 0 errors, 17 ms:

entry contents
host/system.json macOS/aarch64, kernel string, hostname, CPU count, euid, disk capacity+free for state/cache/cwd
host/env.json 103 masked values; HOME/SHELL/TERM verbatim per the allowlist, USER/LANG as <redacted:len=N>
host/dirs.txt the min dirs table
config/loadouts/dev.toml.redacted a dev loadout — package list survives, every [vars] value masked (including a 1170-byte MINIMAL_MOTD)
config/client-cert.json cert metadata only
state/listing.txt metadata-only tree of the state dir
logs/minvmd.log.2026-07-16 the detached-minvmd rolling log from the previous day's run — gvproxy spawn, VMM child pid
providers/local-0/run.log the supervisor's stderr redirect
manifest.json version, duration, collected/skipped/errored accounting

The manifest's four skips explain every absence: config/client.key (never
collected), logs/minimald.log* (no files with that prefix — those begin to
exist with Unit 3), providers/local-0/boot.log (absent), and providers/
(1 instance present, daemon-side state not collected).

Refs: #801

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added the min bug command to create a self-contained compressed diagnostic bundle.
    • Bundles include system information, environment details, configuration, state listings, disk usage, and recent logs.
    • Added support for collecting provider-specific logs and recording unavailable data in the bundle manifest.
    • Added redaction for sensitive JSON, TOML, and environment values; private keys are excluded.
  • Improvements
    • Enhanced directory reporting with clearer formatting and grouped sections.
    • Diagnostic collection continues when individual sources are unavailable or time out.

@coderabbitai
coderabbitai Bot commented Jul 17, 2026 •
Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 3 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a8c8e49-892a-4de0-8f3c-a6880633426f

📥 Commits

Reviewing files that changed from the base of the PR and between 444c1df and 80206fc.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • crates/diagnostics/src/bundle.rs
  • crates/diagnostics/src/lib.rs
  • crates/diagnostics/src/listing.rs
  • crates/diagnostics/src/logs.rs
  • crates/diagnostics/src/system.rs
  • crates/minimal/Cargo.toml
  • crates/minimal/src/diag/collect.rs
  • crates/minimal/src/diag/mod.rs
  • crates/minimal/tests/bug.rs
📝 Walkthrough

Walkthrough

Adds reusable diagnostics utilities and a new min bug command that collects redacted host data, filesystem facts, state listings, and logs into a timed diagnostic archive with manifest reporting.

Changes

Diagnostic bundle

Layer / File(s) Summary
Diagnostics primitives
crates/diagnostics/*
Adds command output capture, Unix disk usage, rotated-log selection, TOML redaction, masked environment collection, public exports, and focused tests.
CLI redaction and directory reporting
crates/minimal/Cargo.toml, crates/minimal/src/diag/redact.rs, crates/minimal/src/dirs.rs
Adds environment-value allowlisting and reusable directory report formatting.
Host collectors
crates/minimal/src/diag/collect.rs
Collects system facts, environment, redacted configuration, certificate metadata, state listings, logs, provider directories, and skip records.
Bug command and integration tests
crates/minimal/src/diag/mod.rs, crates/minimal/src/lib.rs, crates/minimal/tests/bug.rs
Adds the min bug subcommand, timeout-wrapped collectors, archive finalization, CLI wiring, and end-to-end bundle coverage.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Collectors
  participant Diagnostics
  participant Bundle
  CLI->>Collectors: Run timed host collectors
  Collectors->>Diagnostics: Gather and redact diagnostic data
  Diagnostics-->>Collectors: Return facts, logs, and redacted values
  Collectors->>Bundle: Write entries and manifest records
  Bundle-->>CLI: Finalize archive and report counts
Loading

Possibly related issues

Possibly related PRs

  • gominimal/minimal#784 — Directly overlaps the diagnostic bundle command, collectors, redaction, and integration tests.

Poem

A rabbit packed logs in a bundle so neat,
With secrets all masked from the files it would meet.
Facts hopped in quickly, collectors ran true,
The manifest noted each task as it flew.
“Min bug!” cried the hare in delight—
An archive tucked safely by moonligh 8000 t.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: adding the min bug host diagnostic bundle command.

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

@norrietaylor
norrietaylor force-pushed the feat/diag-unit2-min-bug branch from 2325e59 to 9a3c1b9 Compare July 17, 2026 20:48
@norrietaylor norrietaylor changed the title feat(minimal): add the min bug host diagnostic bundle command feat(minimal,diagnostics): add the min bug host diagnostic bundle command Jul 17, 2026
@norrietaylor
norrietaylor marked this pull request as ready for review July 17, 2026 20:59
…mand

Add the bug subcommand: a manifest-bearing minimal-diag-<ts>.tar.zst
assembled through the diagnostics crate's BundleWriter. Host collectors
(system, env, dirs report, config, state listing, logs) run under
per-collector timeouts; any failure is recorded in the manifest and the
run continues. The command mutates no state and spawns no daemons.

Reusable mechanics land in the diagnostics crate so the daemon-side
bundle can share them: redact_toml beside redact_json, masked_process_env
over a caller-supplied allowlist predicate, newest_rotated log selection,
disk_usage (statvfs), and first_stdout_line over command_capture. The
CLI keeps only policy: the env allowlist, log prefixes, config paths,
and archive layout.

Refs: #801
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@norrietaylor
norrietaylor force-pushed the feat/diag-unit2-min-bug branch from 9a3c1b9 to 444c1df Compare July 17, 2026 21:01
@coderabbitai coderabbitai Bot left a comment
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.

Actionable comments posted: 3

🧹 Nitpick comments (2)
crates/diagnostics/src/redact.rs (1)

110-136: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant is_sensitive_key(key) in leaf check.

child_mask already includes is_sensitive_key(key) (line 114), so the child_mask || is_sensitive_key(key) check on line 120 is always just child_mask. This mirrors the JSON version's pattern but is dead logic. Consider simplifying to if child_mask for clarity.

♻️ Simplify redundant condition
                     leaf => {
-                        if child_mask || is_sensitive_key(key) {
+                        if child_mask {
                             *leaf = toml_placeholder(leaf);
                         }
                     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/diagnostics/src/redact.rs` around lines 110 - 136, In
redact_toml_value, simplify the leaf masking condition to check only child_mask,
since it already incorporates is_sensitive_key(key). Preserve the existing
placeholder replacement behavior.
crates/minimal/tests/bug.rs (1)

50-65: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Plant private-key canary content before asserting it is excluded.

No client.key is created, while production unconditionally records its skip. Add a distinct canary key file and verify that its bytes appear nowhere in the archive; this makes the privacy regression test meaningful.

Also applies to: 146-168

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/minimal/tests/bug.rs` around lines 50 - 65, Add a distinct canary
private-key file such as client.key under the temporary configuration directory
created by fake_config_dir, then update the archive assertions in the related
test to verify the key’s bytes are absent. Keep the existing secret-redaction
assertions and ensure the new canary content is not included anywhere in the
archive.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/minimal/src/diag/collect.rs`:
- Around line 230-242: Update add_redacted_toml to inspect the source with
symlink-safe metadata before reading it, accept only regular files, and avoid
following symbolic links. Record rejected symlinks via BundleWriter::skip as
withheld, while preserving the existing not-found and unreadable handling for
valid regular files.
- Around line 278-279: The diagnostics collector must not perform the
synchronous recursive walk inline in the future poll. Update the code around
diagnostics::listing to run the traversal on a blocking-capable worker or
replace it with an asynchronous, cancellation-aware implementation, while
preserving the existing “listing state dir” context and allowing the surrounding
30-second timeout to remain effective.
- Around line 187-188: Update provider_dirs and its callers to return and
propagate a Result, preserving an empty result only for NotFound filesystem
errors. Replace the silent if-let/while-let handling around directory reads and
the additional filesystem operations at the referenced locations so permission
and transient I/O failures reach collect_step! and are recorded in the manifest.

---

Nitpick comments:
In `@crates/diagnostics/src/redact.rs`:
- Around line 110-136: In redact_toml_value, simplify the leaf masking condition
to check only child_mask, since it already incorporates is_sensitive_key(key).
Preserve the existing placeholder replacement behavior.

In `@crates/minimal/tests/bug.rs`:
- Around line 50-65: Add a distinct canary private-key file such as client.key
under the temporary configuration directory created by fake_config_dir, then
update the archive assertions in the related test to verify the key’s bytes are
absent. Keep the existing secret-redaction assertions and ensure the new canary
content is not included anywhere in the archive.
🪄 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: CHILL

Plan: Pro

Run ID: 03159cbb-cefd-4806-96d0-fd4ede02a864

📥 Commits

Reviewing files that changed from the base of the PR and between 909771e and 9a3c1b9.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • crates/diagnostics/Cargo.toml
  • crates/diagnostics/src/capture.rs
  • crates/diagnostics/src/disk.rs
  • crates/diagnostics/src/lib.rs
  • crates/diagnostics/src/logs.rs
  • crates/diagnostics/src/redact.rs
  • crates/minimal/Cargo.toml
  • crates/minimal/src/diag/collect.rs
  • crates/minimal/src/diag/mod.rs
  • crates/minimal/src/diag/redact.rs
  • crates/minimal/src/dirs.rs
  • crates/minimal/src/lib.rs
  • crates/minimal/tests/bug.rs

Comment thread crates/minimal/src/diag/collect.rs Resolved
Comment thread crates/minimal/src/diag/collect.rs Resolved
Comment thread crates/minimal/src/diag/collect.rs Resolved
@coderabbitai coderabbitai Bot left a comment
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 comments (1)
crates/diagnostics/src/redact.rs (1)

110-136: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant is_sensitive_key(key) check on line 120.

child_mask on line 114 already incorporates is_sensitive_key(key), so child_mask || is_sensitive_key(key) on line 120 is always equivalent to child_mask. Simplifying improves clarity and avoids implying there's a case where child_mask is false but is_sensitive_key(key) is true.

♻️ Proposed simplification
                     leaf => {
-                        if child_mask || is_sensitive_key(key) {
+                        if child_mask {
                             *leaf = toml_placeholder(leaf);
                         }
                     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/diagnostics/src/redact.rs` around lines 110 - 136, In
redact_toml_value, simplify the leaf masking condition to rely only on
child_mask, since it already includes is_sensitive_key(key). Remove the
redundant second is_sensitive_key(key) check while preserving the existing
placeholder behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/diagnostics/src/redact.rs`:
- Around line 110-136: In redact_toml_value, simplify the leaf masking condition
to rely only on child_mask, since it already includes is_sensitive_key(key).
Remove the redundant second is_sensitive_key(key) check while preserving the
existing placeholder behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 92f6e2cf-243a-485b-a6d7-a0c186fcfa07

📥 Commits

Reviewing files that changed from the base of the PR and between 9a3c1b9 and 444c1df.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • crates/diagnostics/Cargo.toml
  • crates/diagnostics/src/capture.rs
  • crates/diagnostics/src/disk.rs
  • crates/diagnostics/src/lib.rs
  • crates/diagnostics/src/logs.rs
  • crates/diagnostics/src/redact.rs
  • crates/minimal/Cargo.toml
  • crates/minimal/src/diag/collect.rs
  • crates/minimal/src/diag/mod.rs
  • crates/minimal/src/diag/redact.rs
  • crates/minimal/src/dirs.rs
  • crates/minimal/src/lib.rs
  • crates/minimal/tests/bug.rs
🚧 Files skipped from review as they are similar to previous changes (12)
  • crates/minimal/Cargo.toml
  • crates/minimal/src/diag/redact.rs
  • crates/diagnostics/src/disk.rs
  • crates/diagnostics/src/capture.rs
  • crates/diagnostics/src/lib.rs
  • crates/minimal/tests/bug.rs
  • crates/minimal/src/lib.rs
  • crates/diagnostics/Cargo.toml
  • crates/minimal/src/dirs.rs
  • crates/diagnostics/src/logs.rs
  • crates/minimal/src/diag/mod.rs
  • crates/minimal/src/diag/collect.rs

8000
norrietaylor and others added 2 commits July 17, 2026 14:20
Three review findings on the host collectors:

- Filesystem errors are no longer reported as absence: provider_dirs
  returns Result (NotFound alone means never-spawned), and the loadouts
  and log-dir reads record non-NotFound errors instead of claiming the
  data does not exist.
- The no-follow discipline now covers every content read, not just log
  tails: the O_NOFOLLOW + fstat open moves from add_file_tail into a
  shared open_regular_nofollow, and config TOMLs and the mesh-enrolment
  file read through it, so a symlinked config file cannot steer
  unrelated data into the bundle.
- The synchronous listing walk runs under spawn_blocking so a wedged
  filesystem strands a blocking thread instead of the worker whose
  collect_step timeout is the failsafe; the crate fn documents the
  contract.

Refs: #801
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reverse-lexicographic name order is only correct for date-suffixed
rolling files. logrotate-style numeric shifting (live file bare, .1 the
most recently rotated) sorts in exactly the opposite direction — name
order would select the oldest files and drop the live log. Modified-time
order is rotation-scheme agnostic; name order (descending) breaks ties.

Refs: #801
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread crates/minimal/src/diag/collect.rs Outdated
// ── host/system.json ─────────────────────────────────────────────────────────

#[derive(Serialize)]
struct SystemInfo {
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.

This might be useful to collect in all our binaries - maybe move to diagnostics crate ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 80206fc — SystemInfo and its probes (uname facts, euid, KVM availability, disk capacity) now live in diagnostics::system; the disk paths stay caller policy (system_info(&[state, cache, cwd])), so any binary can open its bundle with the same facts. The CLI's direct libc dependency went with the probe.

…tics crate

Every bundle-producing binary wants the same opening facts (OS, arch,
kernel, hostname, cpu count, euid, KVM availability, disk capacity), so
SystemInfo and its probes move from the CLI collector into
diagnostics::system, with the disk paths staying caller policy. The CLI
keeps only its serialization into host/system.json, and its direct libc
dependency goes with the probe.

Refs: #801
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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

0