Skip to content

feat(cli): rename pf config to pf daemons and add pf settings subcommand#471

Merged
jdx merged 5 commits into
jdx:mainfrom
gaojunran:focal-aardvark-clean
Jun 7, 2026
Merged

feat(cli): rename pf config to pf daemons and add pf settings subcommand#471
jdx merged 5 commits into
jdx:mainfrom
gaojunran:focal-aardvark-clean

Conversation

@gaojunran

@gaojunran gaojunran commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added new daemons command for managing daemon configurations (replaces config)
    • Added new settings command with list, get, and set subcommands for configuration management
    • daemons add/remove now support --local and --project flags to target specific config files
    • settings set supports --global, --local, and --project flags for scope control
    • Added ability to reload configuration at runtime
  • Documentation

    • Updated CLI documentation to reflect new command structure and available options

…xtract shared path resolution

- Rename pf config -> pf daemons (alias daemon), remove old cfg alias
- Rename src/cli/config/ -> src/cli/daemons/, struct Config -> Daemons
- pf daemons (no subcommand) now lists configured daemons (id + run)
- Add pf settings subcommand (list, get, set) with --local/--project/--global
- Add enum validation for log_level and log_file_level settings
- Add --local/--project flags to daemons add and daemons remove
- Extract shared resolve_project_config_path into src/cli/daemons/mod.rs
- Add IpcRequest::ReloadConfig for supervisor config reload after set
- Change settings() from OnceLock to RwLock<Option<Settings>> for reload
- Add SettingsLogsPartial with has_any_set/is_empty for log retention
- Fix clippy: unwrap_or_default in logs.rs, Box<Add> in DaemonsCommand
- Update tests: pitchfork config -> pitchfork daemons
- Regenerate docs via mise run render
@gaojunran gaojunran changed the title feat(settings): rename pf config to pf daemons and add pf settings subcommand feat(cli): rename pf config to pf daemons and add pf settings subcommand Jun 7, 2026
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ddee518e-b454-4d59-b865-dbb6aef9a6d7

📥 Commits

Reviewing files that changed from the base of the PR and between 761651c and bfd5780.

📒 Files selected for processing (15)
  • build/generate_settings.rs
  • docs/cli/commands.json
  • docs/cli/index.md
  • pitchfork.usage.kdl
  • src/cli/list.rs
  • src/cli/settings.rs
  • src/cli/start.rs
  • src/cli/status.rs
  • src/env.rs
  • src/proxy/server.rs
  • src/supervisor/lifecycle.rs
  • src/supervisor/mod.rs
  • src/template.rs
  • src/web/routes/api/daemons.rs
  • src/web/routes/api/proxies.rs
✅ Files skipped from review due to trivial changes (5)
  • src/supervisor/mod.rs
  • src/env.rs
  • docs/cli/index.md
  • src/template.rs
  • src/supervisor/lifecycle.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • pitchfork.usage.kdl
  • src/cli/settings.rs

📝 Walkthrough

Walkthrough

This PR refactors the settings infrastructure to support runtime reload, consolidates daemon and settings management under new CLI commands, and adapts the codebase to work with Arc-backed cached settings passed by reference instead of static references.

Changes

Settings reload and CLI command consolidation

Layer / File(s) Summary
Settings reload: RwLock cache and generator emission
build/generate_settings.rs
Generator now emits RwLock<Option<Arc<Settings>>> for process-lifetime cache with a settings() fast-path read accessor and new reload_settings() function; adds per-nested-field *_is_empty serde skip helpers and has_any_set()/is_empty() methods to all *Partial types to avoid serializing empty objects.
IPC reload protocol
src/ipc/mod.rs, src/ipc/client.rs, src/supervisor/ipc_handlers.rs
Extends IPC wire protocol with ReloadConfig request and ConfigReloaded response, adds IpcClient::reload_config() method with graceful error handling, and implements supervisor handler that runs settings reload and logging reapplication in a blocking task.
Daemons CLI module
src/cli/daemons/mod.rs, src/cli/daemons/add.rs, src/cli/daemons/remove.rs
Introduces new daemons command module with resolve_project_config_path() helper for shared --local/--project targeting, consolidates Add and Remove subcommands with updated config resolution and persistence logic, removes old config module structure.
Settings CLI command
src/cli/settings.rs
Implements new pitchfork settings command with list, get, and set subcommands; includes metadata-driven key validation with "did you mean" suggestions, type-aware value parsing, config file persistence, and best-effort supervisor reload notification.
Top-level CLI wiring
src/cli/mod.rs
Replaces Config with Daemons in modules and Commands enum, adds Settings module and variant, and updates run() dispatcher to route to new command handlers.
CLI usage specs and documentation
pitchfork.usage.kdl, docs/cli/commands.json, docs/cli/*.md, docs/public/schema.json
Updates usage specifications and command definitions to introduce daemons/settings commands; adds comprehensive documentation pages for subcommands with examples and flag descriptions; removes old config docs; updates index and schema defaults.
Settings borrowing refactor
src/cli/list.rs, src/cli/start.rs, src/cli/status.rs, src/env.rs, src/proxy/server.rs, src/supervisor/lifecycle.rs, src/supervisor/mod.rs, src/template.rs, src/web/routes/*.rs
Refactors throughout to pass Arc<Settings> by reference (&s) rather than by value, aligns with new RwLock-based cache, simplifies spawn_blocking error handling in logs route.
Test suite migrations
test/config.bats, test/port.bats
Updates all daemon creation invocations to use pitchfork daemons add instead of pitchfork config add, preserving expected TOML output validation across all test scenarios.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A rabbit hops through settings anew,
With locks and arcs in every view,
CLI commands dance and reflow,
Daemons parade, settings show,
Reloads bloom—the garden grows! 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main changes: renaming pf config to pf daemons and adding a new pf settings subcommand, which are the primary objectives reflected across the extensive changeset.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@greptile-apps

greptile-apps Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR renames the pf config command to pf daemons and introduces a new pf settings subcommand (list, get, set) for reading and writing pitchfork configuration values. It also wires up runtime settings reload via a new ReloadConfig IPC message so the running supervisor picks up changes immediately.

  • build/generate_settings.rs migrates the global settings singleton from OnceLock<Settings> to RwLock<Option<Arc<Settings>>>, fixing the use-after-free reported in the previous review; callers now receive an Arc clone taken while the lock is held, so old references stay alive until dropped.
  • src/cli/settings.rs adds the full settings list/get/set implementation; the logs group is handled in both get_setting_value and apply_setting_to_partial, closing the silent-empty-output gap noted in the prior review.
  • Test files (config.bats, port.bats) are updated to use pitchfork daemons but no new tests cover the settings command paths.

Confidence Score: 5/5

Safe to merge — the two issues flagged in earlier review rounds (memory safety in settings reload and the missing logs group handlers) are both addressed in this revision.

The settings reload now uses Arc-clone-under-lock so old references are never freed while in use. Both get_setting_value and apply_setting_to_partial handle the logs group. The command rename from config to daemons is mechanical and backed by updated tests. No new unsafe patterns or logic gaps were found.

No files require special attention. The new src/cli/settings.rs is the largest addition but is straightforward dispatch code; build/generate_settings.rs carries the memory-safety fix and looks correct.

Important Files Changed

Filename Overview
build/generate_settings.rs Migrates global SETTINGS from OnceLock to RwLock<Option<Arc>> with correct Arc-clone-under-lock pattern; previous use-after-free P0 is resolved
src/cli/settings.rs New settings command with list/get/set subcommands; logs group now correctly handled in both get_setting_value and apply_setting_to_partial, addressing the previous P1 gap
src/cli/daemons/mod.rs Replaces config module with daemons; includes resolve_project_config_path helper shared with settings set; logic mirrors removed config module cleanly
src/cli/daemons/add.rs --cron-immediate flag is present in the struct and wired through to PitchforkTomlCron; --local/--project flags added and validated
src/ipc/client.rs Adds reload_config IPC call; correctly silences errors when supervisor is not running
src/supervisor/ipc_handlers.rs Handles ReloadConfig IPC request via spawn_blocking, calling reload_settings then apply_settings
test/config.bats All test invocations updated from pitchfork config to pitchfork daemons; no new settings tests added

Reviews (5): Last reviewed commit: "fix(settings): replace Box::leak with Ar..." | Re-trigger Greptile

Comment thread build/generate_settings.rs
Comment thread src/cli/daemons/add.rs
…-immediate and logs group

- Replace unsafe raw pointer in settings() with Box::leak to eliminate
  use-after-free when reload_settings() replaces the value (PR review P1)
- Add --cron-immediate flag to 'daemons add' for cron immediate trigger
  (PR review P2)
- Add 'logs' group handler to get_setting_value and apply_setting_to_partial
  so 'pf settings get/set logs.*' works correctly

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
src/settings.rs (1)

43-212: 🏗️ Heavy lift

Generate is_empty() / has_any_set() instead of hardcoding field lists

These manual checks are tightly coupled to generated partial structs. If a new field is added later and not reflected here, the group can be treated as empty and skipped during serialization even when it has data. Consider generating these methods from settings.toml alongside the partial structs.

🤖 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 `@src/settings.rs` around lines 43 - 212, The hardcoded
is_empty()/has_any_set() implementations (impl SettingsPartialGroup for
SettingsPartial, SettingsApiPartial, SettingsGeneralPartial, SettingsIpcPartial,
SettingsLogsPartial, SettingsProxyPartial, SettingsSupervisorPartial,
SettingsTuiPartial, SettingsWebPartial) risk falling out of sync with generated
partial structs when fields are added; replace these manual field-list checks by
generating the is_empty() and has_any_set() methods alongside the partial struct
generation (from settings.toml) so the generated code inspects each generated
Option/field automatically, and update the generator to emit implementations for
SettingsPartialGroup (or a derived impl) for each Partial type rather than
maintaining the lists by hand.
🤖 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 `@build/generate_settings.rs`:
- Around line 173-201: The current settings() unsafely returns &'static Settings
from SETTINGS: RwLock<Option<Settings>> which can dangle when reload_settings()
replaces/drops the inner value; change the cache to store shared ownership (e.g.
RwLock<Option<Arc<Settings>>>), update settings() to return Arc<Settings> (clone
the Arc for the fast path and when initializing via Settings::load() wrap the
new Settings in Arc), and update reload_settings() to swap in a new
Arc<Settings> instead of replacing a raw Settings so previously handed-out Arcs
remain valid and no unsafe pointer casts are needed.

In `@src/cli/daemons/add.rs`:
- Around line 133-141: Add::run currently performs blocking filesystem I/O
(calls to config_path.exists(), config_path.canonicalize(),
std::fs::create_dir_all(parent), and synchronous PitchforkToml::read /
pt.write), so convert these to async Tokio operations: replace path ops with
tokio::fs equivalents (tokio::fs::create_dir_all, tokio::fs::metadata / exists
pattern, tokio::fs::canonicalize) or wrap the synchronous PitchforkToml::read
and pt.write in tokio::task::spawn_blocking until PitchforkToml is made async;
ensure PitchforkToml::new usage and any code that depends on config_path is
updated to await async calls and, where concurrent IO is needed, coordinate with
tokio::select! to avoid blocking the runtime.

In `@src/cli/daemons/mod.rs`:
- Around line 42-52: The code is performing blocking filesystem I/O inside async
flows; change the sync resolver and checks to async: make
resolve_project_config_path async (e.g., resolve_project_config_path_async) and
convert PitchforkToml::list_paths to an async variant that returns paths without
calling blocking Path::exists; replace all
Path::exists/canonicalize/create_dir_all usages with Tokio async equivalents
(tokio::fs::try_exists or tokio::fs::metadata + .is_ok(),
tokio::fs::canonicalize, tokio::fs::create_dir_all) and update callsites in
add.rs, remove.rs and settings.rs to await these calls and make those functions
async (or use futures::stream / join_all to filter paths concurrently), ensuring
any path filtering logic (the block using exists_filter and
is_project_config_path) uses async checks rather than p.exists().

In `@src/cli/settings.rs`:
- Around line 146-156: SetCmd::run is performing blocking filesystem I/O
(config_path.exists(), std::fs::create_dir_all) and then calls synchronous
PitchforkToml::read / PitchforkToml::write_unlocked which use
std::fs::read_to_string and xx::file::write; change all of these to async to
avoid blocking the tokio runtime: either (A) update PitchforkToml to provide
async equivalents (e.g., async fn read_async / write_async that use
tokio::fs::read_to_string, tokio::fs::create_dir_all and tokio::fs::write), and
replace config_path.exists()/create_dir_all with tokio::fs::metadata/try_exists
or tokio::fs::create_dir_all inside SetCmd::run, or (B) keep PitchforkToml sync
but offload the entire blocking sequence (the calls to PitchforkToml::read /
write_unlocked and any std::fs ops) into tokio::task::spawn_blocking and await
its JoinHandle from SetCmd::run; reference SetCmd::run, PitchforkToml::read,
PitchforkToml::write_unlocked when making the changes.

In `@src/ipc/client.rs`:
- Around line 419-433: The reload_config() implementation currently treats
transport errors from self.request(IpcRequest::ReloadConfig).await as failures,
violating the "best-effort" doc; modify reload_config so that any Err returned
by self.request(...) is handled the same way as IpcResponse::Error (log a
debug/info message like "config reload skipped: {err}" and return Ok(())) and
only treat unexpected successful responses as an Err using
Self::unexpected_response("ConfigReloaded", &rsp).into(); keep references to the
existing IpcResponse::ConfigReloaded and IpcResponse::Error match arms and
update the code path that awaits self.request accordingly.

In `@src/supervisor/ipc_handlers.rs`:
- Around line 116-119: The ReloadConfig branch runs
crate::settings::reload_settings() inline but Settings::load uses blocking
std::fs::read_to_string which can stall Tokio worker threads; update the
IpcRequest::ReloadConfig handling to offload blocking work to a blocking thread
(e.g., use tokio::task::spawn_blocking) and await it before calling
crate::logger::apply_settings(); specifically wrap the call to
crate::settings::reload_settings() (and any heavyweight sync config parsing like
Settings::load) inside spawn_blocking and then call
crate::logger::apply_settings() on the async task’s completion to avoid blocking
the Tokio runtime.

---

Nitpick comments:
In `@src/settings.rs`:
- Around line 43-212: The hardcoded is_empty()/has_any_set() implementations
(impl SettingsPartialGroup for SettingsPartial, SettingsApiPartial,
SettingsGeneralPartial, SettingsIpcPartial, SettingsLogsPartial,
SettingsProxyPartial, SettingsSupervisorPartial, SettingsTuiPartial,
SettingsWebPartial) risk falling out of sync with generated partial structs when
fields are added; replace these manual field-list checks by generating the
is_empty() and has_any_set() methods alongside the partial struct generation
(from settings.toml) so the generated code inspects each generated Option/field
automatically, and update the generator to emit implementations for
SettingsPartialGroup (or a derived impl) for each Partial type rather than
maintaining the lists by hand.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 48e1b132-b3be-484e-b4ce-278c7c2661c2

📥 Commits

Reviewing files that changed from the base of the PR and between 072c69b and 6128ae3.

📒 Files selected for processing (29)
  • build/generate_settings.rs
  • docs/cli/commands.json
  • docs/cli/config.md
  • docs/cli/config/remove.md
  • docs/cli/daemons.md
  • docs/cli/daemons/add.md
  • docs/cli/daemons/remove.md
  • docs/cli/index.md
  • docs/cli/settings.md
  • docs/cli/settings/get.md
  • docs/cli/settings/list.md
  • docs/cli/settings/set.md
  • docs/cli/sponsors.md
  • docs/public/schema.json
  • pitchfork.usage.kdl
  • src/cli/config/mod.rs
  • src/cli/config/remove.rs
  • src/cli/daemons/add.rs
  • src/cli/daemons/mod.rs
  • src/cli/daemons/remove.rs
  • src/cli/mod.rs
  • src/cli/settings.rs
  • src/ipc/client.rs
  • src/ipc/mod.rs
  • src/settings.rs
  • src/supervisor/ipc_handlers.rs
  • src/web/routes/logs.rs
  • test/config.bats
  • test/port.bats
💤 Files with no reviewable changes (5)
  • docs/cli/config/remove.md
  • docs/cli/sponsors.md
  • src/cli/config/mod.rs
  • docs/cli/config.md
  • src/cli/config/remove.rs

Comment thread build/generate_settings.rs Outdated
Comment thread src/cli/daemons/add.rs
Comment thread src/cli/daemons/mod.rs Outdated
Comment on lines +42 to +52
let paths = PitchforkToml::list_paths();
let project_paths: Vec<_> = paths
.iter()
.filter(|p| {
if exists_filter {
p.exists() && is_project_config_path(p)
} else {
is_project_config_path(p)
}
})
.collect();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all resolver and caller touchpoints before changing signature to async.
# Expected: find resolve_project_config_path definition + all callsites and sync existence/canonicalize usage.
rg -n -C2 'fn resolve_project_config_path|resolve_project_config_path\(' src/cli/daemons/mod.rs src/cli/daemons/add.rs src/cli/daemons/remove.rs src/cli/settings.rs
rg -n -C2 '\.exists\(|\.canonicalize\(|std::fs::create_dir_all' src/cli/daemons/mod.rs src/cli/daemons/add.rs src/cli/daemons/remove.rs src/cli/settings.rs

Repository: endevco/pitchfork

Length of output: 3946


Refactor project-config resolution + callers to eliminate synchronous filesystem I/O in async code
resolve_project_config_path in src/cli/daemons/mod.rs is sync, and the resolver pipeline uses p.exists() in PitchforkToml::list_paths() filtering; add/remove and parts of settings also call blocking exists(), canonicalize(), and std::fs::create_dir_all(...) while executing in async command flows.

Current relevant snippet
let paths = PitchforkToml::list_paths();
let project_paths: Vec<_> = paths
    .iter()
    .filter(|p| {
        if exists_filter {
            p.exists() && is_project_config_path(p)
        } else {
            is_project_config_path(p)
        }
    })
    .collect();
  • Convert the resolver/existence filtering to Tokio async filesystem APIs.
  • Update all callsites (src/cli/daemons/add.rs, src/cli/daemons/remove.rs, src/cli/settings.rs) to replace Path::exists(), Path::canonicalize(), and std::fs::create_dir_all(...) with Tokio async equivalents so **/*.rs no longer performs blocking I/O in async flows.
🤖 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 `@src/cli/daemons/mod.rs` around lines 42 - 52, The code is performing blocking
filesystem I/O inside async flows; change the sync resolver and checks to async:
make resolve_project_config_path async (e.g., resolve_project_config_path_async)
and convert PitchforkToml::list_paths to an async variant that returns paths
without calling blocking Path::exists; replace all
Path::exists/canonicalize/create_dir_all usages with Tokio async equivalents
(tokio::fs::try_exists or tokio::fs::metadata + .is_ok(),
tokio::fs::canonicalize, tokio::fs::create_dir_all) and update callsites in
add.rs, remove.rs and settings.rs to await these calls and make those functions
async (or use futures::stream / join_all to filter paths concurrently), ensuring
any path filtering logic (the block using exists_filter and
is_project_config_path) uses async checks rather than p.exists().

Source: Coding guidelines

Comment thread src/cli/settings.rs Outdated
Comment thread src/ipc/client.rs
Comment thread src/supervisor/ipc_handlers.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/cli/daemons/add.rs (1)

132-140: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Replace blocking FS calls inside Add::run with Tokio-compatible I/O.

This async path still performs blocking filesystem operations (exists, create_dir_all, canonicalize, plus sync PitchforkToml::read/pt.write), which can block Tokio worker threads.

Suggested direction
-        let mut pt = if config_path.exists() {
+        let mut pt = if tokio::fs::try_exists(&config_path).await? {
             PitchforkToml::read(&config_path)?
         } else {
             if let Some(parent) = config_path.parent() {
-                std::fs::create_dir_all(parent).map_err(|e| {
+                tokio::fs::create_dir_all(parent).await.map_err(|e| {
                     miette::miette!(
                         "Failed to create config directory {}: {e}",
                         parent.display()
                     )
                 })?;
             }
             PitchforkToml::new(config_path.clone())
         };
@@
-        let canonical_path = config_path
-            .canonicalize()
-            .unwrap_or_else(|_| config_path.clone());
+        let canonical_path = tokio::fs::canonicalize(&config_path)
+            .await
+            .unwrap_or_else(|_| config_path.clone());

For PitchforkToml::read / pt.write, move to async internals or isolate via tokio::task::spawn_blocking until async APIs exist.

As per coding guidelines, **/*.rs: “All I/O operations must be async using Tokio; use tokio::select! for concurrent operations”. Based on learnings, the same Tokio async-I/O rule applies to **/*.rs.

Also applies to: 225-227, 276-276

🤖 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 `@src/cli/daemons/add.rs` around lines 132 - 140, The Add::run async method
uses blocking filesystem calls (config_path.exists, std::fs::create_dir_all,
std::fs::canonicalize) and synchronous PitchforkToml::read / pt.write which can
block Tokio threads; replace these with Tokio-compatible operations by using
tokio::fs equivalents (tokio::fs::metadata/is_dir or tokio::fs::try_exists,
tokio::fs::create_dir_all, tokio::fs::canonicalize) for path checks/creation and
offload PitchforkToml::read and pt.write to tokio::task::spawn_blocking (or make
their internals async) so all I/O inside Add::run is non-blocking; apply the
same changes to the other occurrences noted around lines 225-227 and 276.

Sources: Coding guidelines, Learnings

🤖 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.

Duplicate comments:
In `@src/cli/daemons/add.rs`:
- Around line 132-140: The Add::run async method uses blocking filesystem calls
(config_path.exists, std::fs::create_dir_all, std::fs::canonicalize) and
synchronous PitchforkToml::read / pt.write which can block Tokio threads;
replace these with Tokio-compatible operations by using tokio::fs equivalents
(tokio::fs::metadata/is_dir or tokio::fs::try_exists, tokio::fs::create_dir_all,
tokio::fs::canonicalize) for path checks/creation and offload
PitchforkToml::read and pt.write to tokio::task::spawn_blocking (or make their
internals async) so all I/O inside Add::run is non-blocking; apply the same
changes to the other occurrences noted around lines 225-227 and 276.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b3b32890-4928-493c-a94d-4132d8a75faf

📥 Commits

Reviewing files that changed from the base of the PR and between 6128ae3 and 00e2aa4.

📒 Files selected for processing (6)
  • build/generate_settings.rs
  • docs/cli/commands.json
  • docs/cli/daemons/add.md
  • pitchfork.usage.kdl
  • src/cli/daemons/add.rs
  • src/cli/settings.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • pitchfork.usage.kdl
  • build/generate_settings.rs
  • docs/cli/commands.json
  • src/cli/settings.rs

…wn_blocking JoinError

- Remove SettingsPartialGroup trait and all manual has_any_set/is_empty
  implementations from src/settings.rs (now generated by codegen)
- Add #[allow(dead_code)] to generated has_any_set/is_empty methods
- Fix E0277: tokio::task::JoinError doesn't implement miette::Diagnostic
  - Replace .await?? with .await.into_diagnostic()?.map_err(...)
  - Add IntoDiagnostic import to add.rs, remove.rs, settings.rs
  - Clone paths before moving into spawn_blocking closures
- Add sponsors command restoration to src/cli/mod.rs
- Use spawn_blocking + into_diagnostic in ipc_handlers.rs for ReloadConfig
- Make resolve_project_config_path and resolve_config_path async
- Convert CLI file I/O to tokio::fs (try_exists, create_dir_all, canonicalize)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
build/generate_settings.rs (1)

169-169: 💤 Low value

Doc comment mentions "RwLock" but actual type differs.

The comment states "RwLock to support runtime reload" but the actual type is RwLock<Option<&'static Settings>> using a Box::leak approach. Consider updating the comment to accurately describe the leaky singleton pattern.

-        /// Global settings instance (RwLock<Arc> to support runtime reload)
+        /// Global settings instance using leaky singleton pattern for runtime reload
🤖 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 `@build/generate_settings.rs` at line 169, Update the doc comment for the
global settings instance to reflect the actual implementation: replace the
incorrect "RwLock<Arc> to support runtime reload" wording with a description of
the leaky singleton pattern used (RwLock<Option<&'static Settings>> created via
Box::leak), mentioning that the settings are stored as a leaked &'static
reference wrapped in an Option inside an RwLock to allow runtime reload
semantics; remove any reference to Arc and explain Box::leak usage and why the
Option is present for reload.
🤖 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 `@build/generate_settings.rs`:
- Around line 679-681: The generated has_any_set() function can be empty when
the has_any_set_checks iterator yields no items; update the code generation for
has_any_set to handle the empty case by emitting a default false expression when
#(`#has_any_set_checks`)||* would produce nothing. Concretely, change the
expansion logic that builds pub fn has_any_set(&self) -> bool {
#(`#has_any_set_checks`)||* } so that if has_any_set_checks is empty it emits
"false" (or wraps the joined checks with a fallback like
(#(`#has_any_set_checks`)||* false)), ensuring has_any_set always returns a bool;
adjust the generator that defines has_any_set_checks accordingly.

---

Nitpick comments:
In `@build/generate_settings.rs`:
- Line 169: Update the doc comment for the global settings instance to reflect
the actual implementation: replace the incorrect "RwLock<Arc> to support runtime
reload" wording with a description of the leaky singleton pattern used
(RwLock<Option<&'static Settings>> created via Box::leak), mentioning that the
settings are stored as a leaked &'static reference wrapped in an Option inside
an RwLock to allow runtime reload semantics; remove any reference to Arc and
explain Box::leak usage and why the Option is present for reload.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a0925475-d0d2-465c-b43d-6fb123bcd894

📥 Commits

Reviewing files that changed from the base of the PR and between 00e2aa4 and 6ed0c36.

📒 Files selected for processing (8)
  • build/generate_settings.rs
  • src/cli/daemons/add.rs
  • src/cli/daemons/mod.rs
  • src/cli/daemons/remove.rs
  • src/cli/mod.rs
  • src/cli/settings.rs
  • src/ipc/client.rs
  • src/supervisor/ipc_handlers.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/supervisor/ipc_handlers.rs
  • src/ipc/client.rs
  • src/cli/daemons/mod.rs
  • src/cli/daemons/remove.rs
  • src/cli/settings.rs

Comment thread build/generate_settings.rs
junrangao added 2 commits June 7, 2026 19:53
…erged in spawn_blocking

- Fix potential invalid generated code when has_any_set_checks is empty:
  use  as fallback instead of empty token stream
- Wrap PitchforkToml::all_merged() in spawn_blocking in daemons/mod.rs
  to avoid blocking async runtime with sync filesystem I/O
- Use method reference instead of redundant closure (clippy)
…after-free

Replace RwLock<Option<&'static Settings>> with RwLock<Option<Arc<Settings>>>
and return Arc<Settings> from settings(). This eliminates the use-after-free
risk: when reload_settings() replaces the value, old Arc references remain
valid until all holders drop them. No more leaked memory on reload.

Update all call sites that pass settings() to functions expecting &Settings
to use &s (Arc deref) instead of s. Fix E0716 temporary value errors by
binding Arc to a let variable before accessing fields.
@jdx jdx merged commit b730a10 into jdx:main Jun 7, 2026
6 checks passed
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