This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A collection of Jupyter notebooks that demonstrate the pattern: source NetCDF files → VirtualiZarr (virtual references) → Icechunk repository. The large science arrays are never copied; Icechunk stores metadata and byte-range references back to the originals.
Done: The CoastWatch OHC archive was first built and verified as three separate Icechunk repos on Source Cooperative under fish-pace/coastwatch/ocean-heat/{na,np,sp}, each with three groups (daily, 14day_v1, 14day). Docs and notebooks are mirrored at the destination root (see below) and everything is committed to main (via PRs #3–#6). The GitHub repo is https://github.com/fish-pace/icechunks.
In progress: Re-pointing the destination to bucket ocean-icechunks, prefix noaa-ohc (repos at ocean-icechunks/noaa-ohc/{na,np,sp}). All source references (notebooks, README, CLAUDE.md, icechunk_utils.py) are updated; still to do: re-point write credentials for the new org and re-run ocean-heat-production-sc.ipynb to actually build the repos at the new location.
Next tasks (not yet started, design open):
- Finish the org rebuild. Source references are already swept to
ocean-icechunks/noaa-ohc. Remaining: re-point write credentials and re-runocean-heat-production-sc.ipynbagainst the new destination. The build logic itself is org-agnostic —open_region/region_prefix/write_regionall derive fromSC_BUCKET/SC_PREFIX_BASE. - Auto-update pipeline to append new CoastWatch files as they land. Undesigned.
write_groupis already idempotent/append-friendly (skips groups that exist), but it does not yet append new time steps to an existing group — that appending path, plus scheduling/triggering when new source files appear, still needs to be figured out.
Notebooks run in JupyterLab. To install dependencies (when needed, each notebook documents its own):
pip install "icechunk>=2.1" "virtualizarr>=2.4" xarray obspec_utils obstore h5netcdf requests matplotlibThe write notebooks (ocean-heat-production-sc.ipynb, ocean-heat-test-sc.ipynb) import shared helpers from icechunk_utils.py. It lives at the repo root (the notebooks add .. to sys.path); when a notebook is downloaded standalone from Source Cooperative, icechunk_utils.py sits alongside it (Jupyter puts the notebook's own directory on sys.path, so the co-located copy imports without changes). ocean-heat-test-local.ipynb needs no helpers and is fully self-contained.
- Create an
ObjectStoreRegistrypointing to the source data location (S3 or HTTPS). - Open each source file virtually with
open_virtual_dataset(..., loadable_variables=[coords], decode_times=True). - Configure an
icechunk.RepositoryConfigwith aVirtualChunkContainerwhoseurl_prefixmust have a trailing/. - Create or open the Icechunk repo with
icechunk.Repository.create/open(storage, config). - Write the first file with
vds.vz.to_icechunk(session.store)and append later files withappend_dim="time". - Commit with
session.commit("message")— nothing persists until this call. - Reopen for reading:
repo.readonly_session("main").store→xr.open_zarr(store, consolidated=False).
- Source Cooperative write credentials: stored in local JSON files (
source-creds.json,source-cefi-creds.json,globcolour-source-creds.json). These are temporary STS tokens with short TTL. Refresh with:/home/jovyan/.cargo/bin/source-coop login --duration 1d --port 8400
- Public Icechunk repos on Source Coop can be read anonymously via
icechunk.http_storage(url). - NOAA S3 sources use
skip_signature=True/anonymous=True. - CoastWatch HTTPS requires a browser-like User-Agent header; the default
python-requestsUA returns 403.
| Notebook | Source | Icechunk destination |
|---|---|---|
virtualizarr_coastwatch_ohc_http_icechunk_demo.ipynb |
NOAA CoastWatch HTTPS NetCDF (OHC) | Local filesystem |
ocean-heat-test-local.ipynb |
NOAA CoastWatch HTTPS NetCDF (OHC) | Local filesystem — minimal proof-of-concept example |
ocean-heat-test-sc.ipynb |
NOAA CoastWatch HTTPS NetCDF (OHC) | Source Coop — minimal proof-of-concept example |
ocean-heat-production-sc.ipynb |
NOAA CoastWatch HTTPS NetCDF/HDF5 (OHC full archive, na/np/sp) | Source Coop (ocean-icechunks/noaa-ohc/{na,np,sp}) |
cefi_nep_daily-regrid.ipynb |
NOAA CEFI MOM6 S3 NetCDF | Source Coop (eeholmes/cefi/nepacific-icechunk) |
copernicus-icechunk-sc.ipynb |
Copernicus GlobColour HTTPS | Source Coop (fish-pace/globcolour/...) |
url_prefixmust end with/inVirtualChunkContainer— missing the slash silently fails to match virtual chunks.authorize_virtual_chunk_accessmust be passed atRepository.open/createtime for virtual chunks outside the Icechunk repo to be readable.- Anonymous read URL must include the bucket.
icechunk.http_storage(url)needs the full pathhttps://data.source.coop/{BUCKET}/{prefix}(e.g.ocean-icechunks/noaa-ohc/na), not just the prefix. A wrong/short URL raisesRepositoryNotFoundError: the repository doesn't existdeterministically — it is not a flaky gateway. When an open 404s, verify the full{bucket}/{prefix}URL by hand before adding retries. (The S3 write path viaopen_source_icechunk_repotakesbucket=separately, soregion_prefix()intentionally omits it.) save_config()is required for anonymous readers.Repository.open(storage, config=...)uses the config only for the current session. To persist theVirtualChunkContainerso anonymous reopeners pick it up, callrepo.save_config()after open/create.- Scalar vs. slice indexing on virtual arrays: prefer
isel(time=slice(0,1), z_l=slice(0,1)).squeeze(drop=True)overisel(time=0, z_l=0)to avoid loading unexpectedly large chunks. - Writable sessions are single-use: after
session.commit(), callrepo.writable_session("main")again before writing more data. - Variables with different file layouts cannot be merged virtually: the CEFI notebook stores yearly-file variables in
daily/regrid/mainand full-period-file variables indaily/regrid/auxfor this reason. - Time-coordinate repair: some source files have corrupt/duplicate time coordinates. Use a trusted template variable (e.g.,
chlos) to repair before passing tovds.vz.to_icechunk.
For repos with 1000s of time steps, configure manifest splitting to avoid giant manifests at commit time:
config.manifest = icechunk.ManifestConfig(
splitting=icechunk.ManifestSplittingConfig.from_dict({
icechunk.ManifestSplitCondition.AnyArray(): {
icechunk.ManifestSplitDimCondition.DimensionName("time"): 100
}
})
)
config.manifest.max_concurrent_manifest_fetches_during_commit = 16| Dataset | URL |
|---|---|
| CEFI NEP daily regrid | https://data.source.coop/eeholmes/cefi/nepacific-icechunk (groups: daily/regrid/main, daily/regrid/aux) |
| GlobColour/Copernicus CHL | https://data.source.coop/fish-pace/globcolour/cmems_obs-oc_glo_bgc-plankton_my_l3-multi-4km_P1D |
| CoastWatch OHC — North Atlantic (2020–present) | https://data.source.coop/ocean-icechunks/noaa-ohc/na |
| CoastWatch OHC — North Pacific (2020–present) | https://data.source.coop/ocean-icechunks/noaa-ohc/np |
| CoastWatch OHC — South Pacific (2020–present) | https://data.source.coop/ocean-icechunks/noaa-ohc/sp |
Each CoastWatch OHC region is a separate repo (different lat/lon grids). Every region repo has three groups: daily (original {region} product, NetCDF-3), 14day_v1 ({region}14 NetCDF-3 big-endian), 14day ({region}14 HDF5 little-endian). The 14day_v1/14day split is at 2025 day 084/085; the daily/14day split is a variable-set/product-generation difference.
The noaa-ohc/ root (alongside the na//np//sp/ repo subfolders) also holds the human-facing docs, mirrored from git: README.md, icechunk_utils.py, and the three notebooks (ocean-heat-test-local.ipynb, ocean-heat-test-sc.ipynb, ocean-heat-production-sc.ipynb). These are reference/reproducibility copies; keep them in sync when the git versions change.