Skip to content

fix(files): keep local file blobs in the platform data directory - #1233

Merged
SandyChapman merged 1 commit into
mainfrom
files-storage-data-dir/schapman
Aug 13, 2026
Merged

fix(files): keep local file blobs in the platform data directory#1233
SandyChapman merged 1 commit into
mainfrom
files-storage-data-dir/schapman

Conversation

@SandyChapman

@SandyChapman SandyChapman commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

NMP_DATA_DIR relocated the entity-store database but not the Files service, because both bundled local configurations pinned the literal ~/.local/share/nemo/files for blob storage. Before this change, a local run with the variable set put the database in the chosen directory and the file blobs in the default one; after it, both land under the chosen directory. Behaviour is unchanged when NMP_DATA_DIR is unset, and an explicit path still wins.

A half-isolated instance is worse than an un-isolated one: it looks isolated, so wiping the chosen directory silently leaves the blobs behind. Three places already promised otherwise — LocalServicesConfig.data_dir ("SQLite DB, encryption key, files"), SETUP.md (the wipe warning lists files), and packages/nmp_testing/tests/unit/test_e2e_harness.py, which asserts the rendered path is <data dir>/files. Only the bundled configs disagreed.

Changes

  • LocalStorageConfig.make_path_relative_to_program: an empty path now resolves to <platform user-data dir>/files. Absolute, ~-prefixed, and relative paths behave exactly as before.
  • packages/nmp_platform_runner/src/nmp/platform_runner/config/local.yaml and packages/nmp_platform/config/local.yaml: use the empty sentinel instead of a hard-coded path.
  • New unit tests covering the empty-path resolution, the unchanged no-NMP_DATA_DIR location, explicit paths winning, ~ expansion, and per-construction resolution.

Resolved in the field validator rather than as a field default on purpose: docs/set-up/config-reference.mdx is generated from field defaults and committed, so a default derived from the environment would bake the generating machine's home directory into the repository. This mirrors the SQLite path, computed in get_database_url for the same reason. lint-config-reference-docs passes as a result.

Type of Change

  • Code change (feature, bug fix, or refactor)

Quality Gates

  • Tests added or updated for changed behavior
  • Documentation not applicable — justification: this makes the code match what SETUP.md and LocalServicesConfig.data_dir already document; the generated config reference is unchanged by design.

Verification

  • Pull request title follows the repository's Conventional Commit format
  • Every commit includes an appropriate Signed-off-by: trailer
  • uv run pre-commit run -a passes, or any blocked checks are identified below
  • Targeted tests pass, or tests are marked not applicable above
  • No secrets, API keys, or credentials are included

Targeted validation:

  • uv run --frozen pytest services/core/files/tests/ packages/nemo_platform_plugin/tests/1737 passed, 51 skipped.
  • uv run --frozen pytest packages/nemo_platform_plugin/tests/files/test_storage_config_path.py5 passed (the new tests).
  • uv run pre-commit run -a — all substantive hooks pass: ruff, ruff format, Run ty typechecks, Check config reference doc is up to date, Helm Docs Container, Check for uv.lock drift, Fix copyright headers, Plugins must not import from nmp-common, check for merge conflicts. Two hooks could not run in my local environment, which is why the gate above is left unchecked — neither is reported as passing:
    • Run uv lock with platform uv — requires uv 0.9.14; local uv is 0.9.30. No pyproject.toml is touched by this PR, the separate Check for uv.lock drift hook passes, and CI's Check uv lock job passes on this head.
    • Run UI lint-stagedpnpm unavailable locally (untrusted mise.toml). No web/ files are touched by this PR.
  • tools/lint/lint-all.sh — passes except lint-openapi (fails locally on mapfile: command not found, macOS bash 3.2) and lint-web-sdk (same missing pnpm). Neither is affected by this change; no API surface is added.
  • Manual end-to-end, nemo services run --services entities,files:
    • with NMP_DATA_DIR=<tmp>, a new fileset resolves to <tmp>/files/filesets/default/<name> (previously ~/.local/share/nemo/...).
    • resolution semantics checked directly: empty path with no NMP_DATA_DIR~/.local/share/nemo/files (identical to the value the configs used to hard-code), empty path with it set → <dir>/files, explicit /data/files_storage → unchanged, ~/somewhere → expanded.

Summary by CodeRabbit

  • New Features

    • Local file storage now automatically uses the platform’s user-data directory.
    • Storage locations can be customized through the NMP_DATA_DIR environment setting.
    • Explicit storage paths continue to take precedence, including home-relative paths.
  • Bug Fixes

    • Corrected local storage path handling when no path is configured.
  • Tests

    • Added coverage for environment-based, default, explicit, and dynamically evaluated storage paths.

@SandyChapman
SandyChapman requested review from a team as code owners August 11, 2026 14:23
@github-actions github-actions Bot added the fix label Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8a9be320-2cf7-4e86-89e4-2a746260b7ef

📥 Commits

Reviewing files that changed from the base of the PR and between 24d906c and 9850017.

📒 Files selected for processing (4)
  • packages/nemo_platform_plugin/src/nemo_platform_plugin/files/storage_config.py
  • packages/nemo_platform_plugin/tests/files/test_storage_config_path.py
  • packages/nmp_platform/config/local.yaml
  • packages/nmp_platform_runner/src/nmp/platform_runner/config/local.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/nemo_platform_plugin/src/nemo_platform_plugin/files/storage_config.py
  • packages/nmp_platform_runner/src/nmp/platform_runner/config/local.yaml
  • packages/nmp_platform/config/local.yaml
  • packages/nemo_platform_plugin/tests/files/test_storage_config_path.py

📝 Walkthrough

Walkthrough

Empty local storage paths now resolve to the platform user-data files directory. Configuration defaults delegate to this behavior, while explicit paths and home-relative paths retain their existing resolution.

Changes

Local storage path resolution

Layer / File(s) Summary
Dynamic storage path resolution
packages/nemo_platform_plugin/src/nemo_platform_plugin/files/storage_config.py, packages/nmp_platform/config/local.yaml, packages/nmp_platform_runner/src/nmp/platform_runner/config/local.yaml
Empty paths use the platform user-data directory and NMP_DATA_DIR. Explicit paths and home-relative paths keep their existing behavior.
Path resolution regression coverage
packages/nemo_platform_plugin/tests/files/test_storage_config_path.py
Tests cover configured and unset NMP_DATA_DIR, explicit paths, home expansion, and environment changes between constructions.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change to store local file blobs in the platform data directory.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 files-storage-data-dir/schapman

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.

🧹 Nitpick comments (1)
packages/nemo_platform_plugin/tests/files/test_storage_config_path.py (1)

22-30: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Cover the XDG_DATA_HOME fallback.

When NMP_DATA_DIR is unset, nmp_user_data_dir() uses XDG_DATA_HOME before the home-directory fallback. This test deletes XDG_DATA_HOME, so it does not define behavior on XDG-configured hosts. Add a case with only XDG_DATA_HOME set and assert the intended path. If storage must remain unchanged whenever NMP_DATA_DIR is unset, this case will expose the current mismatch.

🤖 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 `@packages/nemo_platform_plugin/tests/files/test_storage_config_path.py` around
lines 22 - 30, Add a test alongside
test_an_empty_path_without_a_data_dir_keeps_the_previous_location that leaves
NMP_DATA_DIR unset, sets only XDG_DATA_HOME, and asserts
LocalStorageConfig(path="").path matches the intended XDG-based location. Keep
HOME and other unrelated environment variables from affecting the case so it
specifically verifies the XDG_DATA_HOME fallback and exposes any mismatch with
the expected unchanged storage behavior.
🤖 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.

Nitpick comments:
In `@packages/nemo_platform_plugin/tests/files/test_storage_config_path.py`:
- Around line 22-30: Add a test alongside
test_an_empty_path_without_a_data_dir_keeps_the_previous_location that leaves
NMP_DATA_DIR unset, sets only XDG_DATA_HOME, and asserts
LocalStorageConfig(path="").path matches the intended XDG-based location. Keep
HOME and other unrelated environment variables from affecting the case so it
specifically verifies the XDG_DATA_HOME fallback and exposes any mismatch with
the expected unchanged storage behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0fe52866-77b4-409f-b8e7-3ecca800cf02

📥 Commits

Reviewing files that changed from the base of the PR and between 58453d3 and 24d906c.

📒 Files selected for processing (4)
  • packages/nemo_platform_plugin/src/nemo_platform_plugin/files/storage_config.py
  • packages/nemo_platform_plugin/tests/files/test_storage_config_path.py
  • packages/nmp_platform/config/local.yaml
  • packages/nmp_platform_runner/src/nmp/platform_runner/config/local.yaml

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 32019/40720 78.6% 63.5%
Integration Tests 18569/38646 48.0% 20.8%

Comment thread packages/nemo_platform_plugin/src/nemo_platform_plugin/files/storage_config.py Outdated
Comment thread packages/nmp_platform/config/local.yaml Outdated
`NMP_DATA_DIR` relocated the entity-store database but not the Files service:
both bundled local configurations pinned the literal `~/.local/share/nemo/files`
for blob storage. A local run with the variable set therefore put the database
in the chosen directory and the blobs in the default one.

A half-isolated instance is worse than an un-isolated one. It looks isolated, so
wiping the chosen directory silently leaves the blobs behind — and `SETUP.md`
tells users that directory holds "the entity-store database, encryption key,
files, job history, secrets", which was not true. `LocalServicesConfig.data_dir`
documents the same promise, and the e2e harness already renders the files path
as `<data dir>/files`; only the bundled configs disagreed.

An empty `path` on `LocalStorageConfig` now means "the platform user-data
directory", resolved in the existing field validator, and both local
configurations use that instead of a literal path.

Resolved in the validator rather than as a field default on purpose: the
config-reference documentation is generated from field defaults and committed,
so a default derived from the environment would bake the generating machine's
home directory into the repository. This mirrors the SQLite path, which is
computed in `get_database_url` for the same reason.

Behaviour is unchanged for anyone who has not set `NMP_DATA_DIR` — an empty path
resolves to exactly the location the configs used to name — and an explicit path
still wins, which is what the container image, the Helm chart and the agentic
runners all rely on.

Signed-off-by: Sandy Chapman <schapman@nvidia.com>
@SandyChapman
SandyChapman force-pushed the files-storage-data-dir/schapman branch from 24d906c to 9850017 Compare August 12, 2026 18:53
@SandyChapman
SandyChapman enabled auto-merge August 12, 2026 19:26
@SandyChapman
SandyChapman added this pull request to the merge queue Aug 12, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 12, 2026
@SandyChapman
SandyChapman added this pull request to the merge queue Aug 13, 2026
Merged via the queue into main with commit 2de7c84 Aug 13, 2026
94 of 101 checks passed
@SandyChapman
SandyChapman deleted the files-storage-data-dir/schapman branch August 13, 2026 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants