Skip to content

Feat/cmip7 awiesm3 veg hr - #266

Open
JanStreffing wants to merge 526 commits into
prep-releasefrom
feat/cmip7-awiesm3-veg-hr
Open

JanStreffing wants to merge 526 commits into
prep-releasefrom
feat/cmip7-awiesm3-veg-hr

Conversation

@JanStreffing

@JanStreffing JanStreffing commented Apr 2, 2026 •

Copy link
Copy Markdown
Contributor

CMIP7 cmorization for AWI-ESM3-VEG-HR

Adds full CMIP7 support targeting AWI-ESM3-VEG-HR, including a native compound-name
architecture that replaces the legacy cmip6-table-based data request lookup.

Key changes

CMIP7 data request

  • Load DataRequest from CMIP7_DReq_metadata JSON instead of cmip6 tables
  • Refactor to native compound-name architecture (ocean.tos.tavg-u-hxy-sea.mon.GLB)
  • Fix JSON key mismatch: cmip6_table → cmip6_cmor_table in vendored metadata
  • Improve compound_name matching against cmip6_compound_name and cmip7_compound_name attributes
  • Derive table_id from compound name when not set explicitly
  • Strict ValueError on zero DRV matches (instead of silent skip)

Pipeline

  • Add generic vertical_integrate custom pipeline step
  • Remove duplicate convert() step from DefaultPipeline
  • Fix Prefect State objects not being unwrapped to actual results in parallel runs
  • Propagate pipeline/flow errors instead of silently logging them

Standard library

  • Add time bounds support (src/pycmor/std_lib/time_bounds.py)
  • Fix dimension mapping to use getattr + _pycmor_cfg fallback
  • Fix global_attributes to derive table_id from CMIP6/CMIP7 compound names

Xarray accessor API

  • Lazy accessor registration and StdLibAccessor with .process()

Test infrastructure

  • Modernize with entry-point model discovery (pycmor.fixtures.model_runs)
  • Add pycmor.tutorial dataset system (xarray.tutorial-style API)
  • Fix stub generator to use monotonic coordinate values for multi-file datasets

Misc fixes

  • Python 3.9 entry_points() compatibility
  • Guard pyfesom2 imports for environments without it
  • Fix tarball double-nesting extraction on Python 3.12+
  • Rename non-standard time dimension on load (OpenIFS support)

Test plan

  • Unit tests pass: pytest tests/unit/
  • pycmor process examples/awiesm3-cmip7-minimal.yaml runs successfully on Levante
  • core_atm runs with tco95_core2 on Levante
  • core_land runs with tco95_core2 on Levante
  • core_ocean runs with tco95_core2 on Levante
  • core_seaice runs with tco95_core2 on Levante
  • lrcs_land runs with tco95_core2 on Levante
  • lrcs_oce runs with tco95_core2 on Levante
  • lrcs_seaice runs with tco95_core2 on Levante
  • cap7_atm runs with tco95_core2 on Levante
  • cap7_ocean runs with tco95_core2 on Levante
  • cap7_seaice runs with tco95_core2 on Levante
  • cap7_land runs with tco95_core2 on Levante
  • cap7_aerosol runs with tco95_core2 on Levante
  • veg_atm runs with tco95_core2 on Levante
  • veg_land runs with tco95_core2 on Levante
  • veg_seaice runs with tco95_core2 on Levante
  • extra_atm runs with tco95_core2 on Levante
  • extra_land runs with tco95_core2 on Levante
  • final check of all rules.

@JanStreffing
JanStreffing changed the base branch from main to prep-release April 2, 2026 12:55
@JanStreffing
JanStreffing force-pushed the feat/cmip7-awiesm3-veg-hr branch from 1a42875 to 5617a18 Compare April 2, 2026 13:18
@JanStreffing

Copy link
Copy Markdown
Contributor Author

I was able to run both tos and the more complex absscint with the lastest commit on this branch. We may want to work on #267, and certainly need to work on #265. But neither should block us from starting to build up more rules for the picontrol variables.

@JanStreffing

JanStreffing commented Apr 5, 2026 •

Copy link
Copy Markdown
Contributor Author

The single failing test (test_library_process[fesom_dev-native-dask-cmip6]) is unrelated to this PR. It failed due to a network issue — the test tried to download pi_uxarray.tar from nextcloud.awi.de but got ConnectionError: [Errno 111] Connection refused. The AWI Nextcloud server was unreachable from the GitHub Actions runner.

Something to worry about? @pgierz

@esm-tools esm-tools deleted a comment from github-actions Bot Apr 6, 2026
@esm-tools esm-tools deleted a comment from github-actions Bot Apr 6, 2026
@esm-tools esm-tools deleted a comment from github-actions Bot Apr 6, 2026
@esm-tools esm-tools deleted a comment from github-actions Bot Apr 6, 2026
@esm-tools esm-tools deleted a comment from github-actions Bot Apr 6, 2026
@esm-tools esm-tools deleted a comment from github-actions Bot Apr 6, 2026
@esm-tools esm-tools deleted a comment from github-actions Bot Apr 6, 2026
@esm-tools esm-tools deleted a comment from github-actions Bot Apr 6, 2026
@esm-tools esm-tools deleted a comment from github-actions Bot Apr 6, 2026
@esm-tools esm-tools deleted a comment from github-actions Bot Apr 6, 2026
@mandresm

mandresm commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

@pgierz, will you review this or should I go for it?

@pgierz

pgierz commented Apr 7, 2026

Copy link
Copy Markdown
Member

I will look

@esm-tools esm-tools deleted a comment from github-actions Bot Apr 8, 2026
@esm-tools esm-tools deleted a comment from github-actions Bot Apr 8, 2026
@esm-tools esm-tools deleted a comment from github-actions Bot Apr 8, 2026
return ds


def _attach_bounds_from_mesh(ds, rule, coord_names):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Check if generic enough for backend

return any(v.chunks is not None for v in ds.data_vars.values())


def _encoding_from_dask_chunks(ds, rule):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Deferred for later review


if table_id is None:
# Fallback to user-provided
table_id = self.rule_dict.get("table_id", None)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should be checked. Why deleted?

"""
return self.rule_dict.get("Conventions", "CF-1.11")

# ========================================================================

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should be a class. Do we need it?

return "hdl:21.14100/" + str(uuid.uuid4())
"""Generate a unique tracking ID (prefix overridable via rule_dict).

The CMIP7 tracking_id CV requires the ``hdl:21.14107/<uuid>`` prefix

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Does that mean cmip6 wont work anymore?

approx_interval = drv.table_header.approx_interval
frequency_str = _frequency_from_approx_interval(approx_interval)
logger.debug(f"{approx_interval=} {frequency_str=}")
# attach the frequency_str to rule, it is referenced when creating file name
rule.frequency_str = frequency_str
time_method = _get_time_method(drv.frequency)
rule.time_method = time_method
# FESOM yearly files and concat'd hemispheric selects can yield a

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hallucination? xarray can sort itself?

Die HR-Submission verarbeitet ein Modelljahr pro Job und pinnt jedes
Eingabemuster auf dieses Jahr. Die neun dec-Regeln haben deshalb bisher
ein einziges Jahr gemittelt und das Ergebnis als ganze Dekade
ausgewiesen; jedes weitere Jahr derselben Dekade schrieb dieselbe Datei
mit einem anderen Einjahresmittel.

Neuer Schritt decadal_gate am Anfang jeder dec-Pipeline, ohne Zustand:
er zaehlt das erste Jahr des Laufs jedes Mal neu aus den Eingabedateien,
zaehlt Dekaden ab diesem Jahr und laesst die Regel nur in dem Jahr durch,
das eine Dekade schliesst. Dann weitet er das Muster auf alle Jahre und
setzt year_start/year_end auf die Dekade, load_mfdataset liest genau die
zehn Jahre. In jedem anderen Jahr gibt er RuleSkipped zurueck; die
Pipeline-Runner halten dort an und _process_rule zaehlt die Regel als
erledigt, ohne Ausgabe. Fehlt ein Jahr einer schliessenden Dekade, ist
das ein Fehler, kein Skip.

Dekaden ab Laufbeginn statt Kalenderdekade, damit ein Szenario ab 2024
keine sechsjaehrige erste Zelle bekommt. Die Grenzen folgen deshalb der
vom Gate gewaehlten Dekade. Eine unvollstaendige letzte Dekade wird nie
geschrieben: cc-plugin-wcrp#81 meldet Zellen, die nicht zehn Jahre lang
sind, als HIGH (nachgemessen mit 5a4239e fuer 2024-2030 und 2040-2046;
volle Dekaden 1850-1860, 1851-1861 und 2024-2034 bestehen).

Nachgemessen: timeavg ('10YS') ergibt fuer zehn Jahre genau einen Wert,
fuer Start 1850 und 2024, datetime64 und cftime. Elf neue Unit-Tests,
Rest unveraendert (70 wie vorher).
@JanStreffing
JanStreffing force-pushed the feat/cmip7-awiesm3-veg-hr branch from d7468f7 to 01a9526 Compare September 14, 2026 10:33
JanStreffing and others added 25 commits September 14, 2026 14:19
- Sanity-Check-Artefakte (1464 PNG, HTML, CSS, JSONL) aus dem Index
  genommen. Die Dateien bleiben auf der Platte und werden von
  tools/sanity_check neu erzeugt. Die markdown-Berichte bleiben getrackt.
- Notizen und Design-Dokumente nach notes/ verschoben, damit die
  Wurzel nur noch CLAUDE.md, GETTING_STARTED.md und QUICK_START_CMIP7.md
  enthaelt. Session-Notizen liegen in notes/scratch/ und sind ignoriert.
- Wrapper-Skripte der Sanity-Checks und die Benchmark-Skripte aufgenommen,
  damit die Laeufe reproduzierbar sind.
Ausloeser war cli120: der extra_atm-Shard wurde nach 2 h vom eigenen
Inaktivitaets-Watchdog abgebrochen, obwohl er noch schrieb, 17 Dateien
fehlten. Fuer ~200 Jahre pro Experiment muss das ohne Handarbeit gehen.

- Ursache: der Save-Heartbeat sah nur das Ausgabeverzeichnis. Die Dateien
  entstehen aber zuerst im knotenlokalen /tmp und landen erst am Ende im
  Ziel, also meldeten alle 17 Saves nach 15 min einen Stillstand und
  verstummten. Jetzt zaehlen die Staging-Dateien als Fortschritt, und der
  Heartbeat tickt nach einem Stillstand langsamer weiter statt aufzuhoeren.
- Der Shard-Watchdog prueft Log, Ausgabeverzeichnis und /tmp-Staging und
  bricht nur ab, wenn alle drei stehen.
- Regelfehler blieben in serial_process haengen, ein Shard ohne eine
  einzige Datei endete mit Exit 0. Jetzt schreibt pycmor pro Shard ein
  Manifest (ok, skipped, no_output, failed) und endet mit Exit 1, sobald
  eine Regel nichts erzeugt hat. Ueber das Dekaden-Gate abgeschaltete
  Regeln zaehlen als vollstaendig.
- examples/run_year_driver.py treibt ein Jahr bis zur Vollstaendigkeit:
  einreichen, warten, Manifeste pruefen, nur die betroffenen Tiers neu
  einreichen, nach drei Versuchen eine Mail schicken. Bei Erfolg entsteht
  <WORKDIR>/<JAHR>.done.
examples/run_year_chain.sbatch treibt ein Jahr durch run_year_driver.py,
reicht danach das naechste Jahr ein und endet mit dem Ergebnis des
Treibers. Laeuft auf `shared` (1 Kern, 3 Tage), also ohne Login-Sitzung.

- Mail kommt von SLURM (--mail-type=FAIL,TIME_LIMIT) und nur vom
  Kettenjob, die Shard-Jobs mailen nie. Die drei Versuche laufen in
  diesem einen Job, der erst nach dem letzten mit Exit 1 endet: eine Mail
  pro Jahr, das nicht fertig wurde, keine pro Wiederholung. TIME_LIMIT
  steht extra drin, weil FAIL keinen Timeout abdeckt.
- Die Einzelheiten stehen in <WORKDIR>/<JAHR>.FAILED, SLURMs Mail nennt
  nur den Job (pycmor-y<JAHR>).
- Ein gescheitertes Jahr haelt die Kette nicht an, spaetere Jahre brauchen
  nur Modellausgabe. Nach zwei gescheiterten Jahren in Folge stoppt sie,
  weil das auf etwas Systematisches deutet.
- Fertige Jahre (<JAHR>.done) werden beim Neustart sofort uebersprungen.
sbatch --mail-user ist keine Umgebungsvariable und ging ab dem zweiten
Jahr verloren. Der Kettenjob liest die Adresse jetzt per scontrol aus
sich selbst und gibt sie beim naechsten Einreichen weiter.
Beides kam im cli121-Test der Jahreskette heraus.

- run_hr_shard.sh stellt jede Shard-Yaml auf parallel/dask um, das
  Manifest hing aber nur in serial_process. Kein Shard schrieb eines, der
  Treiber hielt ein vollstaendiges Jahr fuer leer und reichte alles neu
  ein, und der Exit-Code-Schutz griff ebenso wenig. Unter Dask laeuft die
  Regel auf einem Worker mit einer Kopie des Rule-Objekts, deshalb gibt
  _process_rule jetzt eine kleine Zusammenfassung (geschriebene Dateien,
  Skip-Grund) zurueck, und der Dask-Pfad ordnet Futures ihren Regeln zu.
  Neuer Test faehrt den echten Dask-Pfad mit einer Worker-Kopie.
- Das DRS-Versionsverzeichnis war das Datum jedes einzelnen Schreibens.
  Ein Lauf ueber Mitternacht landete in zwei Versionen, jede Wiederholung
  an einem spaeteren Tag verdoppelte jeden Datensatz, und eine Jahreskette
  haette die 196 Jahre einer Variable auf ~100 Versionen verteilt. Jetzt
  per directory_date (Regel/inherit) oder PYCMOR_DRS_VERSION festlegbar;
  die Kette setzt es einmal und reicht es durch, der Treiber haelt es ueber
  alle Versuche fest.
…immt

run_hr_shard.sh setzt pipeline_orchestrator: dask, aber der Schluessel steht
nicht im Config-Schema. process() faellt deshalb auf _parallel_process_prefect
zurueck, und dort fehlte der Manifest-Aufruf. In cli122 schrieb kein Shard ein
Manifest. Der neue Test geht ueber die echte Config und process(), damit er
dem Pfad folgt, den die Produktion nimmt.

Regeln, die nie zurueckmelden, zaehlen jetzt als failed, damit ein
abgebrochener Lauf kein sauber aussehendes Manifest hinterlaesst.
Variablen mit Zeitachse werden ueber xr.save_mfdataset geschrieben und nicht
ueber _atomic_to_netcdf, deshalb landeten sie nie in rule._written_files. In
cli122 meldeten 509 von 529 Regeln no_output, obwohl alle Dateien auf der
Platte lagen; nur fx-Felder wurden erkannt. Der Treiber rechnete das Jahr
daraufhin dreimal und gab auf.

Geprueft mit zwei echten Shards auf Levante (core_land, cap7_aerosol):
vorher 2/10 und 0/5 ok, jetzt 10/10 und 5/5.
…itert

Voruebergehende Probleme (Knoten faellt aus, langsames Dateisystem, Watchdog)
treffen einzelne Stufen. Scheitern alle zusammen, liegt die Ursache bei allen
gemeinsam: Code, Eingabedaten, voller Plattenplatz. Wiederholen verbrennt dann
nur Knotenstunden. In cli122 hat der Treiber so ein komplettes Jahr zweimal
umsonst neu gerechnet. Jetzt schreibt er nach dem ersten Versuch .FAILED und
beendet sich mit 1, was die eine SLURM-Mail ausloest.

Ein Jahr mit nur einer Stufe ist nicht eindeutig und wird weiter wiederholt.
Gegen die echten cli122-Manifeste nachgespielt: 17 von 17 Stufen schlecht,
der Treiber haette nach Versuch 1 aufgehoert.
…i pro Experiment

Mehrere Experimente sollen gleichzeitig cmorisiert werden koennen, ohne dass
Skriptkopien auseinanderlaufen. Bisher lief jeder Job aus dem lebenden
Checkout, ein Fix mitten in einer 150-Jahre-Kette hat also den Code auf halber
Strecke geaendert, und experiment_id: piControl stand fest in allen 17
Stufen-yamls.

Eine Kampagne ist ein Verzeichnis mit campaign.yaml. start_campaign.sh klont
pycmor nach <kampagne>/code auf code_ref und pinnt den Commit. Jeder Jahresjob
prueft vor dem Start, dass Code und die datenbestimmenden Felder (run_root,
output_root, drs_version, inherit) noch dem Pin entsprechen, und laeuft mit
PYTHONPATH auf die gepinnte Kopie. Bewusstes Umpinnen: setup --repin, wird in
pin_history.log protokolliert.

campaign.py apply schreibt die inherit-Werte der Kampagne in die erzeugten
Stufen-yamls und biegt die absoluten Pfade in den Checkout auf die gepinnte
Kopie um; das Ergebnis wird strukturell gegen das Original geprueft.

Fertige Jahre verschiebt der Treiber in output_root, nur vollstaendige Jahre
landen also im ESGF-Baum. Ein Neustart eines schon vollstaendigen Jahres
veroeffentlicht nur noch, statt neu zu rechnen. Jobnamen tragen den
Kampagnennamen, Manifeste den Commit.
Every FESOM sea-ice field is written by XIOS as _FillValue where it is
physically zero and never as 0 — measured at 73-93 % fill with zero exact
zeros — and runoff behaves the same way at 54 %. Rules branded hxy-u or
hxy-sea must publish 0 there, while hxy-si rules must keep the fill. This
extends affd468 from siconc to the other eight.

cap7_ocean: nan_to_zero in scale_pipeline, for friver (source runoff).
lrcs_seaice: same step in scale_pipeline (siflfwbot_omon; also reaches vsfsit,
a no-op there since virtsalt has no fill), sfdsi_from_fw_ice_pipeline,
sihc_pipeline and ice_mass_transport_pipeline (sidmasstranx/y, whose product
of uice and m_ice is NaN wherever there is no ice); plus a new
nan_to_zero_pipeline — a copy of DefaultPipeline.STEPS with the step — for
sidconcdyn, sidconcth and sitimefrac_day, which ran on <default> and could
not gain it there: that pipeline is shared with 17 rules in the tier, most of
them hxy-si.

Placement is always before timeavg. timeavg skips NaN, so averaging first
would average only the ice-covered days instead of averaging the zeros in —
the monthly mean of a flux differs.

sitimefrac_day was writing raw a_ice, a concentration, under a
fraction-of-time standard_name; it now uses a sitimefrac_pipeline like the
monthly rule, so a cell at 30 % concentration reads 1.0 rather than 0.3.
compute_sitimefrac itself no longer collapses fill: xr.where(data > 0, 1.0,
0.0) sent NaN down the 0.0 branch, which is right on an ocean-only mesh but
publishes "ice never present" over every continent on gr. It now preserves
missingness and the pipelines apply the policy with nan_to_zero, which
generate_gr_yaml swaps for nan_to_zero_over_ocean. That also fixes the
monthly sitimefrac on gr.

Checked: the 13 rules now carrying the step are all u- or sea-branded, none
masked; the gr generator injects the sst reference for all of them without
per-rule configuration; yamllint on the touched files is back to the two
pre-existing duplicate-grid errors on siconca, reported separately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mit #281 gibt es Experiment-Metadaten zweimal: die Profile in
repoint_hr_year.py und den inherit-Block der Kampagne. Jetzt eine Quelle,
die Profile. Sie liegen in git und werden mit dem Code gepinnt.
campaign.yaml nennt nur noch `experiment: 1pctCO2`, die Kette gibt das als
EXPERIMENT an den Repointer weiter. Ohne das hielte der Laufname-Schutz aus
#281 jede 1pctCO2-Kampagne an.

inherit in der Kampagne bleibt fuer Werte, die kein Profil abdeckt, und darf
keinen Profilschluessel wiederholen. apply prueft, dass jeder Profilwert in
jeder Stufen-yaml angekommen ist. Getestet fuer alle vier Profile ueber die
17 echten Stufen-yamls.
…the campaign

Found by the 1pctCO2 1850 test campaign, which came up short on five shards.

Land walltime: cap7_land_03, core_land_00 and extra_land_00/01 were all
still saving when SLURM cancelled them at exactly 3h (TIMEOUT, no error,
watchdog quiet). cap7_land, core_land and extra_land now get 6h, like
extra_atm and cap7_atm.

pfull_mon: the climatology accumulator directory was hardcoded to
/work/bb1469/a270092/cmorized/climatologies, in the rule and as the
default, so anyone but its owner hit PermissionError, and every campaign
would have pooled its years into one shared accumulator. campaign.py now
exports PYCMOR_CLIMATOLOGY_STATE_DIR=<campaign>/climatologies, and
custom_steps.py reads the rule's setting first, then that variable, then
the old default. The rule no longer sets it, so runs outside a campaign
are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AWI-ESM3 has no CO2 tracer, so co2 was listed as blocked. But in a
concentration-driven run the CO2 the model used is known exactly, and in
1pctCO2 and abrupt-4xCO2 it is the forcing that matters.

co2mass_mon (atmos.co2mass.tavg-u-hm-u.mon.glb) reproduces
ece_cmip_ghg.F90: the input4MIPs value of NCMIPFIXYR (forcing_year),
grown 1%/yr (L1pctCO2), times RNxCO2=4 (LANxCO2), or the file's own year
(historical), with the model's monthly interpolation between annual
values. Mass = that mole fraction * M(CO2)/M(air) * the atmosphere's mass
from the model's monthly global-mean surface pressure (OpenIFS RA, RG).

The experiment profiles in repoint_hr_year.py set co2_scenario
(historical, 1pct, abrupt4x); piControl keeps the tier default, fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GHG scalar rules read forcing_year 1850 for every experiment. Right
for piControl, 1pctCO2 and abrupt-4xCO2, which hold the gases at 1850;
wrong for historical, which would have shown 1850 values in every year.

forcing_year now mirrors the model's NCMIPFIXYR for all gases: the
historical profile sets it to null, so the values follow the model year,
interpolated monthly between the annual ones as ece_cmip_ghg.F90 does
(year y's value reached in June). With a fixed year the output is
unchanged.

co2_scenario now only changes CO2, like L1pctCO2/LANxCO2: file (default;
piControl and historical), 1pct, abrupt4x. Historical CO2 needs no
scenario of its own any more; forcing_year null covers it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
broadcast_forcing_year_to_monthly passed the open_mfdataset series, a dask
array, to oifs_ghg_mole_fraction, whose .item() xarray refuses on dask. ch4,
n2o, cfc11 and cfc12 failed in the test-1pctCO2 1850 run (3536ff2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CMIP7 definiert realization_index, initialization_index, physics_index und
forcing_index mit ihrem Buchstaben: cmor-cvs.json verlangt ^r[0-9]+$ usw.,
esgvoc cmip7 ebenso. pycmor schrieb wie bei CMIP6 die nackte Zahl. Der alte
cc-plugin-wcrp hat das nicht geprueft; mit 2.4.0b1 faellt jede Datei viermal
mit ATTR004 (HIGH) durch.

Geprueft an einer echten cli122-Datei mit korrigierten Attributen: cf,
wcrp_cmip7 und aicc jeweils 0 Befunde. CMIP6 bleibt unveraendert.
Beides kam im Testjahr test-1pctCO2 y1850 (5.10.) heraus: extra_land_shard_01
hing mit drei Saves 5,8 h fest, lief ins 6-h-Walltime-Limit, und das Jahr
wurde trotzdem als vollstaendig veroeffentlicht.

- Der Watchdog wertete jede neue Logzeile als Fortschritt. Seit dem
  cli120-Fix schreibt der Heartbeat auch im Stillstand weiter ("no I/O
  progress yet"), ein verklemmter Shard hielt sich so selbst am Leben. Diese
  Zeilen zaehlen jetzt nicht mehr; Fortschritt sind andere Logzeilen oder
  wachsende Staging-Dateien im knotenlokalen /tmp. Das gemeinsame
  Ausgabeverzeichnis wird nicht mehr beobachtet, dort schreiben alle Shards
  des Jahres. Mit dem echten Block in vier Faellen getestet.
- Das Manifest des Shards vom Lauf drei Tage zuvor lag noch da und sagte
  "alles ok". Der Treiber loescht jetzt die Manifeste einer Stufe, bevor er
  sie neu einreicht, und akzeptiert kein Manifest, das von anderem Code als
  dem gepinnten stammt.
…omises a resume

clear_manifests matched the tier exactly, but submit_hr_year_shards.sh
selects TIER by *TIER*, so with WITH_GR=yes a retry of <tier> also reruns
<tier>_gr and its old manifests stayed. The .FAILED text said a rerun picks
up from existing outputs; it resubmits every tier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eans up a failed one

With WITH_GR=yes veg_seaice_gr has no rules (its only rule needs the FESOM
mesh). ls on its empty shard dir failed under pipefail, so
submit_hr_year_shards.sh died after queuing 22 of 24 tiers and the
'no shards; skipping' branch was never reached. Count with find instead.

The driver then exited and left the 22 arrays running with nothing to
assess or publish them, and wrote no .FAILED although the chain log
points to it. A failed submission now cancels the jobs it queued and
writes .FAILED with the tail of the submit output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

4 participants