Skip to content

feat(storage): default database and cache to OS per-user directories#550

Open
SantiagoDePolonia wants to merge 1 commit into
mainfrom
feat/platform-dirs
Open

feat(storage): default database and cache to OS per-user directories#550
SantiagoDePolonia wants to merge 1 commit into
mainfrom
feat/platform-dirs

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

When no path is configured and no legacy ./data (or ./.cache) directory exists, the SQLite database and local model cache now default to the platform-conventional per-user locations, resolved by the new internal/platformdir package:

Database Model cache
Linux $XDG_DATA_HOME/gomodel/gomodel.db (~/.local/share/…) $XDG_CACHE_HOME/gomodel (~/.cache/…)
macOS ~/Library/Application Support/gomodel/gomodel.db ~/Library/Caches/gomodel
Windows %LocalAppData%\gomodel\gomodel.db %LocalAppData%\gomodel\cache

Backward compatibility

  • Explicit GOMODEL_SQLITE_PATH / GOMODEL_CACHE_DIR / YAML paths win, unchanged.
  • The presence of ./data (resp. ./.cache) pins the historical CWD-relative defaults — existing deployments and the Docker image (which pre-creates both directories, and whose volume mounts create their mount points) never move state on upgrade.
  • Startup logs the resolved database path (storage configured … path=…) so it's always visible where state lives.

Motivation

Binary installs (companion PR #549's install.sh/Homebrew flow) run from arbitrary working directories; CWD-relative defaults would scatter databases wherever the user happened to launch from.

Testing

  • New unit tests: platformdir (per-OS layout, XDG override, data≠cache) and DefaultSQLitePath legacy-vs-platform resolution (t.Chdir).
  • Full pre-commit suite green (race tests, lint).
  • Manual smoke on macOS: run from an empty dir → DB in ~/Library/Application Support/gomodel/, cache in ~/Library/Caches/gomodel/; both paths logged.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added automatic OS-specific locations for persistent data and model cache files.
    • Preserved existing local .cache and data directories when present.
    • Startup logs now include the SQLite database file path.
  • Documentation

    • Clarified default cache and SQLite storage paths in configuration documentation.
  • Tests

    • Added coverage for platform-specific directories and SQLite path selection.

When no path is configured and no legacy ./data (or ./.cache) directory
exists, the SQLite database and local model cache now land in the
platform-conventional locations (XDG dirs on Linux, ~/Library on macOS,
%LocalAppData% on Windows) via the new internal/platformdir package.
Existing deployments and the Docker image keep their current paths: the
presence of ./data or ./.cache pins the historical defaults, so upgrades
never move anyone's state. Startup now logs the resolved database path.

This makes binary installs (install.sh, Homebrew) work from any working
directory without scattering state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds platform-specific data and cache directory helpers, updates SQLite and model-cache defaults to use them, preserves existing local directories, updates configuration wiring and startup logging, and revises related documentation and tests.

Changes

Platform-aware path resolution

Layer / File(s) Summary
Platform directory helpers and tests
internal/platformdir/platformdir.go, internal/platformdir/platformdir_test.go
Adds OS-specific DataDir() and CacheDir() resolution with tests for absolute paths, XDG support, and distinct directories.
SQLite default path resolution
internal/storage/storage.go, internal/storage/sqlite.go, internal/storage/default_path_test.go
Preserves data/gomodel.db when ./data exists; otherwise derives a per-user SQLite path and tests both cases.
Model cache directory selection
internal/providers/init.go
Preserves an existing ./.cache directory and otherwise selects the platform cache directory with a fallback.
Configuration and observability
config/..., internal/app/app.go, .env.template, CLAUDE.md
Wires the computed SQLite default through configuration, updates startup logging, and documents the new defaults.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Config
  participant Storage
  participant PlatformDir
  Config->>Storage: request DefaultSQLitePath()
  Storage->>Storage: check for ./data
  Storage->>PlatformDir: request DataDir() when ./data is absent
  PlatformDir-->>Storage: return per-user data directory
  Storage-->>Config: return SQLite database path
Loading

Poem

A rabbit hops through paths anew,
With cache and data directories in view.
Old local folders stay in place,
New homes bloom by platform space.
SQLite logs its chosen trail—
Config and tests ensure they don’t fail.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% 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 title clearly summarizes the main storage change: database and cache defaults now fall back to OS per-user directories.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/platform-dirs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 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 `@internal/providers/init.go`:
- Around line 222-224: Update the `.cache` check in the provider initialization
flow to return the legacy path only when `os.Stat` succeeds and the resulting
file info from `Stat` satisfies `IsDir()`. If `.cache` is a regular file or
unavailable, preserve the existing fallback behavior.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 526e538a-53bd-46b7-affe-733f45ea37f3

📥 Commits

Reviewing files that changed from the base of the PR and between 50e1869 and dfa3631.

📒 Files selected for processing (12)
  • .env.template
  • CLAUDE.md
  • config/config.go
  • config/config_test.go
  • config/storage.go
  • internal/app/app.go
  • internal/platformdir/platformdir.go
  • internal/platformdir/platformdir_test.go
  • internal/providers/init.go
  • internal/storage/default_path_test.go
  • internal/storage/sqlite.go
  • internal/storage/storage.go

Comment on lines +222 to +224
if _, err := os.Stat(".cache"); err == nil {
return ".cache"
}

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Only preserve .cache when it is a directory.

os.Stat also succeeds when .cache is a regular file. The subsequent filepath.Join(".cache", "models.json") then selects an invalid cache path and can prevent local-cache initialization. Check info.IsDir() before returning the legacy path.

Proposed fix
-	if _, err := os.Stat(".cache"); err == nil {
+	if info, err := os.Stat(".cache"); err == nil && info.IsDir() {
 		return ".cache"
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if _, err := os.Stat(".cache"); err == nil {
return ".cache"
}
if info, err := os.Stat(".cache"); err == nil && info.IsDir() {
return ".cache"
}
🤖 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 `@internal/providers/init.go` around lines 222 - 224, Update the `.cache` check
in the provider initialization flow to return the legacy path only when
`os.Stat` succeeds and the resulting file info from `Stat` satisfies `IsDir()`.
If `.cache` is a regular file or unavailable, preserve the existing fallback
behavior.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 33.33333% with 32 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/platformdir/platformdir.go 38.46% 13 Missing and 3 partials ⚠️
internal/providers/init.go 0.00% 8 Missing ⚠️
internal/app/app.go 0.00% 4 Missing ⚠️
internal/storage/storage.go 71.42% 1 Missing and 1 partial ⚠️
config/storage.go 0.00% 1 Missing ⚠️
internal/storage/sqlite.go 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Jul 18, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

One contained configuration bug needs to be fixed before merging.

The storage and cache path resolution is straightforward, but the documented SQLite env override is ignored because the config tag still points at SQLITE_PATH.

config/storage.go

T-Rex T-Rex Logs

What T-Rex did

  • Ran a focused Go test to verify that the documented SQLite env override is ignored when GOMODEL_SQLITE_PATH is set, and observed that the loaded storage path defaults to /tmp/trex-xdg-data/gomodel/gomodel.db while the legacy SQLITE_PATH case resolves to /tmp/trex-legacy-sqlite.db.
  • Reviewed the reproduction artifacts to confirm the test details and validate that the verbose output demonstrates GOMODEL_SQLITE_PATH being ignored and SQLITE_PATH being honored.
  • Validated the unit-test and startup proofs, confirming the storage defaults test ran with EXIT_CODE 0 and the startup smoke test produced the expected storage path under /tmp/trex-smoke-xdg-data/gomodel/gomodel.db, with RUN_EXIT_CODE 124 as expected due to the bounded timeout.

View all artifacts

T-Rex Ran code and verified through T-Rex

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Startup
participant Config as config.Load/buildDefaultConfig
participant Storage as storage.DefaultSQLitePath
participant Platform as platformdir.DataDir/CacheDir
participant Cache as providers.initCache

Startup->>Config: Load defaults, YAML, env
Config->>Storage: Resolve SQLite path when unset
Storage->>Storage: Check ./data exists
alt legacy ./data exists
    Storage-->>Config: data/gomodel.db
else no legacy data dir
    Storage->>Platform: DataDir()
    Platform-->>Storage: OS per-user data directory
    Storage-->>Config: "<data dir>/gomodel.db"
end
Startup->>Cache: Initialize local model cache
Cache->>Cache: Check ./.cache exists
alt legacy ./.cache exists
    Cache-->>Startup: .cache/models.json
else no legacy .cache dir
    Cache->>Platform: CacheDir()
    Platform-->>Cache: OS per-user cache directory
    Cache-->>Startup: "<cache dir>/models.json"
end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Startup
participant Config as config.Load/buildDefaultConfig
participant Storage as storage.DefaultSQLitePath
participant Platform as platformdir.DataDir/CacheDir
participant Cache as providers.initCache

Startup->>Config: Load defaults, YAML, env
Config->>Storage: Resolve SQLite path when unset
Storage->>Storage: Check ./data exists
alt legacy ./data exists
    Storage-->>Config: data/gomodel.db
else no legacy data dir
    Storage->>Platform: DataDir()
    Platform-->>Storage: OS per-user data directory
    Storage-->>Config: "<data dir>/gomodel.db"
end
Startup->>Cache: Initialize local model cache
Cache->>Cache: Check ./.cache exists
alt legacy ./.cache exists
    Cache-->>Startup: .cache/models.json
else no legacy .cache dir
    Cache->>Platform: CacheDir()
    Platform-->>Cache: OS per-user cache directory
    Cache-->>Startup: "<cache dir>/models.json"
end
Loading

Reviews (1): Last reviewed commit: "feat(storage): default database and cach..." | Re-trigger Greptile

Comment thread config/storage.go
// Path is the database file path. Default: ./data/gomodel.db when a
// ./data directory exists, otherwise the OS per-user data directory
// (e.g. ~/.local/share/gomodel/gomodel.db).
Path string `yaml:"path" env:"SQLITE_PATH"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Honor documented env var
.env.template now tells operators to set GOMODEL_SQLITE_PATH, but this tag still only reads SQLITE_PATH. With no YAML path, Load() ignores the documented variable and keeps the default path, so an explicit database location no longer wins as described by the PR and users can start against the wrong SQLite file.

Context Used: CLAUDE.md (source)

Artifacts

Repro: focused Go test exercising config.Load SQLite env override behavior

  • Contains supporting evidence from the run (text/x-go; charset=utf-8).

Repro: verbose go test output showing GOMODEL_SQLITE_PATH ignored and SQLITE_PATH honored

  • Keeps the command output available without making the summary code-heavy.

View artifacts

T-Rex Ran code and verified through T-Rex

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