Skip to content

refactor: split command handlers, CLI output, auth, and store modules#19

Merged
BjornMelin merged 8 commits intomainfrom
refactor/lib-handlers-and-cli-output
Apr 23, 2026
Merged

refactor: split command handlers, CLI output, auth, and store modules#19
BjornMelin merged 8 commits intomainfrom
refactor/lib-handlers-and-cli-output

Conversation

@BjornMelin
Copy link
Copy Markdown
Owner

@BjornMelin BjornMelin commented Apr 23, 2026

This refactor keeps the CLI surface and output contracts stable while splitting lib.rs, cli_output, auth, and store into smaller ownership-focused modules.

Commits in this branch:

  • 131e127 refactor(cli): extract handlers and split output modules
  • 6a44229 refactor(auth): move orchestration into flow
  • f63bdc8 refactor(store): tighten store ownership

Validation:

  • cargo fmt
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test
  • cargo run -- paths
  • cargo run -- paths --json
  • cargo run -- doctor --json

Refs #10

Copilot AI review requested due to automatic review settings April 23, 2026 17:28
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Walkthrough

This PR restructures large modules into focused units: moves OAuth flow and its public API/report types into src/auth/flow.rs and re-exports them from src/auth/mod.rs; splits CLI output into src/cli_output/{errors,json,human}.rs with a consolidated cli_output surface; adds a handlers layer under src/handlers/* and re-exports handler entrypoints; extracts store diagnostics into src/store/doctor.rs and connection pragma helpers into src/store/connection.rs; replaces many inline test suites with dedicated test files (e.g., src/auth/tests.rs, src/cli_output/tests.rs, src/store/tests.rs); and adjusts visibility of several helpers/types.

Possibly related issues

  • BjornMelin/mailroom issue 10 — Implements the god-module decomposition called for by the issue by splitting auth, CLI output, handlers, and store doctor/connection logic into separate modules.

Possibly related PRs

  • BjornMelin/mailroom PR 6 — Strong overlap on the auth surface: both refactor the OAuth setup/login/status/logout flow, credential persistence, and callback handling.
  • BjornMelin/mailroom PR 5 — Directly related changes to Gmail auth/account code, credential store usage, and account upsert behavior.
  • BjornMelin/mailroom PR 3 — Overlapping modifications to the config-aware SQLite store bootstrap, init/doctor reporting, migrations, and pragma handling.
🚥 Pre-merge checks | ✅ 8 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.05% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main refactoring work: splitting command handlers, CLI output, auth, and store modules into smaller, ownership-focused modules.
Description check ✅ Passed The description clearly explains the refactoring objective, lists the commits included, describes validation steps performed, and references a related issue.
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.
React + Next.Js Best Practices ✅ Passed This check is not applicable to the provided pull request. The repository is a Rust CLI application, while the custom check evaluates React + Next.js best practices for frontend projects.
Docstring & Research Standards ✅ Passed Custom check designed for TypeScript/JavaScript projects is not applicable to this Rust project, so it passes by default.
Web Interface Guidelines ✅ Passed The custom check on 'Web Interface Guidelines' is not applicable to this pull request. The PR is a backend refactoring that reorganizes Rust modules for command handlers, CLI output, authentication flow, and data store functionality—it contains no web UI code, HTML/CSS/JavaScript, or user-facing interface components.
Google Python Style Compliance ✅ Passed This PR modifies only Rust files (.rs) in a Rust project; no Python files are present or modified.

✏️ 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 refactor/lib-handlers-and-cli-output
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch refactor/lib-handlers-and-cli-output

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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactor that keeps the CLI surface area and JSON/human output contracts stable while splitting previously large modules (lib.rs, cli_output, auth, store) into smaller ownership-focused submodules.

Changes:

  • Extract CLI subcommand routing into src/handlers/* and slim down src/lib.rs.
  • Split cli_output into errors/json/human submodules and move its tests into src/cli_output/tests.rs.
  • Split store diagnostics into store::doctor, move store connection/report types into store::connection, and relocate store tests into src/store/tests.rs.
  • Move auth orchestration/report types into auth::flow and relocate auth tests into src/auth/tests.rs.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/lib.rs Delegates CLI command handling to handlers and routes human error printing through cli_output.
src/handlers/mod.rs Central re-export of handle_*_command entrypoints used by lib.rs.
src/handlers/account.rs Account command handler + extracted account refresh tests.
src/handlers/attachment.rs Attachment command handler.
src/handlers/audit.rs Audit command handler.
src/handlers/auth.rs Auth command handler.
src/handlers/automation.rs Automation command handler.
src/handlers/config.rs Config/paths command handlers + malformed-config regression test.
src/handlers/doctor.rs Doctor command handler.
src/handlers/gmail.rs Gmail labels handler + local labels report printing.
src/handlers/search.rs Search command handler.
src/handlers/store.rs Store init/doctor command handler.
src/handlers/sync.rs Sync command handler + extracted sync option parsing tests.
src/handlers/workflow.rs Workflow/triage/draft/cleanup handlers + extracted input validation tests.
src/handlers/workspace.rs Workspace init handler.
src/cli_output/mod.rs New module facade for CLI output helpers and tests.
src/cli_output/errors.rs Centralized error classification into stable JSON codes/kinds + exit code mapping.
src/cli_output/json.rs JSON success/failure envelope writers.
src/cli_output/human.rs Human-mode error printing helper.
src/cli_output/tests.rs Comprehensive contract tests for JSON/human error output + selected end-to-end CLI invocations.
src/store/mod.rs Slim store module; re-exports init and doctor report APIs; uses connection::read_pragmas.
src/store/connection.rs Owns DB open/hardening primitives and store init report/pragmas structures.
src/store/doctor.rs Owns store “doctor” inspection report + human printing + regression test for sync summary formatting.
src/store/tests.rs Store module tests extracted from store/mod.rs (migrations, init/doctor behavior, permission hardening, migration fixtures).
src/auth/mod.rs Re-exports auth API from auth::flow and moves tests into dedicated module.
src/auth/flow.rs Auth orchestration and reports moved here (setup/login/logout/status + loopback callback server).
src/auth/tests.rs Auth tests extracted from auth/mod.rs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/store/connection.rs
Copy link
Copy Markdown

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

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

Inline comments:
In `@src/auth/flow.rs`:
- Around line 383-390: The code unnecessarily clones the SecretString before
exposing; update the call to construct the OAuth RefreshToken using the exposed
secret directly (use refresh_token.expose_secret().to_owned() inside
RefreshToken::new) and remove the redundant refresh_token.clone() so that
oauth_client.exchange_refresh_token(...) uses
RefreshToken::new(refresh_token.expose_secret().to_owned()); keep the
surrounding call to request_async(...) and the subsequent
completed_login_from_token_response(...) unchanged.

In `@src/cli_output/tests.rs`:
- Around line 611-736: The subprocesses spawned by Command in the test
automation_apply_auth_failure_uses_auth_exit_code_in_json_and_human_modes
inherit the caller’s real XDG/HOME config which makes auth non-deterministic;
fix by isolating the env for both Command::new(...) calls (the json_output and
human_output invocations) — create an empty config dir under the TempDir (e.g.,
repo_root.path().join("config")) and then call .env("XDG_CONFIG_HOME", that_dir)
and .env_remove("HOME") (or set HOME to the TempDir) on each Command so the
child process cannot pick up the developer’s user config and the test
consistently triggers the MissingCredentials auth failure.

In `@src/handlers/config.rs`:
- Around line 16-23: handle_paths_command currently swallows the error from
config::resolve and silently falls back to paths.print; update the Err(_) branch
to emit a warning to stderr (or use the project's logger) that includes the
error details (the Display/Debug of the error) so users are informed their
configuration failed while preserving the existing fallback behavior; refer to
handle_paths_command and config::resolve when making this change and ensure the
warning is printed before calling paths.print(json).

In `@src/handlers/gmail.rs`:
- Around line 36-38: The current println in gmail.rs iterates over self.labels
and prints label.id, label.name, and label.label_type separated by spaces which
is ambiguous when names contain spaces; update the formatting in the loop that
references self.labels/label.id/label.name/label.label_type to use a clear
delimiter such as tabs (e.g., join with '\t') or emit a structured format (JSON)
per label so each field is unambiguous for human consumption and downstream
parsing.

In `@src/handlers/workflow.rs`:
- Around line 227-250: The function resolve_draft_body_input performs blocking
file and stdin reads on the async runtime; change it to async fn
resolve_draft_body_input(...) -> Result<String> and move any blocking work
(std::fs::read_to_string and std::io::stdin().read_to_string) into
tokio::task::spawn_blocking closures so those operations run off the Tokio
worker threads, await the spawn_blocking futures, and propagate the same
CliInputError variants (DraftBodyFileRead, DraftBodyStdinRead) on error; then
update the async caller handle_draft_command to call and .await the new async
resolve_draft_body_input.

In `@src/store/doctor.rs`:
- Around line 35-475: The write_human method is very long and repeats the same
key=value formatting; extract small helpers (e.g., write_kv and write_kv_opt)
and replace repeated writeln! patterns inside write_human (and nested blocks
handling mailbox, sync_state, summary, etc.) with calls to those helpers to
reduce duplication and improve readability while preserving the same output
formatting; reference the existing write_human function and introduce helper
functions like write_kv<W: Write>(writer, key, value) and write_kv_opt<W: Write,
T: Display>(writer, key, option, "<none>" or "<uninitialized>") and use them for
fields such as mailbox_sync_run_summary_best_clean_run_id,
mailbox_sync_pacing_last_pressure_kind, schema_version/pending_migrations, and
all other repeated match/writeln! sites.

In `@src/store/mod.rs`:
- Around line 89-92: The code currently uses path.display() (UTF-8 lossy) to
build companion paths; instead use path.as_os_str().to_os_string() and append
the literal suffixes so non-UTF-8 bytes are preserved. For example, keep pushing
path.to_path_buf() for the main file, then create a mutable OsString from
path.as_os_str().to_os_string(), call OsString::push("-wal") and push
PathBuf::from(that_osstring) into candidate_paths, and repeat with "-shm"; this
preserves raw bytes and avoids lossy UTF-8 conversions.

In `@src/store/tests.rs`:
- Around line 753-759: The helper unique_temp_dir creates directories that
aren't cleaned up on panic; replace its uses with tempfile::TempDir (e.g.,
TempDir::with_prefix or TempDir::new) so the TempDir handle's Drop performs
automatic cleanup. Update tests that call unique_temp_dir to keep the TempDir
handle alive for the test duration, pass repo_root.path().to_path_buf() (or
similar) into WorkspacePaths::from_repo_root or other APIs that previously
accepted a PathBuf, and remove manual fs::remove_dir_all calls; you can remove
or deprecate the unique_temp_dir function after migrating usages.
- Around line 119-134: Add a short clarifying comment above the assertions
explaining why pragmas.synchronous is 1 while the persisted pragma read via
Connection::open(...) is 2: note that the diagnostics/read-only connection
(where pragmas.synchronous is observed) applies its own runtime pragmas
(synchronous=1), whereas the persisted database value (queried via
connection.pragma_query_value on init_report.database_path) remains the value
set earlier (synchronous=2), and that the test ensures the persisted value
wasn’t overwritten.
🪄 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: 50cb84d2-ced9-4542-b680-092197e9d7d5

📥 Commits

Reviewing files that changed from the base of the PR and between 6e27244 and f63bdc8.

📒 Files selected for processing (28)
  • src/auth/flow.rs
  • src/auth/mod.rs
  • src/auth/tests.rs
  • src/cli_output.rs
  • src/cli_output/errors.rs
  • src/cli_output/human.rs
  • src/cli_output/json.rs
  • src/cli_output/mod.rs
  • src/cli_output/tests.rs
  • src/handlers/account.rs
  • src/handlers/attachment.rs
  • src/handlers/audit.rs
  • src/handlers/auth.rs
  • src/handlers/automation.rs
  • src/handlers/config.rs
  • src/handlers/doctor.rs
  • src/handlers/gmail.rs
  • src/handlers/mod.rs
  • src/handlers/search.rs
  • src/handlers/store.rs
  • src/handlers/sync.rs
  • src/handlers/workflow.rs
  • src/handlers/workspace.rs
  • src/lib.rs
  • src/store/connection.rs
  • src/store/doctor.rs
  • src/store/mod.rs
  • src/store/tests.rs
💤 Files with no reviewable changes (1)
  • src/cli_output.rs
📜 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: Agent
  • GitHub Check: rust
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.rs: Use anyhow for command dispatch and top-level context in src/lib.rs; prefer typed thiserror errors in Gmail, workflow, and store layers in Rust
Keep error enums local to the layer that owns the failure semantics; do not introduce a repo-wide catch-all error enum unless it clearly reduces total code and cognitive load
For new CLI JSON contracts, normalize success and failure to one top-level shape: { "success": true, "data": ... } for success and { "success": false, "error": { "code": ..., "message": ..., "kind": ..., "operation": ..., "causes": [...] } } for failure
Keep error.code stable and operator-oriented, keep error.kind for deeper subsystem detail, and keep error.causes to an ordered message chain only in JSON error contracts
Do not include debug or backtrace payloads in the JSON error contract; use stderr and Rust backtrace env vars for deep diagnostics instead
Preserve existing human-facing error text unless it is misleading, ambiguous, or missing required operator action
When adding or changing CLI failures, keep exit codes in a small stable bucket set rather than creating one-off codes per variant
Keep blocking SQLite and filesystem work behind tokio::task::spawn_blocking; do not treat running spawn_blocking work as abortable
Add focused error-path tests for every new failure class; if CLI JSON or exit-code behavior changes, add contract tests for the new output and exit mapping in the same pass

Files:

  • src/cli_output/human.rs
  • src/handlers/audit.rs
  • src/handlers/search.rs
  • src/cli_output/mod.rs
  • src/handlers/workspace.rs
  • src/handlers/auth.rs
  • src/handlers/sync.rs
  • src/handlers/mod.rs
  • src/handlers/account.rs
  • src/handlers/workflow.rs
  • src/cli_output/errors.rs
  • src/cli_output/json.rs
  • src/store/connection.rs
  • src/auth/tests.rs
  • src/store/doctor.rs
  • src/auth/flow.rs
  • src/handlers/gmail.rs
  • src/store/tests.rs
  • src/handlers/store.rs
  • src/handlers/doctor.rs
  • src/auth/mod.rs
  • src/store/mod.rs
  • src/lib.rs
  • src/handlers/automation.rs
  • src/handlers/attachment.rs
  • src/handlers/config.rs
  • src/cli_output/tests.rs
🔍 Remote MCP Firecrawl, GitHub Copilot

Based on my tool results, I can now provide you with a comprehensive summary of the relevant context for this PR review.

Summary of Additional Context

PR Overview: This is a substantial refactoring PR (5114 additions, 4773 deletions) that successfully decomposes large monolithic modules into focused, ownership-based submodules across four major areas: handlers, CLI output, authentication, and store management.

Key Architectural Changes:

  1. Handlers Module Extraction - All command handlers previously embedded in src/lib.rs (~960 lines removed) are now in src/handlers/ with 13 specialized modules, each handling a distinct command domain (account, attachment, audit, auth, automation, config, doctor, gmail, search, store, sync, workflow, workspace).

  2. CLI Output Refactoring - src/cli_output.rs (1320 lines) split into modular substructure:

    • errors.rs: Error classification and JSON body generation (384 lines, 100% code coverage)
    • json.rs: JSON serialization envelopes (54 lines)
    • human.rs: Human-readable output formatting (5 lines)
    • tests.rs: Comprehensive error mapping tests (875 lines)
  3. Authentication Module Reorganization - OAuth flow logic moved to dedicated src/auth/flow.rs (563 additions, 212 deletions). Defines structured report types (AuthStatusReport, SetupReport, LoginReport, LogoutReport) and refactors token exchange into helpers.

  4. Store Module Tightening - New src/store/doctor.rs (627 lines) extracts diagnostic and inspection logic, with new StoreDoctorReport and StorePragmas types. Tests migrated to src/store/tests.rs (759 lines) with comprehensive migration validation.

Visibility Patterns (per Rust best practices):
The PR follows Rust's recommended module organization pattern: flat facade with re-exports as the crate's public interface. Handler functions are pub(crate) to restrict visibility to the workspace, internal helpers use pub(super) for sibling-module access, and the top-level module (handlers/mod.rs) re-exports only the primary command entrypoints.

Code Quality Metrics:

  • Validation performed: cargo fmt, cargo clippy --all-targets --all-features -- -D warnings, cargo test
  • Manual testing: cargo run -- paths, cargo run -- paths --json, cargo run -- doctor --json
  • All tests pass as indicated by status on PR creation (2026-04-23)

Risk Assessment:
Low risk - The refactoring is conservative. CLI surface contracts and output formats remain stable. Error mappings are re-tested in new location. Tests moved from embedded suites to dedicated modules with increased coverage (664 lines added in src/auth/tests.rs, 759 in src/store/tests.rs).

🔇 Additional comments (28)
src/auth/flow.rs (5)

274-281: Blocking SQLite calls on async path without spawn_blocking.

The status and logout functions call accounts::get_active and accounts::deactivate_all synchronously within async context. Per coding guidelines, blocking SQLite and filesystem work should be behind tokio::task::spawn_blocking.

However, since status and logout are synchronous functions (not async fn), this is acceptable - the blocking happens before the async runtime is involved. The caller (handle_auth_command) awaits these via .await only for the async variants (setup, login).

Also applies to: 303-311


31-72: Report types and print methods follow the expected pattern.

The report structs (AuthStatusReport, SetupReport, LoginReport, LogoutReport) correctly:

  • Derive Serialize for JSON output
  • Implement print(&self, json: bool) -> Result<()> delegating to crate::cli_output::print_json_success for JSON mode
  • Provide human-readable println! output for non-JSON mode

This aligns with the handler usage pattern shown in src/handlers/auth.rs.

Also applies to: 74-117, 119-147, 149-168


170-188: Well-structured typed error enum using thiserror.

AuthError properly uses thiserror for layer-specific errors with clear, operator-oriented messages. This follows the coding guideline to prefer typed thiserror errors in domain layers.


487-508: Credential rollback on persistence failure is a good defensive pattern.

The persist_login_state function correctly implements rollback semantics: if persist_active_account fails after credentials are saved, it attempts to restore the previous credential state. This prevents partial state corruption.


333-337: MSRV is sufficient for let-chains syntax.

The project's minimum supported Rust version is 1.89 (Cargo.toml), which exceeds the Rust 1.65 requirement for let-chains. No changes needed.

src/cli_output/human.rs (1)

1-5: LGTM - Clean helper for human-readable error output.

The {error:#} format specifier correctly uses anyhow's alternate display, which prints the error with its full cause chain in a human-readable format.

src/handlers/gmail.rs (1)

6-24: LGTM - Handler follows the established pattern.

The async handler correctly resolves config, dispatches to the Gmail client, and uses the report's print method for output. The pattern is consistent with other handlers.

src/handlers/search.rs (1)

5-25: LGTM - Clean search handler implementation.

The handler correctly maps CLI arguments to the SearchRequest struct and delegates output formatting to the search result's print method, maintaining consistency with other handlers.

src/handlers/doctor.rs (1)

5-10: LGTM - Synchronous doctor handler is appropriate.

The handler is synchronous, which is suitable for the doctor/inspection workflow that performs blocking SQLite operations. The pattern is consistent with other handlers.

src/cli_output/mod.rs (1)

1-10: LGTM - Clean module organization with appropriate re-exports.

The facade pattern correctly exposes the crate-internal API (pub(crate)) while keeping implementation details in submodules. The re-exports align with the actual function definitions shown in the context snippets.

src/handlers/audit.rs (1)

6-18: LGTM - Audit handler follows the established dispatch pattern.

The handler cleanly routes AuditCommand variants to the appropriate audit functions and uses consistent output handling via the report's print method.

src/handlers/store.rs (1)

11-18: LGTM - Store handler with appropriate setup steps.

The Init branch correctly ensures runtime directories exist before initializing the store. The Doctor branch passes config_report by value, which appears intentional for the store::inspect API.

src/handlers/auth.rs (1)

1-28: LGTM!

Clean handler implementation following the established dispatch pattern. Uses anyhow::Result appropriately for command dispatch, delegates to typed auth flow functions, and prints output via the report's .print(json) method. Error propagation is correct throughout.

src/handlers/attachment.rs (1)

1-56: LGTM!

Well-structured async handler with consistent dispatch pattern. Properly awaits all attachment operations and uses the report's .print(json) method for output formatting. The AttachmentListRequest struct construction is clean and readable.

src/handlers/workspace.rs (1)

1-25: LGTM!

The handler correctly resolves configuration, ensures runtime directories exist, and reports the created paths. The direct println! usage (rather than a JSON-aware report) differs from other handlers but is reasonable for a one-time initialization command that doesn't appear to support --json output.

src/handlers/automation.rs (1)

1-41: LGTM!

Consistent async handler following the established dispatch pattern. All automation operations are properly awaited with correct error propagation and JSON-aware output formatting via .print(json).

src/auth/mod.rs (1)

1-8: LGTM!

Clean module façade that re-exports the public API (AuthError, AuthStatusReport, and the flow functions) while keeping implementation details in the flow submodule. The test module is appropriately gated with #[cfg(test)].

src/store/connection.rs (2)

10-49: LGTM!

Well-structured report types with proper Serialize derivation. The print method correctly handles both JSON (via print_json_success) and human-readable output (delegating pragma printing to the doctor module). The separation of concerns is clean.


130-150: LGTM!

The read_pragmas function cleanly queries SQLite connection pragmas and returns a structured StorePragmas. The visibility pub(super) appropriately restricts access to sibling modules within the store layer.

src/handlers/config.rs (1)

33-57: Good test coverage for the fallback behavior.

The test properly verifies that handle_paths_command succeeds even when configuration is malformed, using a unique temp directory for isolation.

src/handlers/sync.rs (3)

1-32: LGTM!

Clean async handler with proper dispatch. The shared handling for Run and Benchmark (lines 13-17) makes sense as benchmark uses the same sync logic with identical options. The alternate subcommand paths (SyncCommand::PerfExplain and SyncPerfCommand::Explain) are handled correctly.


34-90: Well-structured profile and option resolution.

The layered approach—profile defaults → legacy defaults → CLI overrides—is clean and maintainable. Validation for zero values uses typed CliInputError variants as recommended by the coding guidelines, enabling precise error matching in tests.


92-307: Excellent test coverage.

The tests comprehensively cover:

  • Legacy default selection without profile
  • Deep-audit profile application
  • CLI override authority over profile defaults
  • All three zero-value rejection paths with exact error text verification
  • CLI parsing parity between run and benchmark subcommands
  • Help text and invalid value rejection

This aligns well with the coding guideline to add focused error-path tests for every new failure class.

src/cli_output/json.rs (1)

1-54: LGTM!

The JSON envelope implementation correctly follows the coding guidelines for CLI JSON contracts: success responses use { "success": true, "data": ... } and failure responses use { "success": false, "error": ... }. The visibility scoping (pub(super) for internal types, pub(crate) for the public API) is appropriate, and the use of anyhow::Result aligns with guidelines for command dispatch layers.

src/store/tests.rs (1)

12-751: LGTM!

The test suite is comprehensive and well-structured:

  • Tests cover initialization, inspection, permission hardening, and migration round-trips
  • Assertions validate schema versions, pragma values, data integrity (null checks, foreign key consistency), and edge cases (foreign databases, ahead-of-migrations scenarios)
  • Unix-specific tests are properly gated with #[cfg(unix)]

The migration tests (v6 backfill and v16 round-trip) are particularly thorough, seeding realistic data volumes and verifying both structural and data-level correctness after migrations.

src/store/doctor.rs (3)

8-33: LGTM!

The StoreDoctorReport struct is well-designed with appropriate Option wrappers for fields that may be unavailable when the database doesn't exist. The print method correctly dispatches to print_json_success for JSON output (adhering to the CLI JSON contract) and write_human for human-readable output.


478-522: LGTM!

The inspect function is well-structured:

  • Early return for non-existent databases avoids unnecessary work and correctly returns database_exists: false
  • Uses a read-only diagnostic connection, appropriate for inspection without side effects
  • Properly propagates errors from all subsystem inspection calls
  • The ConfigReport ownership transfer is appropriate since it's embedded in the returned report

524-626: LGTM!

The pragma helpers are clean and focused. The unit test write_human_restores_sync_run_summary_fields is well-constructed—it uses TempDir for automatic cleanup and thoroughly validates that the new sync run summary fields are correctly formatted in human-readable output.

Comment thread src/auth/flow.rs
Comment thread src/cli_output/tests.rs
Comment thread src/handlers/config.rs
Comment thread src/handlers/gmail.rs
Comment thread src/handlers/workflow.rs Outdated
Comment thread src/store/doctor.rs
Comment thread src/store/mod.rs Outdated
Comment thread src/store/tests.rs
Comment thread src/store/tests.rs Outdated
…ate scope

- Updated `ParsedCallback` struct in `flow.rs` to use `pub(in crate::auth)` visibility for its fields.
- Changed visibility of `JsonSuccessEnvelope` and `JsonFailureEnvelope` structs and their associated functions in `json.rs` to `pub(crate)`, enhancing encapsulation within the crate.
- Simplified the refresh token handling in `flow.rs` by removing unnecessary cloning.
- Enhanced error handling in `config.rs` to provide warnings when configuration resolution fails.
- Updated output formatting in `gmail.rs` to use tab separation for better readability.
- Refactored draft body input resolution in `workflow.rs` to be asynchronous, improving performance and responsiveness.
- Introduced utility functions in `doctor.rs` for writing key-value pairs, streamlining the reporting process.
Copy link
Copy Markdown

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

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

Inline comments:
In `@src/auth/flow.rs`:
- Around line 510-520: persist_active_account (and the similar blocking calls in
status and logout) performs synchronous SQLite and filesystem work (store::init,
accounts::upsert_active) from async call paths; wrap these blocking operations
in tokio::task::spawn_blocking and await the join handle (or refactor the
persistence API to be async) and update the callers: make finalize_login async
so it can await the spawn_blocking result, then propagate async changes to login
and setup; likewise wrap the blocking code in status and logout (or convert
their persistence helpers to return futures) to ensure no blocking occurs on the
Tokio worker thread.

In `@src/cli_output/tests.rs`:
- Around line 546-609: The test
cli_entrypoint_contract_round_trips_json_and_human_failures currently sets
XDG_CONFIG_HOME on the subprocess Commands but does not remove HOME, causing
inconsistent environment isolation; update both Command constructions (the
json_output and human_output invocations referenced in this test) to call
.env_remove("HOME") in addition to .env("XDG_CONFIG_HOME", ...) so HOME is not
inherited, and apply the same change to the attachment_fetch_cli_contract...
test (the other test that sets XDG_CONFIG_HOME) to ensure consistent isolation
across the suite.

In `@src/handlers/config.rs`:
- Around line 39-51: Update the test
paths_command_still_prints_repo_local_paths_when_config_is_malformed to assert
the CLI output contract: after creating WorkspacePaths via
WorkspacePaths::from_repo_root and asserting resolve(&paths).is_err(), capture
stdout/stderr from handle_paths_command(&paths, true) (and/or call the JSON-mode
variant) instead of just unwraping, then assert that the printed output contains
the expected repo-local path strings and that the exit/result mapping is correct
(e.g., returns Ok for printing behavior or propagates an error in JSON mode).
Use the existing symbols WorkspacePaths::from_repo_root, resolve, and
handle_paths_command to locate the test and add assertions for the textual/JSON
output and any expected exit-code semantics.
- Around line 20-22: The stderr warning in src/handlers/config.rs prints a
pretty-debug payload ("{error:#?}") which can expose internals; update the
eprintln in the config::resolve fallback message to only use the Display-form
error ("{error}") so the warning remains human-facing and stable—locate the
eprintln call that currently formats "warning: config::resolve failed for
`mailroom paths`; falling back to repo-local paths: {error}\n{error:#?}" and
remove the "{error:#?}" portion, leaving only the descriptive message with
"{error}".

In `@src/handlers/gmail.rs`:
- Line 10: config::resolve is performing blocking filesystem operations via
ConfigLocations::discover and must be moved out of the async context; wrap the
call to config::resolve(paths) in a tokio::task::spawn_blocking closure (clone
paths into the closure), await the JoinHandle and propagate errors (i.e.,
await?? pattern), or alternatively make config::resolve async and run the
blocking bits with spawn_blocking inside it; locate the call to config::resolve
and replace it with a spawn_blocking-wrapped invocation that clones paths and
awaits the result.

In `@src/store/doctor.rs`:
- Around line 451-495: The handlers called from async run_cli
(handle_store_command, handle_audit_command, handle_doctor_command) call
blocking functions like inspect that perform SQLite/filesystem work and must be
executed via tokio::task::spawn_blocking; update the call sites in run_cli to
wrap each synchronous handler invocation in spawn_blocking (move the handler
call into the closure), await the JoinHandle to get its Result, and propagate or
map errors as before so the handler signatures can remain synchronous (e.g.,
tokio::task::spawn_blocking(|| handle_store_command(&paths, command)).await?).
Ensure you reference the synchronous function inspect in src/store/doctor.rs
remains unchanged but is only invoked from the spawned blocking closure.

In `@src/store/tests.rs`:
- Around line 209-236: The test
harden_database_permissions_updates_sqlite_sidecars creates a TempDir with
TempDir::with_prefix which already creates the directory, so remove the
redundant fs::create_dir_all(repo_root.path()) call; update the test to rely on
TempDir::with_prefix alone and ensure no other logic depends on that extra
create_dir_all invocation (references: TempDir::with_prefix, fs::create_dir_all,
harden_database_permissions_updates_sqlite_sidecars).
🪄 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: a5fa56ed-78e0-4649-8f12-8134745c4f39

📥 Commits

Reviewing files that changed from the base of the PR and between f63bdc8 and 002e678.

📒 Files selected for processing (8)
  • src/auth/flow.rs
  • src/cli_output/json.rs
  • src/cli_output/tests.rs
  • src/handlers/config.rs
  • src/handlers/gmail.rs
  • src/handlers/workflow.rs
  • src/store/doctor.rs
  • src/store/tests.rs
📜 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). (1)
  • GitHub Check: rust
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.rs: Use anyhow for command dispatch and top-level context in src/lib.rs; prefer typed thiserror errors in Gmail, workflow, and store layers in Rust
Keep error enums local to the layer that owns the failure semantics; do not introduce a repo-wide catch-all error enum unless it clearly reduces total code and cognitive load
For new CLI JSON contracts, normalize success and failure to one top-level shape: { "success": true, "data": ... } for success and { "success": false, "error": { "code": ..., "message": ..., "kind": ..., "operation": ..., "causes": [...] } } for failure
Keep error.code stable and operator-oriented, keep error.kind for deeper subsystem detail, and keep error.causes to an ordered message chain only in JSON error contracts
Do not include debug or backtrace payloads in the JSON error contract; use stderr and Rust backtrace env vars for deep diagnostics instead
Preserve existing human-facing error text unless it is misleading, ambiguous, or missing required operator action
When adding or changing CLI failures, keep exit codes in a small stable bucket set rather than creating one-off codes per variant
Keep blocking SQLite and filesystem work behind tokio::task::spawn_blocking; do not treat running spawn_blocking work as abortable
Add focused error-path tests for every new failure class; if CLI JSON or exit-code behavior changes, add contract tests for the new output and exit mapping in the same pass

Files:

  • src/handlers/gmail.rs
  • src/handlers/config.rs
  • src/cli_output/json.rs
  • src/handlers/workflow.rs
  • src/store/doctor.rs
  • src/cli_output/tests.rs
  • src/auth/flow.rs
  • src/store/tests.rs
🔍 Remote MCP Firecrawl

Summary of additional, review-relevant facts found on the PR page (scraped via Firecrawl):

  • PR page metadata and CI/automation:

    • PR #19 by BjornMelin: "refactor: split command handlers, CLI output, auth, and store modules" (open).
    • Automated reviewers (Copilot, coderabbitai) ran; Codera bbit/agents produced detailed file-level review output and comments. One pre-merge check failed: Docstring Coverage (40.74% vs required 60%). Other checks reported as passed.
  • Notable automated review/findings (actionable for a human reviewer):

    • Upward dependency / layering concern: StoreInitReport::print in src/store/connection.rs calls crate::store::doctor::print_pragmas, creating cross-layer coupling; reviewer suggested moving pragma rendering into connection or a small shared helper (store::output/pragmas).
    • Path handling edge case: several reviewers pointed out use of path.display() to build "-wal"/"-shm" companion paths (lossy UTF-8). Suggested replacement with OsString (path.as_os_str().to_os_string(); push suffix) so non-UTF-8 DB paths preserve bytes. This is security-relevant because hardening may miss companion files. Example suggested patch provided.
    • Blocking/async notes and spawn_blocking guidance: reviewers flagged a few places where blocking file/SQLite reads should be behind tokio::task::spawn_blocking (workflow draft-body reads, some sqlite calls referenced), and suggested making resolve_draft_body_input async + spawn_blocking in workflow handler tests.
    • Test determinism and environment isolation: a test spawns subprocesses that may inherit real XDG/HOME and cause non-determinism; suggested setting env (XDG_CONFIG_HOME, HOME) in Command in tests to ensure deterministic MissingCredentials behavior.
    • Formatting/output suggestion: gmail handler human-mode printing should use tabs (or otherwise unambiguous delimiter) instead of spaces to avoid ambiguity when label names include spaces. (PR appears to have updated this in a later commit.)
    • Code-quality/style/duplication suggestions: large write_human in store::doctor.rs suggested to be split into small write_kv/write_kv_opt helpers to reduce duplication.
    • Tests and coverage: PR adds extensive test files (auth, cli_output, store) and many tests migrated out of monolithic files into dedicated test modules; reviewers noted tests are comprehensive but suggested improvements (TempDir use, cleanup on panic).
  • Files highlighted by automated reviewers for focused attention (representative subset):

    • src/store/connection.rs (StoreInitReport::print calling doctor::print_pragmas)
    • src/store/mod.rs (path.display() usage for -wal/-shm)
    • src/store/doctor.rs (large write_human)
    • src/auth/flow.rs (refresh token cloning, credential rollback; some spawn_blocking guidance)
    • src/cli_output/tests.rs (subprocess env isolation)
    • src/handlers/workflow.rs (blocking file/stdin reads -> make async + spawn_blocking)
    • src/handlers/gmail.rs (human output delimiter change)
      (These files were explicitly enumerated in the automated review output.)
  • Operational notes from PR page:

Sources used:

  • GitHub PR page scraped with Firecrawl (#19) — all items above are from that page.
🔇 Additional comments (18)
src/handlers/config.rs (1)

5-14: Good handler split and command dispatch flow.

handle_config_command is clean and correctly scoped for ConfigCommand::Show { json }.

src/handlers/gmail.rs (1)

31-40: Clean output split and readable human formatting.

GmailLabelsReport::print keeps JSON output centralized and uses unambiguous tab-separated fields for human mode. Nice, maintainable boundary.

src/store/doctor.rs (4)

9-25: LGTM!

The write_kv and write_kv_opt helper functions cleanly address the previous review feedback about repetitive key-value formatting. This reduces duplication throughout write_human while maintaining readability.


27-52: LGTM!

The StoreDoctorReport struct is well-defined with appropriate derives, and the print method cleanly handles both JSON and human-readable output modes.


54-448: LGTM!

The write_human method effectively uses the extracted helper functions throughout, making the code much more maintainable than repetitive writeln! patterns. The handling of optional subsystem reports and nested optional fields is clean and consistent.


514-599: LGTM!

Good test coverage for the write_human method, validating key sync run summary fields. The test uses TempDir for proper cleanup and exercises the mailbox subsystem report path thoroughly.

src/store/tests.rs (5)

1-20: LGTM!

Clean imports with proper use of tempfile::TempDir (addressing the previous review suggestion). The basic tests for busy_timeout and migration validation are concise and focused.


22-67: LGTM!

Thorough test validating store initialization, schema version, application_id, and presence of all 20 expected substrate tables.


82-136: LGTM!

The clarifying comment at lines 114-119 properly explains the distinction between runtime pragmas applied by the read-only diagnostics connection vs. the persisted on-disk values. This addresses the previous review feedback about the apparent discrepancy between pragmas.synchronous == 1 and the persisted value of 2.


238-527: LGTM!

Excellent migration round-trip test that seeds realistic data across multiple accounts, rolls back to v6, and validates that the forward migration correctly backfills the account_id column on attachments. The shared attachment key test at the end verifies that the new account-scoped uniqueness constraint works correctly.


529-741: LGTM!

Comprehensive test for the v16 sync history comparability migration. The test properly validates:

  • Summary count reduction after down.sql (2 → 1 due to merging)
  • Summary count restoration after up.sql (1 → 2)
  • Correct best_clean_run_id preservation for both "tiny" and "large" comparability keys

This ensures the migration is safely reversible and correctly rebuilds per-comparability-key summaries.

src/cli_output/json.rs (1)

1-54: LGTM!

The JSON envelope implementation correctly follows the CLI JSON contract guidelines with normalized success/failure shapes. The use of lifetimes avoids unnecessary allocations, and error propagation through anyhow::Result aligns with the command dispatch guidelines.

src/handlers/workflow.rs (2)

227-257: LGTM! Blocking I/O is correctly offloaded.

The resolve_draft_body_input function now properly uses tokio::task::spawn_blocking for both file reads (line 242-246) and stdin reads (line 249-256), addressing the previous review concern. Error propagation with the double ?? correctly handles both the JoinError and the inner CliInputError.

As per coding guidelines: "Keep blocking SQLite and filesystem work behind tokio::task::spawn_blocking".


259-311: Good test coverage for input validation.

The tests properly verify both error messages and typed error variants via downcast_ref, ensuring the error classification layer can correctly map these to exit codes.

src/auth/flow.rs (2)

383-390: Previous review addressed: redundant clone removed.

The refresh token is now exposed directly without the unnecessary .clone() before .expose_secret().


487-508: Good rollback pattern for credential persistence.

The persist_login_state function correctly implements a rollback pattern: it saves credentials first, then attempts to persist the account. If account persistence fails, it restores or clears the previous credential state. This prevents leaving the system in an inconsistent state.

src/cli_output/tests.rs (2)

611-742: Previous review addressed: environment isolation applied.

The test now properly sets XDG_CONFIG_HOME to a controlled directory and removes HOME (lines 707-708, 731-732) to ensure the subprocess cannot inherit real user configuration. This makes the MissingCredentials error deterministic.


29-544: Comprehensive error contract coverage.

The unit tests thoroughly verify the error classification layer, covering a wide range of error types and their mapping to JSON codes, kinds, and exit codes. Each test validates the complete error structure including the operation field, which ensures the CLI contract remains stable.

Comment thread src/auth/flow.rs
Comment thread src/cli_output/tests.rs
Comment thread src/handlers/config.rs
Comment thread src/handlers/config.rs
Comment thread src/handlers/gmail.rs Outdated
Comment thread src/store/doctor.rs
Comment thread src/store/tests.rs
Copy link
Copy Markdown

@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 `@src/store/connection.rs`:
- Around line 32-50: StoreInitReport::print currently uses multiple println!
calls (lines printing database_path, database_previously_existed,
schema_version, known_migrations, pending_migrations) which is inconsistent with
StoreDoctorReport::print's locked-stdout + write_human pattern and can cause
interleaved output; change StoreInitReport::print to acquire a locked stdout
(e.g., let stdout = io::stdout(); let mut out = stdout.lock();), then replace
println! uses with the same write_human(...) helper calls used by
StoreDoctorReport::print (or call a write_human-like function for each line) and
adapt the call to print_pragmas to accept/write to the locked writer (update
print_pragmas signature or add a print_pragmas_writer variant) so all human
output goes through the locked writer consistently.
🪄 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: 329fcb30-bcef-471f-8841-8f8381ae4f63

📥 Commits

Reviewing files that changed from the base of the PR and between 002e678 and d5605dc.

📒 Files selected for processing (10)
  • src/auth/flow.rs
  • src/auth/tests.rs
  • src/handlers/auth.rs
  • src/handlers/config.rs
  • src/handlers/gmail.rs
  • src/lib.rs
  • src/store/connection.rs
  • src/store/doctor.rs
  • src/store/mod.rs
  • src/store/tests.rs
📜 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). (1)
  • GitHub Check: rust
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.rs: Use anyhow for command dispatch and top-level context in src/lib.rs; prefer typed thiserror errors in Gmail, workflow, and store layers in Rust
Keep error enums local to the layer that owns the failure semantics; do not introduce a repo-wide catch-all error enum unless it clearly reduces total code and cognitive load
For new CLI JSON contracts, normalize success and failure to one top-level shape: { "success": true, "data": ... } for success and { "success": false, "error": { "code": ..., "message": ..., "kind": ..., "operation": ..., "causes": [...] } } for failure
Keep error.code stable and operator-oriented, keep error.kind for deeper subsystem detail, and keep error.causes to an ordered message chain only in JSON error contracts
Do not include debug or backtrace payloads in the JSON error contract; use stderr and Rust backtrace env vars for deep diagnostics instead
Preserve existing human-facing error text unless it is misleading, ambiguous, or missing required operator action
When adding or changing CLI failures, keep exit codes in a small stable bucket set rather than creating one-off codes per variant
Keep blocking SQLite and filesystem work behind tokio::task::spawn_blocking; do not treat running spawn_blocking work as abortable
Add focused error-path tests for every new failure class; if CLI JSON or exit-code behavior changes, add contract tests for the new output and exit mapping in the same pass

Files:

  • src/handlers/config.rs
  • src/handlers/gmail.rs
  • src/store/doctor.rs
  • src/handlers/auth.rs
  • src/store/connection.rs
  • src/store/mod.rs
  • src/auth/tests.rs
  • src/auth/flow.rs
  • src/lib.rs
  • src/store/tests.rs
🔍 Remote MCP Firecrawl

Summary of relevant review facts found (from the PR page scraped via Firecrawl):

  • Architectural scope: large refactor splitting lib.rs, cli_output, auth, and store into focused modules and moving many tests into dedicated files; preserves CLI surface and JSON/human output contracts.

  • Notable reviewer findings / actionable items already surfaced on the PR:

    • Upward dependency: StoreInitReport::print in src/store/connection.rs calls crate::store::doctor::print_pragmas, creating cross-module coupling; reviewer suggested moving pragma rendering into connection or a small shared helper (store::output/pragmas).
    • Path handling edge case: code uses path.display() to build "-wal"/"-shm" companion paths (lossy UTF-8). Suggestion: use OsString (path.as_os_str().to_os_string(); push suffix) to preserve non‑UTF‑8 bytes so hardening handles WAL/SHM correctly. A concrete code snippet was provided.
    • Blocking/async guidance: several places should push blocking filesystem/SQLite work into tokio::task::spawn_blocking (e.g., draft-body/stdin reads, some auth/store calls). Tests and handlers were flagged to use spawn_blocking or be converted to async where appropriate.
    • Test determinism: some tests spawn subprocesses and inherit host XDG/HOME causing nondeterminism; reviewers recommended setting env vars (XDG_CONFIG_HOME, HOME) in Command invocations or using TempDir to isolate.
    • CLI output formatting: gmail handler human-mode printing was flagged for ambiguous spacing; reviewers suggested using tabs or structured output. (PR later notes a commit updating gmail output to use tabs.)
    • Code quality suggestions: large human-formatters (store::doctor.write_human) should be split into small helpers (write_kv/write_kv_opt) to reduce duplication.
  • Tests & CI: author ran cargo fmt, clippy, tests and some CLI runs; automated reviewers ran and reported one failing pre-merge check (docstring coverage below threshold). Multiple automated reviewers (Copilot, coderabbitai) left detailed comments and suggested fixes.

  • Commits/Follow-ups: later commits in the branch mention fixes addressing several reviewer points (refresh-token cloning simplification, config warning on resolve failure, gmail tab-separated output, making draft-body input resolution async, and doctor write_kv helpers). Verify those commits cover the exact change sites during review.

Sources

  • PR page and automated reviewer comments for PR #19 (BjornMelin/mailroom) — scraped via Firecrawl.
🔇 Additional comments (27)
src/handlers/gmail.rs (1)

1-43: LGTM!

The handler correctly uses tokio::task::spawn_blocking for the blocking config::resolve call, uses tab delimiters for human-readable output, and follows the established JSON output contract with print_json_success.

src/handlers/config.rs (2)

5-28: LGTM!

Both handlers use the correct fallback pattern with a warning to stderr when config resolution fails. The warning uses Display-form error only (no debug payload), addressing prior review feedback. The synchronous nature is appropriate since the caller in lib.rs wraps these in spawn_blocking.


39-90: Solid error-path test with proper output contract validation.

The test correctly:

  • Sets XDG_CONFIG_HOME and removes HOME to isolate from host environment
  • Asserts the warning message is present on stderr
  • Validates the JSON success contract (success: true, data.repo_root, data.runtime_root)
  • Confirms no debug payload ({error:#?}) appears in the warning

This addresses the prior review feedback about strengthening error-path tests.

src/handlers/auth.rs (1)

6-39: LGTM!

The handler correctly distinguishes between async OAuth operations (setup, login) and synchronous blocking operations (status, logout). Blocking operations are properly wrapped in spawn_blocking, and config::resolve is also correctly isolated. This aligns with the coding guideline to keep blocking SQLite and filesystem work behind tokio::task::spawn_blocking.

src/lib.rs (2)

10-31: Clean module organization with consistent handler delegation.

The new handlers module provides a well-organized layer for command dispatch. The imports are appropriately scoped and the delegation pattern is consistent across all command types.


91-127: Correct blocking/async segregation in command dispatch.

The refactor correctly wraps synchronous blocking handlers (Audit, Config, Paths, Doctor, Workspace, Store) in spawn_blocking, while async handlers that perform network I/O (Auth, Account, Gmail, Search, etc.) are awaited directly. This follows the coding guideline to keep blocking SQLite and filesystem work behind tokio::task::spawn_blocking.

src/auth/flow.rs (4)

31-168: Well-structured report types with consistent output contract.

All four report types (AuthStatusReport, SetupReport, LoginReport, LogoutReport) follow a consistent pattern with print(json) methods that use print_json_success for JSON mode and key=value format for human mode. The #[derive(Serialize)] enables the JSON contract.


170-188: Good use of thiserror for domain-specific errors.

The AuthError enum follows the coding guideline to use typed thiserror errors in the auth layer. Error variants are descriptive and operator-oriented.


488-532: Blocking SQLite operations correctly isolated in spawn_blocking.

The persist_login_state function properly wraps all blocking operations (directory creation, credential save, store init, account upsert) inside spawn_blocking. The rollback mechanism correctly restores previous credentials if account persistence fails. This addresses the prior review concern about SQLite operations blocking the async runtime.


261-318: Sync functions appropriately designed for spawn_blocking wrapper.

Both status() and logout() are synchronous functions that perform blocking file and SQLite operations. These are designed to be called from spawn_blocking in the handler layer (verified in src/handlers/auth.rs lines 24-35), which correctly isolates the blocking work.

src/auth/tests.rs (4)

23-52: Good coverage of authorization prompt routing logic.

These tests verify the correct behavior of authorization_prompt():

  • Omits prompt when JSON mode with browser opening
  • Routes to stderr JSON when headless JSON mode
  • Routes to stdout text for human output

54-94: Robust edge-case test for logout with missing accounts table.

This test verifies graceful handling when the database exists but lacks the accounts table. It correctly:

  • Sets up credentials before logout
  • Creates a minimal schema without the accounts table
  • Verifies credentials are removed and deactivated_accounts is 0

333-477: Thorough rollback behavior tests for credential persistence.

These three tests comprehensively cover the credential rollback mechanism:

  1. No account upsert when credential save fails
  2. New credentials rolled back (cleared) when store init fails
  3. Previous credentials restored when store init fails

This aligns with the coding guideline for focused error-path tests.


511-667: Comprehensive TCP-based callback listener tests.

The tests verify:

  • OAuth error responses are properly propagated
  • Unrelated requests are ignored until valid callback arrives
  • Split TCP reads across multiple chunks are handled correctly

These tests effectively exercise the loopback callback handling code paths.

src/store/connection.rs (2)

131-151: LGTM!

The read_pragmas function correctly reads SQLite PRAGMAs with appropriate type handling. The explicit type annotations for boolean conversions (foreign_keys, trusted_schema) improve clarity.


159-168: LGTM!

The write_pragmas function is well-designed with the Write trait bound for testability and consistent key=value formatting.

src/store/mod.rs (2)

91-96: LGTM! OsString path handling correctly preserves non-UTF-8 bytes.

The -wal and -shm companion path construction now uses OsString::push instead of format!("{}-wal", path.display()), which preserves raw bytes and avoids lossy UTF-8 conversion.


19-47: LGTM!

The init function properly orchestrates database initialization with correct sequencing: parent directory creation → connection setup → pragma validation → hardening → migrations → final permission hardening. The dual harden_database_permissions calls are intentional to cover files created during migration.

src/store/tests.rs (5)

22-67: LGTM!

The store_init_creates_and_migrates_database test comprehensively validates database initialization, schema version, application ID, and table creation. The test correctly uses TempDir for automatic cleanup.


82-136: LGTM!

The test correctly validates that inspect reads pragma values without modifying them. The clarifying comment (lines 114-119) explaining the synchronous pragma discrepancy between runtime and persisted values addresses the previous review feedback.


209-234: LGTM!

The test validates that harden_database_permissions correctly sets 0o600 permissions on the main database file and both sidecar files (-wal, -shm). The redundant create_dir_all from previous feedback has been removed.


236-525: LGTM!

The migration test is comprehensive, validating the v6→v16 migration round-trip including attachment account scope backfill. It correctly:

  • Seeds realistic fixture data across multiple accounts
  • Executes down migrations in reverse order
  • Verifies attachment account_id backfill correctness
  • Tests post-migration behavior with shared attachment keys

527-740: LGTM!

The v16 sync history comparability migration test correctly validates:

  • Summary row splitting by comparability_key during up migration
  • Summary row merging during down migration
  • Preservation of best_clean_run_id for each comparability tier (tiny/large)
src/store/doctor.rs (4)

9-25: LGTM!

The write_kv and write_kv_opt helpers effectively reduce code duplication in the human output formatting, as suggested in previous reviews.


41-52: LGTM!

The print method correctly locks stdout for human output, maintaining consistency with the pattern and avoiding interleaved output.


451-495: LGTM!

The inspect function correctly handles the absent-database case early and gathers comprehensive diagnostics when the database exists. The blocking I/O concern was addressed at the call sites per previous review feedback.


508-582: LGTM!

The test validates that write_human correctly outputs the expected key=value pairs for sync pacing and sync run summary fields. Good coverage for the human-readable output format.

Comment thread src/store/connection.rs
Comment on lines +32 to +50
impl StoreInitReport {
pub fn print(&self, json: bool) -> Result<()> {
if json {
crate::cli_output::print_json_success(self)?;
} else {
println!("database_path={}", self.database_path.display());
println!(
"database_previously_existed={}",
self.database_previously_existed
);
println!("schema_version={}", self.schema_version);
println!("known_migrations={}", self.known_migrations);
println!("pending_migrations={}", self.pending_migrations);
print_pragmas(&self.pragmas)?;
}

Ok(())
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Inconsistent stdout handling compared to StoreDoctorReport::print.

StoreInitReport::print uses multiple println! calls for human output (lines 37-44), while StoreDoctorReport::print in doctor.rs uses a locked stdout handle with write_human. Using println! without locking can cause interleaved output if other threads write to stdout concurrently, and it's inconsistent with the pattern established elsewhere in this PR.

Consider aligning with the StoreDoctorReport pattern:

♻️ Suggested refactor
 impl StoreInitReport {
     pub fn print(&self, json: bool) -> Result<()> {
         if json {
             crate::cli_output::print_json_success(self)?;
         } else {
-            println!("database_path={}", self.database_path.display());
-            println!(
-                "database_previously_existed={}",
-                self.database_previously_existed
-            );
-            println!("schema_version={}", self.schema_version);
-            println!("known_migrations={}", self.known_migrations);
-            println!("pending_migrations={}", self.pending_migrations);
-            print_pragmas(&self.pragmas)?;
+            let stdout = std::io::stdout();
+            let mut stdout = stdout.lock();
+            writeln!(stdout, "database_path={}", self.database_path.display())?;
+            writeln!(stdout, "database_previously_existed={}", self.database_previously_existed)?;
+            writeln!(stdout, "schema_version={}", self.schema_version)?;
+            writeln!(stdout, "known_migrations={}", self.known_migrations)?;
+            writeln!(stdout, "pending_migrations={}", self.pending_migrations)?;
+            write_pragmas(&mut stdout, &self.pragmas)?;
         }

         Ok(())
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
impl StoreInitReport {
pub fn print(&self, json: bool) -> Result<()> {
if json {
crate::cli_output::print_json_success(self)?;
} else {
println!("database_path={}", self.database_path.display());
println!(
"database_previously_existed={}",
self.database_previously_existed
);
println!("schema_version={}", self.schema_version);
println!("known_migrations={}", self.known_migrations);
println!("pending_migrations={}", self.pending_migrations);
print_pragmas(&self.pragmas)?;
}
Ok(())
}
}
impl StoreInitReport {
pub fn print(&self, json: bool) -> Result<()> {
if json {
crate::cli_output::print_json_success(self)?;
} else {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
writeln!(stdout, "database_path={}", self.database_path.display())?;
writeln!(stdout, "database_previously_existed={}", self.database_previously_existed)?;
writeln!(stdout, "schema_version={}", self.schema_version)?;
writeln!(stdout, "known_migrations={}", self.known_migrations)?;
writeln!(stdout, "pending_migrations={}", self.pending_migrations)?;
write_pragmas(&mut stdout, &self.pragmas)?;
}
Ok(())
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/store/connection.rs` around lines 32 - 50, StoreInitReport::print
currently uses multiple println! calls (lines printing database_path,
database_previously_existed, schema_version, known_migrations,
pending_migrations) which is inconsistent with StoreDoctorReport::print's
locked-stdout + write_human pattern and can cause interleaved output; change
StoreInitReport::print to acquire a locked stdout (e.g., let stdout =
io::stdout(); let mut out = stdout.lock();), then replace println! uses with the
same write_human(...) helper calls used by StoreDoctorReport::print (or call a
write_human-like function for each line) and adapt the call to print_pragmas to
accept/write to the locked writer (update print_pragmas signature or add a
print_pragmas_writer variant) so all human output goes through the locked writer
consistently.

@BjornMelin BjornMelin merged commit d36213e into main Apr 23, 2026
3 checks passed
@BjornMelin BjornMelin deleted the refactor/lib-handlers-and-cli-output branch April 23, 2026 19:03
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