From 0d9b300aaf0a6f058a75ad8d1fcd8066b0f5ecbc Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Tue, 10 Mar 2026 19:26:43 -0400 Subject: [PATCH] Remove --no-dotenv flag, always load ~/.arc/.env, never load ./.env The local .env loading was unnecessary since arc install writes all config to ~/.arc/.env. Simplifies dotenv behavior to unconditionally load from the home directory only. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/reference/cli.mdx | 3 +- lib/crates/arc-agent/src/cli.rs | 1 - lib/crates/arc-cli/src/main.rs | 12 +++----- lib/crates/arc-cli/tests/cli.rs | 53 ++++++++++++++++----------------- 4 files changed, 30 insertions(+), 39 deletions(-) diff --git a/docs/reference/cli.mdx b/docs/reference/cli.mdx index 7f0034dce..8e0540a68 100644 --- a/docs/reference/cli.mdx +++ b/docs/reference/cli.mdx @@ -9,12 +9,11 @@ These flags apply to all subcommands: | Flag | Description | |---|---| -| `--no-dotenv` | Skip loading `.env` file | | `--debug` | Enable DEBUG-level logging (default is INFO) | | `-h, --help` | Print help | | `-V, --version` | Print version | -Arc loads environment variables from `.env` in the current directory and `~/.arc/.env` (unless `--no-dotenv` is set). +Arc loads environment variables from `~/.arc/.env`. ## Configuration diff --git a/lib/crates/arc-agent/src/cli.rs b/lib/crates/arc-agent/src/cli.rs index 4dc76d7cf..2fafa1c04 100644 --- a/lib/crates/arc-agent/src/cli.rs +++ b/lib/crates/arc-agent/src/cli.rs @@ -631,7 +631,6 @@ pub async fn run_with_args_and_client( } pub async fn run() -> anyhow::Result<()> { - let _ = dotenvy::dotenv(); let cli = Cli::parse(); let mut args = cli.args; args.apply_cli_defaults(None, None, None, None); diff --git a/lib/crates/arc-cli/src/main.rs b/lib/crates/arc-cli/src/main.rs index c22f94f7d..d1df7dd13 100644 --- a/lib/crates/arc-cli/src/main.rs +++ b/lib/crates/arc-cli/src/main.rs @@ -11,10 +11,6 @@ use tracing::debug; #[derive(Parser)] #[command(name = "arc", version, long_version = arc_util::version::LONG_VERSION.as_str())] struct Cli { - /// Skip loading .env file - #[arg(long, global = true)] - no_dotenv: bool, - /// Enable DEBUG-level logging (default is INFO) #[arg(long, global = true)] debug: bool, @@ -176,11 +172,11 @@ async fn main_inner() -> Result<()> { let _ = rustls::crypto::ring::default_provider().install_default(); let cli = Cli::parse(); - if !cli.no_dotenv { - if let Some(home) = dirs::home_dir() { - let _ = dotenvy::from_path(home.join(".arc").join(".env")); + if let Some(home) = dirs::home_dir() { + let env_path = home.join(".arc").join(".env"); + if dotenvy::from_path(&env_path).is_ok() { + debug!(path = %env_path.display(), "Loaded environment file"); } - dotenvy::dotenv().ok(); } let command_name = match &cli.command { diff --git a/lib/crates/arc-cli/tests/cli.rs b/lib/crates/arc-cli/tests/cli.rs index 7f8d8fd9b..ef08a779f 100644 --- a/lib/crates/arc-cli/tests/cli.rs +++ b/lib/crates/arc-cli/tests/cli.rs @@ -6,12 +6,6 @@ fn arc() -> Command { Command::cargo_bin("arc").unwrap() } -/// Load .env into the process so subprocess inherits API keys -/// even when current_dir is set to a tempdir. -fn load_dotenv() { - dotenvy::dotenv().ok(); -} - // == Models =================================================================== #[test] @@ -99,14 +93,7 @@ fn prompt_errors_without_prompt_text() { #[test] fn prompt_reads_from_stdin() { let result = arc() - .args([ - "--no-dotenv", - "llm", - "prompt", - "--no-stream", - "-m", - "test-model", - ]) + .args(["llm", "prompt", "--no-stream", "-m", "test-model"]) .write_stdin("hello from stdin") .assert() .failure(); @@ -119,7 +106,6 @@ fn prompt_reads_from_stdin() { fn prompt_concatenates_stdin_and_arg() { let result = arc() .args([ - "--no-dotenv", "llm", "prompt", "--no-stream", @@ -198,7 +184,6 @@ fn prompt_usage_shows_tokens() { fn prompt_schema_rejects_invalid_json() { arc() .args([ - "--no-dotenv", "llm", "prompt", "--no-stream", @@ -320,11 +305,12 @@ fn exec_help_flag_prints_help() { #[test] fn exec_missing_api_key_exits_with_error() { - let tmp = std::env::temp_dir(); + let tmp = tempfile::tempdir().expect("tempdir"); arc() - .args(["--no-dotenv", "exec", "test prompt"]) + .args(["exec", "test prompt"]) .env_clear() - .current_dir(&tmp) + .env("HOME", tmp.path().to_str().unwrap()) + .current_dir(tmp.path()) .assert() .failure() .stderr(predicate::str::contains("API key not set")); @@ -343,7 +329,7 @@ fn exec_invalid_permissions_value() { #[test] #[ignore = "requires API key"] fn exec_creates_file() { - load_dotenv(); + dotenvy::dotenv().ok(); let tmp = tempfile::tempdir().expect("tempdir"); arc() .args([ @@ -373,7 +359,7 @@ fn exec_creates_file() { #[test] #[ignore = "requires API key"] fn exec_shell_command() { - load_dotenv(); + dotenvy::dotenv().ok(); let tmp = tempfile::tempdir().expect("tempdir"); arc() .args([ @@ -396,7 +382,7 @@ fn exec_shell_command() { #[test] #[ignore = "requires API key"] fn exec_read_only_blocks_write() { - load_dotenv(); + dotenvy::dotenv().ok(); let tmp = tempfile::tempdir().expect("tempdir"); arc() .args([ @@ -423,7 +409,7 @@ fn exec_read_only_blocks_write() { #[test] #[ignore = "requires API key"] fn exec_json_output_format() { - load_dotenv(); + dotenvy::dotenv().ok(); let tmp = tempfile::tempdir().expect("tempdir"); let output = arc() .args([ @@ -462,7 +448,7 @@ fn exec_json_output_format() { #[test] #[ignore = "requires API key"] fn exec_read_and_edit() { - load_dotenv(); + dotenvy::dotenv().ok(); let tmp = tempfile::tempdir().expect("tempdir"); std::fs::write(tmp.path().join("data.txt"), "old content").expect("write data.txt"); arc() @@ -648,12 +634,23 @@ fn dry_run_legacy_tool() { .success(); } +// == Dotenv =================================================================== + +#[test] +fn no_dotenv_flag_is_rejected() { + arc() + .args(["--no-dotenv", "doctor"]) + .assert() + .failure() + .stderr(predicate::str::contains("unexpected argument")); +} + // == Doctor =================================================================== #[test] fn doctor_runs_and_prints_header() { arc() - .args(["--no-dotenv", "doctor"]) + .args(["doctor"]) .env_clear() .assert() .stdout(predicate::str::contains("Arc Doctor")); @@ -662,7 +659,7 @@ fn doctor_runs_and_prints_header() { #[test] fn doctor_verbose_runs_and_prints_header() { arc() - .args(["--no-dotenv", "doctor", "-v"]) + .args(["doctor", "-v"]) .env_clear() .assert() .stdout(predicate::str::contains("Arc Doctor")); @@ -671,7 +668,7 @@ fn doctor_verbose_runs_and_prints_header() { #[test] fn doctor_no_color_when_no_color_set() { arc() - .args(["--no-dotenv", "doctor"]) + .args(["doctor"]) .env_clear() .env("NO_COLOR", "1") .assert() @@ -681,7 +678,7 @@ fn doctor_no_color_when_no_color_set() { #[test] fn doctor_dry_run_flag_accepted() { arc() - .args(["--no-dotenv", "doctor", "--dry-run"]) + .args(["doctor", "--dry-run"]) .env_clear() .assert() .stdout(predicate::str::contains("Arc Doctor"));