Summary
On macOS, the TUI API-key login flow reports that it saved the key to
~/.config/jcode/<provider>.env, but the file is actually written to
~/Library/Application Support/jcode/<provider>.env. The directory in that message is a
hardcoded string literal, not the resolved config directory.
The save itself is correct and the key works. Only the message is wrong — but it is wrong
in the one place a user looks to confirm setup succeeded, so it reads as "jcode failed to
create its config files."
Environment
- jcode v0.80.0 (c3ccfa0), macOS 15.5, aarch64
- Reproduced with OpenRouter; affects every provider that goes through the TUI API-key flow
Steps to reproduce
-
On macOS, run jcode and use the TUI login flow to add an OpenRouter API key.
-
Observe the success message:
OpenRouter API key saved.
Stored at ~/.config/jcode/openrouter.env.
You can now use /model to switch to OpenRouter models. If the model list looks stale,
run /refresh-model-list.
-
ls ~/.config/jcode/ → no such directory.
-
ls ~/Library/Application\ Support/jcode/openrouter.env → the key is here, mode 0600.
Root cause
storage::app_config_dir() (crates/jcode-storage/src/lib.rs:189) resolves the config
directory properly:
pub fn app_config_dir() -> Result<PathBuf> {
if let Ok(path) = std::env::var("JCODE_HOME") {
return Ok(PathBuf::from(path).join("config").join("jcode"));
}
let config_dir = dirs::config_dir()
.ok_or_else(|| anyhow::anyhow!("No config directory found"))?;
Ok(config_dir.join("jcode"))
}
On macOS dirs::config_dir() is ~/Library/Application Support, not ~/.config.
But the TUI success message hardcodes the directory and interpolates only the filename —
crates/jcode-tui/src/tui/app/auth.rs:2450:
message: format!(
"{}.\n\n\
Stored at ~/.config/jcode/{}.\n\
{}{}",
saved_label, env_file, guidance, model_hint
),
The CLI login path already does this correctly — src/cli/login.rs:625 formats the
resolved directory:
eprintln!(
"Stored at {}",
crate::storage::app_config_dir()?.join("openrouter.env").display()
);
So jcode login --provider openrouter prints the truth while the TUI flow does not.
Other affected sites
Same hardcoded literal, non-test:
crates/jcode-tui/src/tui/app/auth.rs:2450 — generic API-key login success (OpenRouter, OpenAI, Anthropic, Bedrock, custom profiles)
crates/jcode-tui/src/tui/app/auth.rs:2646 — Cursor
crates/jcode-tui/src/tui/app/auth.rs:3441 — Azure OpenAI
crates/jcode-base/src/auth/mod.rs:751, 766, 788, 807, 828, 869, 880 — config_source(...) display labels in the auth inventory
crates/jcode-base/src/auth/azure.rs:72 — "not found in environment or ~/.config/jcode/{}" error text
The runtime "key not found" error carries the same string; see the log line quoted in #849
(OPENROUTER_API_KEY not found in environment or /home/xxx/.config/jcode/openrouter.env).
On Linux that path happens to be correct, which is probably why this has gone unreported.
Also wrong when JCODE_HOME is set
Independent of platform: with JCODE_HOME set, keys go to
$JCODE_HOME/config/jcode/<provider>.env, but the message still claims ~/.config/jcode/.
Expected
The message names the directory the key was actually written to, on every platform and
under JCODE_HOME.
Suggested fix
Format these messages from app_config_dir() the way src/cli/login.rs:625 already does,
rather than embedding the Linux path as a literal. The config_source call sites in
auth/mod.rs take a path_label argument that could be derived the same way.
Summary
On macOS, the TUI API-key login flow reports that it saved the key to
~/.config/jcode/<provider>.env, but the file is actually written to~/Library/Application Support/jcode/<provider>.env. The directory in that message is ahardcoded string literal, not the resolved config directory.
The save itself is correct and the key works. Only the message is wrong — but it is wrong
in the one place a user looks to confirm setup succeeded, so it reads as "jcode failed to
create its config files."
Environment
Steps to reproduce
On macOS, run
jcodeand use the TUI login flow to add an OpenRouter API key.Observe the success message:
ls ~/.config/jcode/→ no such directory.ls ~/Library/Application\ Support/jcode/openrouter.env→ the key is here, mode 0600.Root cause
storage::app_config_dir()(crates/jcode-storage/src/lib.rs:189) resolves the configdirectory properly:
On macOS
dirs::config_dir()is~/Library/Application Support, not~/.config.But the TUI success message hardcodes the directory and interpolates only the filename —
crates/jcode-tui/src/tui/app/auth.rs:2450:The CLI login path already does this correctly —
src/cli/login.rs:625formats theresolved directory:
So
jcode login --provider openrouterprints the truth while the TUI flow does not.Other affected sites
Same hardcoded literal, non-test:
crates/jcode-tui/src/tui/app/auth.rs:2450— generic API-key login success (OpenRouter, OpenAI, Anthropic, Bedrock, custom profiles)crates/jcode-tui/src/tui/app/auth.rs:2646— Cursorcrates/jcode-tui/src/tui/app/auth.rs:3441— Azure OpenAIcrates/jcode-base/src/auth/mod.rs:751, 766, 788, 807, 828, 869, 880—config_source(...)display labels in the auth inventorycrates/jcode-base/src/auth/azure.rs:72— "not found in environment or~/.config/jcode/{}" error textThe runtime "key not found" error carries the same string; see the log line quoted in #849
(
OPENROUTER_API_KEY not found in environment or /home/xxx/.config/jcode/openrouter.env).On Linux that path happens to be correct, which is probably why this has gone unreported.
Also wrong when
JCODE_HOMEis setIndependent of platform: with
JCODE_HOMEset, keys go to$JCODE_HOME/config/jcode/<provider>.env, but the message still claims~/.config/jcode/.Expected
The message names the directory the key was actually written to, on every platform and
under
JCODE_HOME.Suggested fix
Format these messages from
app_config_dir()the waysrc/cli/login.rs:625already does,rather than embedding the Linux path as a literal. The
config_sourcecall sites inauth/mod.rstake apath_labelargument that could be derived the same way.