Skip to content

fix: never let a source checkout default to the production data directory - #517

Merged
clstaudt merged 1 commit into
mainfrom
fix/dev-never-writes-production-data
Aug 18, 2026
Merged

fix: never let a source checkout default to the production data directory#517
clstaudt merged 1 commit into
mainfrom
fix/dev-never-writes-production-data

Conversation

@clstaudt

Copy link
Copy Markdown
Contributor

Summary

A dev run from this repo silently migrated the real ~/.tuttle database, after which the installed release refused to start with Schema migration failed (transient): Can't locate revision identified by 'f87515d1d068'. That revision ships in #511, which is on main but not in any released build, so the packaged app's bundled migrations don't contain it.

The root cause is that dev/production isolation was opt-in: get_data_dir() defaulted to the real ~/.tuttle unless TUTTLE_DATA_DIR was set, and the only thing setting it was the just dev recipe. Anything running the core another way — uv run python -m tuttle.rpc_server, a one-off uv run python -c "...", an unpackaged Electron build launched outside just dev — opened the real database, and ensure_schema() upgrades to head on open.

  • tuttle/data_dir.py now derives the default from how the code is running rather than from an env var somebody has to remember. Only a PyInstaller-frozen build defaults to ~/.tuttle; everything unfrozen defaults to ~/.tuttle-dev. TUTTLE_DATA_DIR still takes precedence, so aiming dev tooling at real data (e.g. after just sync-data) stays possible but has to be deliberate. This reuses the sys.frozen test tuttle/db_schema.py already makes for the same distinction.
  • tuttle/rpc_server.py logs the resolved data directory at startup. Nothing ever announced which database was in use, which is what made this invisible until a release build choked on it.
  • justfilejust calendar-setup genuinely needs the real ~/.tuttle (the macOS calendar permission grant is tied to that path, and the recipe already opened the helper from there), so it now sets TUTTLE_DATA_DIR explicitly instead of relying on the old ambient default.

Why sys.frozen is a trustworthy production signal: tuttle-rpc.spec is PyInstaller, and both .github/workflows/pack-electron.yml and .github/workflows/core-smoke.yml build the core through it, so every shipping artifact is frozen.

Notes for the reviewer

  • Behavior change: just demo-reset previously reset the demo user inside the real ~/.tuttle; it now hits ~/.tuttle-dev. That matches the neighbouring just reset and is the intended direction, but it is a change rather than a no-op.
  • The [project.scripts] tuttle-rpc console script is an unfrozen entry point. Nothing ships through it today, but a future Linux package that installs the script instead of the frozen binary would need TUTTLE_DATA_DIR=~/.tuttle.
  • Not addressed here: the packaged branch of ui/electron/python-bridge.ts still passes an ambient TUTTLE_DATA_DIR through to the frozen binary, so launching the installed app from a shell exporting the dev dir points production at dev data. Pre-existing and the opposite direction from this bug; happy to lock it down too.
  • This does not repair an already-migrated ~/.tuttle. That needs a build containing f87515d1d068, or the .bak-* snapshot ensure_schema() takes before every upgrade.

Test plan

  • uv run pytest — 571 passed, 1 skipped
  • tuttle_tests/test_data_dir.py rewritten to pin both defaults: unfrozen resolves to ~/.tuttle-dev, frozen to ~/.tuttle, explicit env var overrides both
  • Verified at runtime, not just against mocked sys.frozen: unfrozen with no env var resolves ~/.tuttle-dev; sys.frozen = True resolves ~/.tuttle; TUTTLE_DATA_DIR overrides both
  • ruff check and npx tsc --noEmit clean; pre-commit hooks pass
  • Someone on a fresh clone confirming just dev still lands in ~/.tuttle-dev and that just calendar-setup still gets its permission grant

No product UI surface changed — this is core/tooling only, so no Electron screenshots.

Checklist

  • I have read the Contributing guide.
  • The test suite passes locally (just test).
  • Pre-commit hooks are installed and pass (just precommit).
  • I have added or updated tests where appropriate.
  • I have updated the documentation / docstrings where appropriate (CONTRIBUTING.md, module docstring).
  • My change does not touch the schema (tuttle/model.py), so no migration is needed.
  • UI verification evidence is included for product UI changes, or this PR has no product UI surface.
  • I understand and can explain all submitted changes. AI assistance was significant: the diagnosis and patch were produced with Cursor, then reviewed and corrected (an earlier revision of this patch duplicated the .tuttle-dev default into python-bridge.ts, which was dropped as a second source of truth).

Made with Cursor

…tory

Dev/production data isolation was opt-in: get_data_dir() defaulted to the
real ~/.tuttle unless TUTTLE_DATA_DIR was set, and the only place setting
it was the `just dev` recipe. Anything that ran the core another way —
`uv run python -m tuttle.rpc_server`, a one-off `uv run python -c`, an
unpackaged Electron build launched outside `just dev` — silently opened
the user's real database and, because ensure_schema() upgrades to head on
open, could stamp it with an unreleased migration. An installed release
then refuses to start with "Can't locate revision identified by ...".

Derive the default from how the code is running instead. Only a
PyInstaller-frozen build (the sole shipping artifact — see tuttle-rpc.spec
and the pack-electron / core-smoke workflows) defaults to ~/.tuttle;
everything unfrozen defaults to ~/.tuttle-dev. TUTTLE_DATA_DIR still wins,
so pointing dev tooling at real data stays possible but has to be
deliberate. This reuses the sys.frozen test db_schema.py already makes for
the same dev-vs-production distinction.

Also log the resolved data directory at RPC startup: nothing ever
announced which database was in use, which is what made this invisible.

`just calendar-setup` genuinely needs the real ~/.tuttle (the macOS
calendar permission grant is tied to that path, and it already opened the
helper from there), so it now says so explicitly rather than relying on
the old ambient default.

Co-authored-by: Cursor <cursoragent@cursor.com>
@clstaudt
clstaudt added this pull request to the merge queue Aug 18, 2026
Merged via the queue into main with commit 2c98fb7 Aug 18, 2026
3 checks passed
@clstaudt
clstaudt deleted the fix/dev-never-writes-production-data branch August 18, 2026 17:15
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.

1 participant