Skip to content

[skycap] Pull a run's records and step index from W&B - #2436

Merged
kouroshHakha merged 4 commits into
skycap/wandb-indexfrom
skycap/record-pull
Oct 8, 2026
Merged

kouroshHakha merged 4 commits into
skycap/wandb-indexfrom
skycap/record-pull

Conversation

@kouroshHakha

@kouroshHakha kouroshHakha commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

TLDR: record_index pull turns a run's W&B record index (#2351) back into a local record directory: the step's records, read from the mirror (#2350), plus the step index. Stacked on #2351.

uv run --isolated --extra skyrl-train --extra harbor --extra skycap \
  python -m examples.train_integrations.harbor_skycap.record_index \
  pull <entity>/<project>/skycap-records-train-<run id>[:<alias>] <out_dir>

How it works

  • Versions: an alias (train-step-3, v7) pulls that version; with no alias, every version is pulled.
  • Per version:
    • Only step.json comes from W&B, written as out_dir/index/<phase>/step-<N>.json.
    • Each mirrored record's files are read straight from the mirror through fsspec with the caller's credentials (s3fs for s3://, gcsfs for gs://), so read access to the mirror is enough. W&B's own reference download isn't used: for s3:// it needs s3:ListBucketVersions, which a reader may not have.
    • Files land in out_dir with an atomic rename from a scratch directory inside it. A file already there and identical is left alone, so pulling again only adds what's new.
  • Fails open per record:
    • a record with an unreadable file is reported missing, naming the files;
    • a record that can't move in is reported, and files it newly placed are taken back out;
    • a file name that isn't a plain basename is refused;
    • a version without a readable step.json is reported, and the other versions still pull.
  • Local-only records (no mirror) are indexed and reported at host:path, so they can be fetched from the node that wrote them.
  • Output: the command prints a summary and exits non-zero only when nothing was pulled.

Test plan

  • tests/integrations/harbor_skycap/test_record_pull.py, with a fake W&B API and an in-memory mirror: one version and all versions; identical files left alone; an unreadable file; a record that can't move in; an unsafe name; a bad version; local-only rows; exit codes. pytest tests/integrations/harbor_skycap: 66 passed.
  • On a real run (Qwen3-4B, 1 step, mirror on S3): 8 records and 16 files pulled, byte-identical to the training node's record_dir, plus index/train/step-1.json.

🤖 Generated with Claude Code


Note

Low Risk
Offline tooling with defensive path validation and isolated test coverage; no changes to training or W&B indexing callbacks.

Overview
Adds record_index pull, a CLI (and pull() API) that reconstructs a local skycap record directory from W&B training artifacts so runs can be opened in skycap-viewer.

For each artifact version it downloads step.json from W&B, writes it under index/<phase>/step-<N>.json, and fetches mirrored record bytes directly from the object-store mirror via fsspec (avoiding W&B reference downloads that need extra S3 permissions). Optional alias pulls one version; omitting it pulls all versions. Fails open per version/record (missing files skip whole records; local-only rows are reported with host:path). Idempotent moves use SHA256 checks so re-pulls only add new content. PullSummary and exit code 1 only when nothing was pulled.

README documents usage; tests/integrations/harbor_skycap/test_record_pull.py covers the flow with a fake W&B API and memory mirror.

Reviewed by Cursor Bugbot for commit d2f7dca. Bugbot is set up for automated code reviews on this repo. Configure here.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a pull command-line utility to the record_index module, allowing users to retrieve a run's records and step index from Weights & Biases (W&B) into a local directory. It includes comprehensive unit tests mocking the W&B API and updates the documentation accordingly. The feedback suggests optimizing the file comparison in _move_in by checking file sizes before computing SHA-256 digests to avoid unnecessary hashing.

Comment thread examples/train_integrations/harbor_skycap/record_index.py
@greptile-apps

greptile-apps Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 3/5

[Medium risk] Adds a pull command to fetch run records from W&B.

The PR should not merge until downloaded filenames are prevented from overwriting files outside the selected directory.

Findings

  1. P1 Security Downloaded filenames escape the directory ▶
  2. P2 One error stops a version ▶
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A["Artifact reference"] --> B{"Alias supplied?"}
  B -->|Yes| C["Resolve one version"]
  B -->|No| D["List all versions"]
  C --> E["Download into scratch directory"]
  D --> E
  E -->|Download raises| F["Fetch entries individually"]
  E --> G["Read step.json"]
  F --> G
  G --> H{"All record files present?"}
  H -->|No| I["Report missing record"]
  H -->|Yes| J["Move sidecars, then document"]
  I --> K["Write step index"]
  J --> K
  K --> L["Return pull summary"]
Loading

Reviews (1) · Last reviewed commit: "[skycap] Pull a run's records and step i..." · Reviewed by Greptile

Comment thread examples/train_integrations/harbor_skycap/record_index.py Outdated
Comment thread examples/train_integrations/harbor_skycap/record_index.py

@kouroshHakha kouroshHakha left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

leaving reviews.

Comment thread examples/train_integrations/harbor_skycap/README.md Outdated
Comment thread examples/train_integrations/harbor_skycap/record_index.py Outdated
kouroshHakha and others added 3 commits October 8, 2026 00:52
`python -m examples.train_integrations.harbor_skycap.record_index pull
<entity>/<project>/<artifact>[:<alias>] <out_dir>` (and `pull()`) turns the
record index's artifact back into a record directory: per version, W&B
downloads the record files from the mirror with the caller's credentials,
they move into out_dir by atomic rename (an identical file already there is
left alone), and step.json goes to index/<phase>/step-<N>.json. With an
alias it pulls that version, without one every version.

It fails open per record: a record whose files can't all be fetched is
reported and left out whole, the rest go on; a version that can't be read is
reported and the others go on. Local-only records are indexed with nothing
to pull, and reported. It prints a summary and exits non-zero only when
nothing was pulled.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Kourosh Hakhamaneshi <kourosh@anyscale.com>
The summary names where a record that isn't in the mirror is, from the index
row's `record.host` and `path` (`10.0.0.5:/data/record/tr_c.json.zst`), so it
can be fetched from the node that wrote it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Kourosh Hakhamaneshi <kourosh@anyscale.com>
…at can't move in; no viewer mentions

- A record file name from the downloaded step.json must be a plain file name
  (no `/`, `\`, `.`, `..`), or the record is reported and not pulled: a crafted
  `../x` would otherwise move a file out of out_dir.
- A record that can't move in (a directory where a file goes) is reported, the
  files it newly placed are taken back out, and the other records and the
  step index still go in.
- Comparing an existing file checks its size before hashing.
- The docs no longer mention the viewer: this pulls a run's records out of
  W&B, and readers of a record directory are their own business.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Kourosh Hakhamaneshi <kourosh@anyscale.com>
…mes from W&B

W&B's download of an s3:// reference added with checksum=False first calls
ListObjectVersions, which a role that can read the bucket may not be allowed
(s3:ListBucketVersions): on a real run every record failed to fetch that way.
pull now takes only step.json from W&B and reads each record file from the
mirror through fsspec with the caller's credentials, so read access to the
mirror is enough. A file it can't read is left out, and the record is
reported missing, naming exactly the files that failed. The whole-artifact
download and its entry-by-entry fallback are gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Kourosh Hakhamaneshi <kourosh@anyscale.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d2f7dca. Configure here.

fs, path = fsspec.core.url_to_fs(uri)
fs.get_file(path, str(dest / name))
except Exception as error: # noqa: BLE001 - this record is reported missing
logger.warning(f"skycap record pull: fetching {uri} failed: {type(error).__name__}: {error}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Partial fetches treated as complete

Medium Severity

A failed fs.get_file can leave an empty or partial file in the scratch directory. _move_record only checks that each name is_file(), so that leftover is treated as a complete fetch and the record is moved in and counted as pulled.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d2f7dca. Configure here.

@kouroshHakha
kouroshHakha merged commit ef2a873 into skycap/wandb-index Oct 8, 2026
8 checks passed
kouroshHakha added a commit that referenced this pull request Oct 8, 2026
…ull writes a directory per phase (#2441)

**TLDR:** lands #2351 (the per-step W&B record index) and #2436
(`record_index pull`) on `main`. Both were reviewed and merged, but into
their stacked base branches after #2350 had already landed, so neither
reached `main`. On top of that, `pull` writes each phase as a record
directory of its own.

---------

Signed-off-by: Kourosh Hakhamaneshi <kourosh@anyscale.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — d2f7dcaa Deployed Oct 8, 2026 by vercel[bot]
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