Skip to content

Prompt for local profile during quick onboarding#2389

Merged
ilblackdragon merged 3 commits intostagingfrom
codex/local-profile-choice
Apr 18, 2026
Merged

Prompt for local profile during quick onboarding#2389
ilblackdragon merged 3 commits intostagingfrom
codex/local-profile-choice

Conversation

@serrrfirat
Copy link
Copy Markdown
Collaborator

@serrrfirat serrrfirat commented Apr 13, 2026

Summary

  • add a first-run quick onboarding choice between local and local-sandbox
  • persist the selected profile to bootstrap .env as IRONCLAW_PROFILE
  • make local profiles proactive by default with heartbeat, routines, and hygiene enabled
  • update setup docs and profile tests

⚠️ Breaking change for existing local / local-sandbox users

Both profiles/local.toml and profiles/local-sandbox.toml now set
heartbeat.enabled = true, routines.enabled = true, and
hygiene.enabled = true. Existing users with IRONCLAW_PROFILE=local
(or local-sandbox) will get background tasks on next startup without
explicit opt-in. Previously the profile comment described these as
"disabled server-oriented features".

Migration: Users who want the old behavior can add
heartbeat.enabled = false, routines.enabled = false, and
hygiene.enabled = false to ~/.ironclaw/config.toml.

Tests

  • RUSTFLAGS=-Cdebuginfo=0 cargo test --no-default-features --features libsql local_profile --lib
  • RUSTFLAGS=-Cdebuginfo=0 cargo test --no-default-features --features libsql local_sandbox_enables_sandbox --lib
  • RUSTFLAGS=-Cdebuginfo=0 cargo test --no-default-features --features libsql test_bootstrap_env_vars_ --lib
  • RUSTFLAGS=-Cdebuginfo=0 cargo test --no-default-features --features libsql test_apply_quick_local_profile_ --lib
  • git diff --check

@github-actions github-actions bot added scope: setup Onboarding / setup scope: docs Documentation size: M 50-199 changed lines risk: high Safety, secrets, auth, or critical infrastructure contributor: core 20+ merged PRs labels Apr 13, 2026
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request enables background tasks (heartbeat, routines, and hygiene) by default in the 'local' and 'local-sandbox' profiles. It also enhances the setup wizard's quick mode to prompt users for a deployment profile on their first run and ensures this selection is persisted to the bootstrap environment file. Feedback was provided to improve the maintainability of the profile application logic by encapsulating the backup and restoration of database settings into helper functions.

Comment thread src/setup/wizard.rs Outdated
Comment on lines +1182 to +1203
let prior_database_backend = self.settings.database_backend.clone();
let prior_database_url = self.settings.database_url.clone();
let prior_database_pool_size = self.settings.database_pool_size;
let prior_libsql_path = self.settings.libsql_path.clone();
let prior_libsql_url = self.settings.libsql_url.clone();

crate::config::set_runtime_env("IRONCLAW_PROFILE", profile);
crate::config::profile::apply_profile(&mut self.settings)
.map_err(|e| SetupError::Config(e.to_string()))?;

if prior_database_backend.is_some()
|| prior_database_url.is_some()
|| prior_database_pool_size.is_some()
|| prior_libsql_path.is_some()
|| prior_libsql_url.is_some()
{
self.settings.database_backend = prior_database_backend;
self.settings.database_url = prior_database_url;
self.settings.database_pool_size = prior_database_pool_size;
self.settings.libsql_path = prior_libsql_path;
self.settings.libsql_url = prior_libsql_url;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To improve maintainability and adhere to the project's encapsulation standards, the logic for saving and restoring database settings should be moved into helper functions. This avoids exposing complex internal state transitions and provides a cleaner interface for the caller, consistent with the rule to encapsulate complex data structures and logic within helper functions.

Suggested change
let prior_database_backend = self.settings.database_backend.clone();
let prior_database_url = self.settings.database_url.clone();
let prior_database_pool_size = self.settings.database_pool_size;
let prior_libsql_path = self.settings.libsql_path.clone();
let prior_libsql_url = self.settings.libsql_url.clone();
crate::config::set_runtime_env("IRONCLAW_PROFILE", profile);
crate::config::profile::apply_profile(&mut self.settings)
.map_err(|e| SetupError::Config(e.to_string()))?;
if prior_database_backend.is_some()
|| prior_database_url.is_some()
|| prior_database_pool_size.is_some()
|| prior_libsql_path.is_some()
|| prior_libsql_url.is_some()
{
self.settings.database_backend = prior_database_backend;
self.settings.database_url = prior_database_url;
self.settings.database_pool_size = prior_database_pool_size;
self.settings.libsql_path = prior_libsql_path;
self.settings.libsql_url = prior_libsql_url;
}
let prior_db_settings = self.settings.backup_database_config();
crate::config::set_runtime_env("IRONCLAW_PROFILE", profile);
crate::config::profile::apply_profile(&mut self.settings)
.map_err(|e| SetupError::Config(e.to_string()))?;
self.settings.restore_database_config(prior_db_settings);
References
  1. Encapsulate complex data structures and logic within a helper function, and expose a simpler interface or data structure tailored to the caller's needs.

Extract backup_database_config() and restore_database_config() on Settings
to replace inline field-by-field save/restore in the setup wizard. Cleaner
interface, consistent with project encapsulation standards.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added scope: config Configuration size: L 200-499 changed lines and removed size: M 50-199 changed lines labels Apr 13, 2026
Copy link
Copy Markdown
Collaborator

@henrypark133 henrypark133 left a comment

Choose a reason for hiding this comment

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

Review: Local profile prompt during quick onboarding (Risk: High)

Good UX improvement — first-run users get a clear choice between local and local-sandbox. The profile persistence to ~/.ironclaw/.env is correct (needed before DB connection). However, the profile TOML changes have broader implications.

Positives:

  • step_quick_local_profile correctly skips when IRONCLAW_PROFILE is already set
  • Profile name validated against a fixed allowlist (local, local-sandbox)
  • bootstrap_env_vars() extraction from write_bootstrap_env() is clean
  • README spec updated with the new flow

Concerning: Profile TOML changes silently enable background tasks for existing users [Architecture]

Files: profiles/local.toml, profiles/local-sandbox.toml
Both profiles flip heartbeat, routines, and hygiene from enabled = false to enabled = true. Existing users with IRONCLAW_PROFILE=local get background agent runs on next startup without opt-in. The profile comment previously said it "disables server-oriented features (gateway, heartbeat, routines, sandbox)" — this is a semantic contract change.
Suggestion: Consider documenting this as a breaking change in the PR description, or mentioning it in release notes so existing local-profile users are aware.

Concerning: DatabaseConfigBackup adds unnecessary complexity [Architecture]

File: src/settings.rs (new struct), src/setup/wizard.rs
The quick-mode flow already does step1_settings = self.settings.clone() then self.settings.merge_from(&step1_settings) after try_load_existing_settings(), which preserves wizard-chosen values over DB/profile values. The backup/restore pattern on top of this is redundant.
Suggestion: Consider reordering profile selection before auto_setup_database() instead — the existing merge_from pattern handles the rest.

Minor: Missing caller-level tests [Testing]

The two tests (test_bootstrap_env_vars_include_selected_deployment_profile, test_bootstrap_env_vars_do_not_persist_unselected_profile) cover the env var accessor but don't verify the DB-config preservation invariant through apply_quick_local_profile.

Convention notes:

  • Profile comment headers in the TOMLs are updated to reflect the new defaults
  • try_load_existing_settings returning bool is a clean change

Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Review: Prompt for local profile during quick onboarding

+202/-28, 6 files. Adds first-run profile selection (local vs local-sandbox) and enables heartbeat/routines/hygiene by default.

Items to address

  1. try_load_existing_settings cfg-gated return value is fragile. The loaded variable is initialized false and only mutated inside #[cfg(feature = "postgres")] and #[cfg(feature = "libsql")] blocks. If neither feature is enabled, it always returns false, triggering the profile prompt unconditionally. The variable shadowing makes this easy to break. Add a comment or make the logic more explicit.

  2. Behavior change for existing local profile users. Enabling heartbeat/routines/hygiene by default in local.toml means existing users who upgrade suddenly get background tasks. Worth a changelog note or migration comment.

Minor

  1. apply_quick_local_profile match arm _ => QUICK_PROFILE_LOCAL_SANDBOX silently maps any non-zero index to sandbox. Since select_one only returns 0 or 1 here, it's safe, but 1 => ... _ => unreachable!() is clearer about intent.

  2. bootstrap_env_vars extraction is clean — good refactor for testability.

Overall solid. Address or acknowledge the two items above before merging.

…ove backup/restore (#2389)

- Remove `DatabaseConfigBackup` struct and backup/restore methods;
  reorder quick-mode flow so profile selection runs before
  `auto_setup_database()`, letting the existing clone→try_load→merge_from
  pattern preserve wizard-chosen DB settings naturally (henrypark133).
- Add comment explaining the cfg-gated `loaded` variable shadowing in
  `try_load_existing_settings` (zmanian #1).
- Change catch-all `_ =>` to explicit `1 => ... _ => unreachable!()`
  in profile match arm (zmanian #3).
- Add caller-level test verifying profile application preserves DB config
  through the merge_from cycle (henrypark133 testing feedback).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@serrrfirat
Copy link
Copy Markdown
Collaborator Author

Addressed review feedback

@henrypark133:

  • Removed DatabaseConfigBackup — reordered quick-mode flow so step_quick_local_profile() runs before auto_setup_database(). The existing clone → try_load → merge_from pattern now naturally preserves DB settings, eliminating the backup/restore struct entirely.
  • Added caller-level test (test_apply_quick_local_profile_sets_profile_and_preserves_db_config_on_merge) that verifies the DB-config preservation invariant through the full apply_quick_local_profile → merge_from cycle.

@zmanian:

  • cfg-gated loaded variable — added a comment block explaining the intentional shadowing pattern and the behavior when neither postgres nor libsql feature is enabled.
  • Match arm — changed _ => QUICK_PROFILE_LOCAL_SANDBOX to 1 => ... _ => unreachable!("select_one only returns 0 or 1 for a two-option menu").

Both reviewers:

  • Breaking change note — updated PR description with migration guidance for existing local/local-sandbox users who get background tasks enabled by default.

Copy link
Copy Markdown
Collaborator

@henrypark133 henrypark133 left a comment

Choose a reason for hiding this comment

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

Review: Local profile prompt during quick onboarding

No verified findings in the current diff. The flow now applies the selected local profile before quick database setup, persists the bootstrap profile choice explicitly, and re-merges fresh step-1 settings after loading prior DB state so stale partial-onboarding data no longer wins.

@ilblackdragon ilblackdragon merged commit 0ab2f13 into staging Apr 18, 2026
15 checks passed
@ilblackdragon ilblackdragon deleted the codex/local-profile-choice branch April 18, 2026 04:08
@ilblackdragon ilblackdragon mentioned this pull request Apr 18, 2026
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: high Safety, secrets, auth, or critical infrastructure scope: config Configuration scope: docs Documentation scope: setup Onboarding / setup size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants