Skip to content

fix: honor VOICEBOX_DATA_DIR in both the backend and the desktop app - #1004

Open
Lvigentini wants to merge 4 commits into
jamiepine:mainfrom
Lvigentini:feat/voicebox-data-dir-env
Open

Lvigentini wants to merge 4 commits into
jamiepine:mainfrom
Lvigentini:feat/voicebox-data-dir-env

Conversation

@Lvigentini

@Lvigentini Lvigentini commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

backend/README.md documents VOICEBOX_DATA_DIR alongside --data-dir as a way to override the data directory, but it never worked. Two open issues report this: #981 and #533.

Why it was broken in two places

Backend. config.py only wired up VOICEBOX_MODELS_DIR; the data directory default stayed hardcoded to ./data.

Desktop app. Fixing the backend alone is not enough. The Tauri shell resolved the directory from app.path().app_data_dir() unconditionally and passed it to the sidecar as an explicit --data-dir, which beats the environment variable on the Python side. Both issue reports show it in the log:

Arguments: [...'voicebox-server.exe', '--data-dir',
            'C:\Users\<user>\AppData\Roaming\sh.voicebox.app', ...]

So the variable could only ever reach a bare uvicorn backend.main:app, never the packaged app.

Changes

  • backend/config.py — read VOICEBOX_DATA_DIR when computing the default. --data-dir still takes precedence, since it calls set_data_dir() after import. This is also the only override that reaches just dev, which starts uvicorn directly and never parses CLI arguments.
  • tauri/src-tauri/src/main.rs — resolve the override before the argument is built. data_dir_str feeds all four sidecar spawn sites, so one change covers the plain, ROCm, CUDA and fallback paths.

Unset, empty and whitespace-only all mean "no override", so a variable cleared to "" falls back to the platform path rather than the process working directory. Surrounding whitespace is trimmed, since variables set through the Windows GUI often carry a trailing space.

The startup log now names which source won, and the create_dir_all failure names the path, so a typo surfaces as an actionable error rather than a silent fallback.

Behaviour note

The Tauri data_dir also anchors backends/rocm, backends/cuda and the backend_override file, so those move under the overridden directory too. That seemed right — the point of the variable is to put app data somewhere else — but flagging it in case you'd rather scope the override more narrowly.

Tests

  • backend/tests/test_data_dir_env.py covers the precedence rules on the Python side.
  • Four unit tests in main.rs cover the override decision: unset, empty, whitespace-only, verbatim path, and trimming. cargo test --bin voicebox passes.

No UI changes, so no screenshots apply.

Fixes #981
Fixes #533

Also partially addresses #577

#577 asks for the Generated and Captured folder locations to be configurable. Both sit under the data directory, so both move with this change — two of that issue's three asks. What it does not cover is downloading models straight to a chosen location (the issue's first point; VOICEBOX_MODELS_DIR plus the existing migration flow is the current answer), or a folder picker in Settings. Leaving #577 open on that basis rather than claiming it.

Refs #577

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Configure the application’s data directory with the VOICEBOX_DATA_DIR environment variable.
    • Relative paths are resolved consistently, and surrounding whitespace is removed.
    • Explicit data-directory settings continue to take precedence over the environment variable.
  • Bug Fixes

    • Improved fallback behavior when the variable is missing, empty, or whitespace-only.
  • Tests

    • Added coverage for configuration, path resolution, defaults, trimming, and overrides.

backend/README.md documents VOICEBOX_DATA_DIR alongside --data-dir as a way to
override the data directory, but config.py never read it -- only
VOICEBOX_MODELS_DIR was wired up, so the default stayed hardcoded to ./data.

This matters in development: `just dev` starts the backend with a bare
`uvicorn backend.main:app`, which never reaches main.py's argparse, so
--data-dir is unavailable there and the dev server always writes to ./data
inside the repo. Pointing a dev server at the packaged app's data directory
meant editing the justfile.

Read the variable when computing the default. --data-dir still takes
precedence, since it calls set_data_dir() after import.

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

coderabbitai Bot commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

VOICEBOX_DATA_DIR now configures the backend and Tauri data directories. Blank values use existing fallbacks. Relative paths resolve to absolute paths. Explicit set_data_dir() values remain authoritative.

Changes

Data directory configuration

Layer / File(s) Summary
Backend data directory resolution
backend/config.py, backend/tests/test_data_dir_env.py
The backend reads and resolves VOICEBOX_DATA_DIR. Tests cover configured, default, empty, relative, module restoration, and explicit set_data_dir() values.
Tauri startup data directory resolution
tauri/src-tauri/src/main.rs
Tauri trims valid environment values, ignores blank values, selects the configured or platform app-data directory, reports its source, creates the selected directory, and tests each path form.

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

Possibly related PRs

  • jamiepine/voicebox#987: This PR implements VOICEBOX_DATA_DIR support in backend and Tauri data-directory resolution.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes honor VOICEBOX_DATA_DIR, preserve --data-dir precedence, and address the reported desktop-app failures with tests [#981, #533].
Out of Scope Changes check ✅ Passed All production and test changes support VOICEBOX_DATA_DIR handling, precedence, path resolution, and the linked desktop-app fixes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: honoring VOICEBOX_DATA_DIR in both the backend and desktop app.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 `@backend/tests/test_data_dir_env.py`:
- Around line 40-41: Restore the original environment before reloading config in
the fixture teardown, so config is reinitialized without the test’s
VOICEBOX_DATA_DIR value. Update the cleanup around importlib.reload(config),
preserving the existing environment-matching behavior for subsequent tests.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ed058dcb-2930-44d2-98de-c5c69210d76d

📥 Commits

Reviewing files that changed from the base of the PR and between 51f49de and f287649.

📒 Files selected for processing (2)
  • backend/config.py
  • backend/tests/test_data_dir_env.py

Comment thread backend/tests/test_data_dir_env.py
The desktop app ignored VOICEBOX_DATA_DIR entirely. The Tauri shell resolved
the data directory from `app.path().app_data_dir()` unconditionally and passed
it to the sidecar as an explicit `--data-dir`, which beats the environment
variable on the Python side. Reading the variable in backend/config.py is
therefore not enough on its own — it only reaches a bare `uvicorn` run, never
the packaged app.

Resolve the override in the shell instead, before the argument is built.
`data_dir_str` feeds all four sidecar spawn sites, so one change covers the
plain, ROCm, CUDA and fallback paths.

Unset, empty and whitespace-only are all treated as "no override", so a
variable cleared to "" falls back to the platform path rather than resolving
to the process working directory. Surrounding whitespace is trimmed, since
variables set through the Windows GUI often carry a trailing space.

The startup log now names the source, and the create_dir_all failure names
the path, so a typo in the variable surfaces as an actionable error rather
than a silent fallback.

Note this also moves `backends/rocm`, `backends/cuda` and `backend_override`
under the overridden directory, since they are anchored to the same path.
That keeps all app data in one place, which is the point of the variable.

Fixes jamiepine#981
Fixes jamiepine#533

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Lvigentini Lvigentini changed the title fix(backend): honor VOICEBOX_DATA_DIR environment variable fix: honor VOICEBOX_DATA_DIR in both the backend and the desktop app Aug 8, 2026

@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 `@tauri/src-tauri/src/main.rs`:
- Around line 221-225: Update data_dir_override to resolve non-absolute values
against the Tauri process directory before returning them, while preserving
absolute overrides and empty-value handling. Ensure start_server and the
ROCm/CUDA launch paths use this resolved path for directory creation and
--data-dir construction. Add coverage for a relative override asserting the
selected path is absolute and consistent with backend/config.py and
test_data_dir_env.py.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d23711a5-c42f-42b1-9d67-77d9f335765e

📥 Commits

Reviewing files that changed from the base of the PR and between f287649 and 5eb2481.

📒 Files selected for processing (1)
  • tauri/src-tauri/src/main.rs

Comment thread tauri/src-tauri/src/main.rs Outdated
Review catch on the previous commit. data_dir_override returned relative
values unchanged, and the ROCm and CUDA branches spawn the sidecar with
current_dir() set to the backend's onedir folder. A relative --data-dir would
therefore resolve against backends/rocm or backends/cuda rather than where the
user meant — and to a different place than the CPU path, which does not change
the working directory. Two backends could end up with separate data
directories from one setting.

Resolve relative values against the process working directory before the
argument is built. The base is passed in rather than read inside the function,
so the behaviour stays unit-testable without touching the environment.

Also fixes two things in the Python test flagged in review:

  - The fixture reloaded config during teardown, which pytest runs *before* it
    restores monkeypatch, so the module kept the last test's VOICEBOX_DATA_DIR
    for every test that followed. Undo first, then reload.
  - The module was imported flat via a sys.path hack, so it reloaded a
    different module object than the one the app holds — the app imports
    `from ..config import`, i.e. backend.config. Import it the same way the
    rest of the suite does, which also drops the sys.path insert.

Rust: 5 unit tests pass, now covering the relative case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Lvigentini

Copy link
Copy Markdown
Contributor Author

Verified end to end on Windows 11 against a build from this branch.

Set VOICEBOX_DATA_DIR to an empty C:\Users\psyco\test_folder and launched the built app. Startup log:

=================================================================
Starting voicebox-server sidecar
Data directory: "C:\Users\psyco\test_folder" (from VOICEBOX_DATA_DIR)
Remote mode: false
No ROCm backend found
No CUDA backend found, using bundled CPU binary
Sidecar command created successfully
Spawning bundled CPU server process...

GET /health/filesystem — the exact check from #981, which previously returned AppData paths:

{"healthy": true, "directories": [
  {"path": "C:\Users\psyco\test_folder\generations", "exists": true, "writable": true},
  {"path": "C:\Users\psyco\test_folder\captures",    "exists": true, "writable": true},
  {"path": "C:\Users\psyco\test_folder\profiles",    "exists": true, "writable": true},
  {"path": "C:\Users\psyco\test_folder",              "exists": true, "writable": true}]}

The directory was populated with backends/, captures/, generations/, profiles/ and a fresh 139 KB voicebox.db, and GET /profiles returned 0 — the app came up on an empty database rather than the existing library, which confirms it is genuinely reading from the override and not just logging it. The real data directory was untouched (voicebox.db still 1,839,104 bytes).

Also worth flagging from the same build: just build fails at the very end with

A public key has been found, but no private key.
Make sure to set `TAURI_SIGNING_PRIVATE_KEY` environment variable.
error: recipe `build-tauri` failed

Both installers are produced before that point, so it only affects the updater artifacts — unrelated to this PR, but it means a local just build exits non-zero without that secret.

Lvigentini pushed a commit to Lvigentini/voicebox that referenced this pull request Aug 9, 2026
Five findings from the review of jamiepine#1005.

Folder assignment on profile creation
-------------------------------------
`create_profile` persisted `folder_id` without checking it. The
folder-assignment endpoint enforces that a voice only lands in a voice
folder, but creation bypassed that entirely — a client could file a new
profile straight into a clip folder, or reference a folder that does not
exist. Same two checks, raised as ValueError so the route returns 400 like
its neighbours.

Whitespace-only names
---------------------
`"   "` passed `min_length=1` because the raw string is three characters
long, and the route then stored `.strip()` — an empty label. Same for the
duplicate-profile override, which could produce a copy with no name at all.
Both now use a `TrimmedName` type that strips first and length-checks the
result. Left `AudioChannelUpdate` and `EffectPresetUpdate` alone; they have
the same gap but are not this PR's concern.

Moved clips lingering in the list
---------------------------------
Moving a clip to another folder invalidated the history query but left
`allHistory` intact. Past page 0 the refreshed first page is appended rather
than replacing the list, so the clip stayed visible in a folder it no longer
belonged to. Folder moves now go through one handler that drops back to page
0, which is what makes the next response replace the list.

Detaching a folder
------------------
`useDetachFolder` invalidated only the folder tree. Member lists include
subfolders, so anyone filtered by the former parent kept seeing the detached
child's items until an unrelated refetch.

The sixth finding — the flat `config` import in test_data_dir_env — is fixed
on the jamiepine#1004 branch and resolves here on merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Lvigentini
Lvigentini force-pushed the feat/voicebox-data-dir-env branch from 0e99d75 to 2c13dde Compare August 9, 2026 13:46
Lvigentini added a commit to Lvigentini/voicebox that referenced this pull request Aug 9, 2026
Five findings from the review of jamiepine#1005.

Folder assignment on profile creation
-------------------------------------
`create_profile` persisted `folder_id` without checking it. The
folder-assignment endpoint enforces that a voice only lands in a voice
folder, but creation bypassed that entirely — a client could file a new
profile straight into a clip folder, or reference a folder that does not
exist. Same two checks, raised as ValueError so the route returns 400 like
its neighbours.

Whitespace-only names
---------------------
`"   "` passed `min_length=1` because the raw string is three characters
long, and the route then stored `.strip()` — an empty label. Same for the
duplicate-profile override, which could produce a copy with no name at all.
Both now use a `TrimmedName` type that strips first and length-checks the
result. Left `AudioChannelUpdate` and `EffectPresetUpdate` alone; they have
the same gap but are not this PR's concern.

Moved clips lingering in the list
---------------------------------
Moving a clip to another folder invalidated the history query but left
`allHistory` intact. Past page 0 the refreshed first page is appended rather
than replacing the list, so the clip stayed visible in a folder it no longer
belonged to. Folder moves now go through one handler that drops back to page
0, which is what makes the next response replace the list.

Detaching a folder
------------------
`useDetachFolder` invalidated only the folder tree. Member lists include
subfolders, so anyone filtered by the former parent kept seeing the detached
child's items until an unrelated refetch.

The sixth finding — the flat `config` import in test_data_dir_env — is fixed
on the jamiepine#1004 branch and resolves here on merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lvigentini added a commit to Lvigentini/voicebox that referenced this pull request Aug 9, 2026
Five findings from the review of jamiepine#1005.

Folder assignment on profile creation
-------------------------------------
`create_profile` persisted `folder_id` without checking it. The
folder-assignment endpoint enforces that a voice only lands in a voice
folder, but creation bypassed that entirely — a client could file a new
profile straight into a clip folder, or reference a folder that does not
exist. Same two checks, raised as ValueError so the route returns 400 like
its neighbours.

Whitespace-only names
---------------------
`"   "` passed `min_length=1` because the raw string is three characters
long, and the route then stored `.strip()` — an empty label. Same for the
duplicate-profile override, which could produce a copy with no name at all.
Both now use a `TrimmedName` type that strips first and length-checks the
result. Left `AudioChannelUpdate` and `EffectPresetUpdate` alone; they have
the same gap but are not this PR's concern.

Moved clips lingering in the list
---------------------------------
Moving a clip to another folder invalidated the history query but left
`allHistory` intact. Past page 0 the refreshed first page is appended rather
than replacing the list, so the clip stayed visible in a folder it no longer
belonged to. Folder moves now go through one handler that drops back to page
0, which is what makes the next response replace the list.

Detaching a folder
------------------
`useDetachFolder` invalidated only the folder tree. Member lists include
subfolders, so anyone filtered by the former parent kept seeing the detached
child's items until an unrelated refetch.

The sixth finding — the flat `config` import in test_data_dir_env — is fixed
on the jamiepine#1004 branch and resolves here on merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

VOICEBOX_DATA_DIR is ignored by the desktop app (docs say it overrides the data directory) VOICEBOX_DATA_DIR it doesn't seem to work

1 participant