Skip to content

Fix local card storage root resolution#3301

Open
arjunxplorer wants to merge 2 commits into
Netflix:masterfrom
arjunxplorer:fix-local-storage
Open

Fix local card storage root resolution#3301
arjunxplorer wants to merge 2 commits into
Netflix:masterfrom
arjunxplorer:fix-local-storage

Conversation

@arjunxplorer

@arjunxplorer arjunxplorer commented Jul 14, 2026

Copy link
Copy Markdown

PR Type

  • Bug fix
  • New feature
  • Core Runtime change (higher bar -- see CONTRIBUTING.md)
  • Docs / tooling
  • Refactoring

Summary

Keep local and spin cards under the resolved artifact datastore root when no explicit card root is configured. Also ensure an explicit METAFLOW_CARD_LOCALROOT is returned and used correctly.

Issue

Fixes #2139

Reproduction

Runtime: Local datastore with a separate datastore sysroot, reproducing the storage configuration used with local Kubernetes.

Commands to run:

python3 -m pytest \
  test/unit/test_card_datastore.py \
  test/core/test_card_local_storage.py \
  -v

Where evidence shows up: The filesystem card location and the card list output exercised by the integration test.

Before (error / log snippet)
AssertionError: Expected cards below configured datastore root
<sysroot>/.metaflow/mf.cards.
assert []

The card HTML was written under <cwd>/.metaflow/mf.cards instead of the configured local datastore root.

The explicit-root unit test also failed because get_storage_root() returned None when CARD_LOCALROOT was configured.

After (evidence that fix works)
test/unit/test_card_datastore.py ... PASSED
test/core/test_card_local_storage.py::test_card_round_trip_uses_configured_local_datastore_root PASSED

10 passed

The card is written under <sysroot>/.metaflow/mf.cards, no stray <cwd>/.metaflow directory is created, and card list finds the card.

Root Cause

Artifact storage resolves METAFLOW_DATASTORE_SYSROOT_LOCAL, but card storage independently searched upward from the current working directory. Consequently, artifacts and cards could use different roots.

Additionally, when CARD_LOCALROOT was configured, the local/spin branch assigned it to a variable but did not return it, causing the method to return None.

Why This Fix Is Correct

Local and spin card roots now follow a single resolution order:

  1. Return the explicitly configured CARD_LOCALROOT.
  2. Otherwise, append mf.cards to the already-resolved artifact datastore root.
  3. If neither root is available, retain the legacy nearest-parent datastore-directory lookup.

The card writer and reader both use this resolution logic, restoring the invariant that cards are read from the same location where they were written.

Failure Modes Considered

  1. An explicit CARD_LOCALROOT continues to take precedence over the artifact datastore root.
  2. The legacy nearest-parent and current-working-directory fallbacks remain available when no resolved datastore root is supplied.
  3. Spin storage follows the corrected local-storage behavior.
  4. S3, Azure, and Google Storage root resolution remains unchanged.

Tests

  • Unit tests added/updated
  • Reproduction script provided (required for Core Runtime)
  • CI passes
  • If tests are impractical: explain why below and provide manual evidence above

Local verification:

10 passed in 1.32s

CI remains unchecked until the pull request checks complete.

Non-Goals

This change does not alter cloud card storage behavior, change card file layout, or remove the existing local fallback behavior.

AI Tool Usage

  • No AI tools were used in this contribution
  • AI tools were used (describe below)

Cursor was used to assist with issue investigation, implementation, comments, and regression tests. I reviewed the changes and ran the relevant tests locally.

Keep local and spin cards under the resolved artifact datastore root when no explicit card root is configured, ensuring card writers and readers use the same location.
@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes local card storage root resolution. The main changes are:

  • Local and spin card roots now prefer an explicit card root.
  • Card roots now fall under the resolved artifact datastore root when available.
  • Empty datastore roots now fall back to the legacy local lookup.
  • Reader and CLI paths now use the shared card root resolver.
  • Tests cover configured local roots, empty roots, spin roots, and cloud roots.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
metaflow/plugins/cards/card_datastore.py Updates local and spin card root resolution to use explicit card roots, resolved datastore roots, and legacy fallback in that order.
metaflow/plugins/cards/card_client.py Routes local and spin reader roots through the shared card root resolver.
metaflow/plugins/cards/card_cli.py Passes the resolved artifact datastore root into card root resolution for CLI card operations.
test/core/test_card_local_storage.py Adds an end-to-end local card round-trip test for configured datastore roots.
test/unit/test_card_datastore.py Adds unit tests for local, spin, empty-root, explicit-root, fallback, and cloud root behavior.

Reviews (2): Last reviewed commit: "Handle empty local card datastore roots" | Re-trigger Greptile

Comment thread metaflow/plugins/cards/card_datastore.py Outdated
Treat empty local and spin datastore roots as missing so card root resolution preserves the legacy lookup behavior for older metadata.

Co-authored-by: Cursor <[email protected]>
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.

Local Storage Card with local K8s

1 participant