fix(config): fix directory locations to follow XDG spec#336
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request refactors the storage location for Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request refactors the storage location for the lease ledger to follow XDG standards, moving it from the configuration directory to a state directory. This architectural improvement includes adding a new FNOX_STATE_DIR environment variable and implementing automatic migration for existing lease files. However, the migration logic introduces a high-severity race condition that can lead to data loss if multiple processes access the ledger concurrently during the first run after an upgrade. A process failing migration due to a concurrent move could fall back to an empty ledger and overwrite successfully migrated data. It is recommended to perform the migration under a stable lock or ensure consistent path resolution. Furthermore, the error handling for directory creation and lock file migration in src/lease.rs needs improvement to prevent potential data loss or race conditions, and the new state directory path on Windows in src/env.rs is currently identical to the configuration directory, which undermines the separation goal.
I am having trouble creating individual review comments. Click here to see my feedback.
src/lease.rs (89-114)
The ledger_path migration logic is highly susceptible to a race condition, which can lead to data loss. When multiple processes attempt to access the lease ledger for the first time after an upgrade, a non-atomic check-and-rename sequence without proper file locking can result in one process overwriting successfully migrated data with an empty ledger if it fails a concurrent rename and falls back to the old path.
Furthermore, the current implementation has several robustness issues:
- Error Handling for
create_dir_all: Errors during directory creation fornew_pathare silently ignored. This can lead to subsequent file operation failures, and the migration should fall back toold_pathin such cases. - Error Handling for Lock File Migration: The result of renaming the lock file (
fs::rename(&old_lock, &new_lock)) is ignored. If the ledger file migrates but the lock file doesn't, it could introduce further race conditions or inconsistent locking behavior. This failure should at least be logged. - Readability: The deeply nested logic makes it harder to follow and debug. Refactoring to handle simple cases first would improve clarity and maintainability.
fn ledger_path(project_dir: &Path) -> PathBuf {
let hash = hash_project_dir(project_dir);
let filename = format!("{hash}.toml");
let new_path = env::FNOX_STATE_DIR.join("leases").join(&filename);
let old_path = env::FNOX_CONFIG_DIR.join("leases").join(&filename);
// If new path exists, or old path doesn't, no migration is needed.
if new_path.exists() || !old_path.exists() {
return new_path;
}
// New path does not exist, but old one does. Attempt migration.
tracing::debug!(
"Lease ledger at old path {} found, attempting migration to {}",
old_path.display(),
new_path.display()
);
if let Some(parent) = new_path.parent() {
if let Err(e) = fs::create_dir_all(parent) {
tracing::warn!(
"Failed to create directory for lease ledger migration {}: {}. Falling back to old path.",
parent.display(),
e
);
return old_path;
}
} else {
// This is unlikely, but worth handling.
tracing::warn!(
"Could not determine parent directory for {}. Falling back to old path.",
new_path.display()
);
return old_path;
}
// Migrate the ledger file.
if let Err(e) = fs::rename(&old_path, &new_path) {
tracing::warn!(
"Failed to migrate lease ledger from {} to {}: {}. Falling back to old path.",
old_path.display(),
new_path.display(),
e
);
return old_path;
}
tracing::debug!("Migrated lease ledger to {}", new_path.display());
// Also migrate the lock file if present.
let old_lock = old_path.with_extension("lock");
if old_lock.exists() {
let new_lock = new_path.with_extension("lock");
if let Err(e) = fs::rename(&old_lock, &new_lock) {
tracing::warn!(
"Failed to migrate lease lock file from {} to {}: {}. This may cause locking issues.",
old_lock.display(),
new_lock.display(),
e
);
}
}
new_path
}src/env.rs (36-37)
On Windows, both FNOX_STATE_DIR and the existing FNOX_CONFIG_DIR are being set to the same path (~/AppData/Local/fnox). This defeats the purpose of separating state from configuration on the Windows platform. As a result, the migration logic in src/lease.rs will not trigger for Windows users as old_path and new_path will be identical.
To properly align with the goal of this refactor, the state directory should be distinct from the configuration directory on Windows as well. A common pattern is to use sibling directories or subdirectories within AppData/Local.
Since changing FNOX_CONFIG_DIR would be a breaking change for existing Windows users, I suggest modifying the default path for FNOX_STATE_DIR on Windows to ensure it's unique.
#[cfg(windows)]
let default = HOME_DIR.join("AppData").join("Local").join("fnox-state");
| fn ledger_path(project_dir: &Path) -> PathBuf { | ||
| let hash = hash_project_dir(project_dir); | ||
| env::FNOX_CONFIG_DIR | ||
| env::FNOX_STATE_DIR |
There was a problem hiding this comment.
No migration for existing lease ledger files
Medium Severity
The ledger_path now points to FNOX_STATE_DIR instead of FNOX_CONFIG_DIR, but the load method returns an empty default ledger when the new path doesn't exist. Existing ledger files at ~/.config/fnox/leases/ are silently orphaned — no migration code exists despite the PR summary claiming "Automatically migrates existing ledger files (and lock files) on first access." This causes existing cached leases to be lost and unnecessarily re-created.
Greptile SummaryThis PR moves lease ledger storage from Key changes:
Issues found:
Confidence Score: 2/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[LeaseLedger::load / lock / save] --> B{FNOX_STATE_DIR set\nand absolute?}
B -- yes --> C[Use FNOX_STATE_DIR/leases/hash.toml]
B -- no --> D{XDG_STATE_HOME set\nand absolute?}
D -- yes --> E[Use XDG_STATE_HOME/fnox/leases/hash.toml]
D -- no --> F{Platform}
F -- Unix --> G[~/.local/state/fnox/leases/hash.toml]
F -- Windows --> H[~/AppData/Local/fnox/leases/hash.toml]
C --> I[Check path.exists?]
E --> I
G --> I
H --> I
I -- exists --> J[Read & deserialize TOML]
I -- missing --> K[⚠️ Return empty default\nNO migration attempted\nOld ~/.config/fnox/leases/ silently ignored]
style K fill:#f96,color:#000
|
70170f8 to
f9b23f1
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Lease ledgers are runtime state, not configuration, so they belong under XDG_STATE_HOME (~/.local/state) rather than XDG_CONFIG_HOME (~/.config). Adds FNOX_STATE_DIR env var and automatic migration from the old location. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use dirs::config_dir() and dirs::state_dir() instead of hardcoding ~/.config and ~/.local/state paths, so XDG environment variables are respected. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…paths dirs::config_dir() returns ~/Library/Application Support on macOS, breaking the expected ~/.config/fnox path. Use $XDG_CONFIG_HOME and $XDG_STATE_HOME directly with ~/.config and ~/.local/state fallbacks, which is consistent across all platforms. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dirs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The XDG Base Directory spec requires that empty values be treated as unset and all paths be absolute. An empty XDG_STATE_HOME would produce a relative path causing ledger files to be written to the cwd. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests set HOME to a temp dir and create configs at $HOME/.config/fnox/. If XDG_CONFIG_HOME is set (e.g. in CI), it takes precedence and fnox looks in the wrong directory, causing "No providers configured" errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The bw CLI stores server URL and login state in ~/.config/Bitwarden CLI/data.json. When _common_setup changes HOME to a temp dir, bw can't find its config and reports "You are not logged in" even with BW_SESSION set. Fix by capturing BITWARDENCLI_APPDATA_DIR before HOME changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7d3dae8 to
b6a93ee
Compare
### 🚀 Features - **(cloudflare)** add Cloudflare API token lease backend by [@jdx](https://github.com/jdx) in [#335](#335) - **(fido2)** bump demand to v2, mask PIN during typing by [@jdx](https://github.com/jdx) in [#334](#334) - **(init)** add -f as short alias for --force by [@jdx](https://github.com/jdx) in [#329](#329) - **(lease)** add --all flag, default to creating all leases by [@jdx](https://github.com/jdx) in [#337](#337) - **(lease)** add GitHub App installation token lease backend by [@jdx](https://github.com/jdx) in [#342](#342) ### 🐛 Bug Fixes - **(config)** fix directory locations to follow XDG spec by [@jdx](https://github.com/jdx) in [#336](#336) - **(exec)** use unix exec and exit silently on subprocess failure by [@jdx](https://github.com/jdx) in [#339](#339) - **(fido2)** remove duplicate touch prompt by [@jdx](https://github.com/jdx) in [#332](#332) - **(set)** write to lowest-priority existing config file by [@jdx](https://github.com/jdx) in [#331](#331) - **(tui)** skip providers requiring interactive auth by [@jdx](https://github.com/jdx) in [#333](#333) ### 🛡️ Security - **(ci)** retry lint step to handle transient pkl fetch failures by [@jdx](https://github.com/jdx) in [#341](#341) - **(mcp)** add MCP server for secret-gated AI agent access by [@jdx](https://github.com/jdx) in [#343](#343) - add guide for fnox sync by [@jdx](https://github.com/jdx) in [#328](#328) ### 🔍 Other Changes - share Rust cache across CI jobs by [@jdx](https://github.com/jdx) in [#340](#340)


Summary
~/.config/fnox/leases/to~/.local/state/fnox/leases/(XDG state dir)FNOX_STATE_DIRenv var for overriding the state directoryTest plan
~/.config/fnox/leases/are migrated on firstfnox execorfnox lease list~/.local/state/fnox/leases/FNOX_STATE_DIRoverride works🤖 Generated with Claude Code
Note
Medium Risk
Changes on-disk locations for the lease ledger and config/state resolution, which can break existing caches or lookups in environments relying on prior paths or non-absolute overrides.
Overview
Updates fnox’s directory resolution to follow the XDG spec:
FNOX_CONFIG_DIRnow honorsXDG_CONFIG_HOME, and a newFNOX_STATE_DIR(withXDG_STATE_HOMEfallback) is introduced for stateful data. Environment path parsing (var_path) now ignores empty values and rejects non-absolute paths.Moves the per-project lease ledger from the config directory to the XDG state directory (
~/.local/state/fnox/leases/...) and updates the leases guide accordingly. Test harnesses are adjusted to avoid CI/user XDG env leaking into tests, and Bitwarden bats tests preserveBITWARDENCLI_APPDATA_DIRbeforeHOMEis rewritten.Written by Cursor Bugbot for commit b6a93ee. This will update automatically on new commits. Configure here.