Skip to content

Encode Celery tasks and results as JSON - #850

Merged
lewisjared merged 31 commits into
mainfrom
json-task
Jul 29, 2026
Merged

Encode Celery tasks and results as JSON#850
lewisjared merged 31 commits into
mainfrom
json-task

Conversation

@lewisjared

@lewisjared lewisjared commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

Replaces pickle with a JSON wire format for Celery messages, compresses them, and fixes non-finite floats breaking metric ingestion on PostgreSQL.

Closes #839, closes #845, closes #847.

  • Encodes tasks and results as JSON (ref-json). Pickle let anything that could write to the broker run arbitrary code in a worker, so it is no longer in accept_content. A rolling deploy with pickled messages still in flight can set CELERY_ACCEPT_CONTENT to include pickle until the queues drain.
  • Only the diagnostic's slug crosses the wire and the receiver resolves it against its own provider registry. This also fixes CondaDiagnosticProvider pickles a snapshot of os.environ into every task message #847, because the client's environment snapshot no longer travels inside the pickled provider and thread caps or DASK_SCHEDULER set on worker pods now apply to queued work.
  • The attrs classes are converted with cattrs. Hand-rolled codecs remain only where cattrs has no answer: cftime dates, non-finite floats and the dtype-faithful dataset frame.
  • Compresses task and result bodies with gzip by default, which shrinks them by roughly 80% and cuts broker memory on a full solve. The JSON encoding is also about 40% smaller than the equivalent pickle before compression. Issue Expose task_compression: 41Ki pickled messages saturate the broker on a full solve #845 suggested defaulting compression off, but the broker memory saving makes on the better default. Override with CELERY_TASK_COMPRESSION and CELERY_RESULT_COMPRESSION, or set either to an empty string to disable.
  • Stores non-finite floats as null in JSON columns, so a metric series containing NaN can be ingested on PostgreSQL rather than rejecting the whole row and leaving the group dirty (PostgreSQL: metric ingestion fails when a series contains NaN (invalid JSON) #839). The sanitising happens on the column type and no migration is required.

Checklist

Please confirm that this pull request has done the following:

  • Tests added
  • Documentation added (where applicable)
  • Changelog item added to changelog/

Notes

  • CondaDiagnosticProvider no longer captures os.environ as instance state. It keeps only its own overrides and merges the live process environment at command launch, completing the fix suggested in CondaDiagnosticProvider pickles a snapshot of os.environ into every task message #847.
  • Rolling upgrades must roll the workers before any client that submits tasks, because an old worker cannot decode ref-json. The helm README documents this and the pickle drain escape hatch.

Summary by CodeRabbit

  • New Features
    • Switched Celery task and result wire format to JSON-based “ref-json”, with defaults updated to accept json,ref-json (pickle no longer on the wire).
    • Enabled gzip compression for tasks/results by default, with environment-variable controls to override or disable.
    • Added upgrade guidance to purge queued messages when moving away from pickle-based compatibility.
  • Bug Fixes
    • Non-finite numeric values (NaN/Infinity/-Infinity) are now stored as JSON null in the database.
  • Documentation
    • Updated configuration and Helm chart docs with the new wire format, compression controls, and rolling-upgrade guidance.
  • Operational Improvements
    • Improved Kubernetes deployment failure diagnostics for faster worker troubleshooting.

json.dumps writes bare NaN and Infinity tokens by default.
SQLite stores them without complaint but PostgreSQL validates on insert and rejects the whole row,
so an execution could succeed while ingesting 0 metric values.
Sanitising on the column type covers every JSON write rather than the one reported path.

Closes #839
Pickle let anything that could write to the broker run arbitrary code in a worker.
The wire format is now JSON and pickle is no longer in accept_content.
Only the diagnostic's slug crosses the wire,
and the receiver resolves it against its own provider registry.
This also fixes a latent bug where the client's conda prefix and HOME
travelled inside the pickled diagnostic and overrode the worker's configuration.

Closes #847
Message bodies are dominated by the datasets frame in the execution definition
and shrink by roughly 80%, which cuts broker memory on a full solve.
Override with CELERY_TASK_COMPRESSION and CELERY_RESULT_COMPRESSION,
or set either to an empty string to disable.

Closes #845
The attrs classes now unstructure and structure through a cattrs converter,
so a new field on ExecutionResult or DatasetCollection serialises without hand copying.
The hand-rolled codecs remain only where cattrs has no answer:
cftime dates, non-finite floats and the dtype-faithful dataset frame.
Numeric columns are emitted directly rather than dispatched cell by cell.
Also collapses the duplicate lookup loop in provider_by_slug
and reads the compression settings through env like their neighbours.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Celery transport migration

Layer / File(s) Summary
REF codec and Celery configuration
packages/climate-ref-celery/src/..., packages/climate-ref-celery/tests/unit/...
Celery registers ref-json, uses JSON-based task and result serializers, and supports configurable gzip compression.
Celery deployment guidance
helm/README.md, helm/values.yaml, docs/configuration.md, changelog/850.*.md
Documentation describes compression, ref-json acceptance, and migration from pickle messages.

Core wire serialisation and provider resolution

Layer / File(s) Summary
Tagged wire format and domain structures
packages/climate-ref-core/src/climate_ref_core/serialisation.py, packages/climate-ref-core/tests/unit/test_serialisation.py
Tagged JSON conversion supports cftime values, paths, non-finite floats, dataframes, datasets, execution definitions, and results.
Diagnostic resolution and live environments
packages/climate-ref-core/src/climate_ref_core/{diagnostics,providers}.py, packages/climate-ref-core/tests/unit/test_providers.py, packages/climate-ref-pmp/..., stubs/cftime/__init__.pyi
Execution definitions resolve diagnostics lazily, provider environments merge at launch time, PMP overrides use the new storage, and cftime typing declarations are expanded.

Non-finite JSON database handling

Layer / File(s) Summary
Database JSON sanitisation
packages/climate-ref/src/climate_ref/models/base.py, packages/climate-ref/tests/unit/models/test_non_finite_json.py, changelog/850.fix.md
Non-finite floats are recursively converted to JSON null before binding, with database and strict-JSON regression tests.

Deployment failure diagnostics

Layer / File(s) Summary
Expanded worker failure capture
.github/workflows/packaging.yaml, helm/ci/gh-actions-values.yaml
Deployment failures collect pod, broker, Celery, py-spy, and current or previous PMP worker diagnostics, with ptrace capability enabled.

Sequence Diagram(s)

sequenceDiagram
  participant CeleryApp
  participant KombuRegistry
  participant CeleryProducer
  participant Broker
  participant CeleryWorker
  CeleryApp->>KombuRegistry: register ref-json
  CeleryProducer->>KombuRegistry: encode and gzip task
  CeleryProducer->>Broker: publish task message
  Broker->>CeleryWorker: deliver task message
  CeleryWorker->>KombuRegistry: decode ref-json
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also includes unrelated comment/type-only edits in cmip6_to_cmip7.py, datasets/utils.py, and dataset_registry.py. Move those cleanup-only edits into a separate PR so this one stays focused on the linked issue fixes.
Docstring Coverage ⚠️ Warning Docstring coverage is 39.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: encoding Celery tasks and results as JSON.
Description check ✅ Passed The description follows the template and includes the required Description and Checklist sections with all items checked.
Linked Issues check ✅ Passed The changes address #839, #845 and #847 by nulling non-finite floats, exposing task compression, and removing the env snapshot from queued work.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch json-task

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

- Breaks the remaining hard-wrapped comments and docstrings at clause boundaries.
- Notes in the helm README that workers upgrade before clients.
- Adds a roundtrip test asserting every ExecutionDefinition field survives the wire,
  since the structure hook lists the fields by hand.
@lewisjared
lewisjared marked this pull request as ready for review July 28, 2026 03:43
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.27273% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ate-ref-core/src/climate_ref_core/serialisation.py 95.04% 3 Missing and 3 partials ⚠️
Flag Coverage Δ
core 92.87% <97.24%> (+0.08%) ⬆️
providers 87.33% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...s/climate-ref-celery/src/climate_ref_celery/app.py 100.00% <100.00%> (ø)
...f-celery/src/climate_ref_celery/celeryconf/base.py 93.93% <100.00%> (+0.60%) ⬆️
...ref-celery/src/climate_ref_celery/serialisation.py 100.00% <100.00%> (ø)
...te-ref-core/src/climate_ref_core/cmip6_to_cmip7.py 94.34% <100.00%> (ø)
...-ref-core/src/climate_ref_core/dataset_registry.py 99.16% <ø> (ø)
...imate-ref-core/src/climate_ref_core/diagnostics.py 98.23% <100.00%> (+0.86%) ⬆️
...climate-ref-core/src/climate_ref_core/providers.py 95.20% <100.00%> (+0.60%) ⬆️
...es/climate-ref-pmp/src/climate_ref_pmp/__init__.py 100.00% <100.00%> (ø)
...ages/climate-ref/src/climate_ref/datasets/utils.py 93.79% <100.00%> (ø)
...ackages/climate-ref/src/climate_ref/models/base.py 100.00% <100.00%> (ø)
... and 1 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

CondaDiagnosticProvider kept a snapshot of os.environ from construction
and passed it to every command it launched.
The provider now stores only its own overrides
and merges them over the live process environment at launch,
so settings applied to a worker reach every execution.
HOME still falls back to the conda prefix when the environment does not set it.
* origin/main: (35 commits)
  test(datasets): isolate parallel worker setting
  chore: clean up docs
  feat(datasets): configure parallel fetch workers
  test(datasets): verify netcdf access stays on caller thread
  fix(datasets): prepare cache directories before parallel fetch
  docs: add parallel registry fetch changelog
  perf(datasets): fetch registry files concurrently
  chore: rename the exported variable
  fix: point the missing-data warning at the fetch, not at setup
  fix(pmp): do not download micromamba while building the provider registry
  test: re-mint the sea ice baselines for the reference requirement
  fix: name the reference source type the way a request is read
  test: harden the offline fixtures after review
  fix: freeze the shared reference facets
  fix: address review of the sea ice reference requirement
  test: prove the offline and read-only paths with blocked sockets
  docs: add changelog fragment for #846
  feat(esmvaltool): select the OSI-450 observations for sea ice area
  feat(esmvaltool): ingest reference data during provider setup
  fix(core): address the CodeRabbit review
  ...

# Conflicts:
#	packages/climate-ref-core/src/climate_ref_core/providers.py
#	packages/climate-ref-pmp/src/climate_ref_pmp/__init__.py
#	packages/climate-ref-pmp/tests/unit/test_provider.py
@lewisjared

lewisjared commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Smoke tested this on a Kubernetes deployment (k3s, CNPG Postgres, Dragonfly broker, real CMIP6 data), running the pr-850 image in an isolated namespace against the published climate-ref-aft chart 0.5.0.

Chart 0.5.0 sets CELERY_ACCEPT_CONTENT in defaults.env to the JSON array string:

CELERY_ACCEPT_CONTENT: |
  ["json", "pickle"]

The old code ignored it, because accept_content was hardcoded. This PR reads it with env.list(), which splits on commas, so it yields ['["json"', '"pickle"]'] and every worker dies at startup:

SerializerNotInstalled: No encoder/decoder installed for ["json"

The PR removes that default from helm/values.yaml, so a matched chart-and-image upgrade is fine, but I need a follow up PR to the other repo.

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

🧹 Nitpick comments (5)
.github/workflows/packaging.yaml (1)

179-180: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid an unbounded KEYS('*') scan in the failure path.

KEYS('*') blocks the broker while scanning every key, and r.type(k) adds one request per key. A backlog during a failed deployment can therefore delay or exhaust the diagnostic job before logs are collected. Prefer SCAN with bounded batches and a pipeline for the type/length queries.

packages/climate-ref-core/src/climate_ref_core/serialisation.py (2)

148-181: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Frame encoding assumes unique column names and str(dtype)-reconstructible dtypes.

Two narrow gaps worth noting:

  • _decode_frame assigns columns by name, so a frame with duplicate column labels collapses to a single column.
  • str(dtype) is not a faithful round-trip for parameterised extension dtypes (e.g. category loses the categories and ordered flag).

Neither is exercised by the current tests. If catalogs can never hold either, a short comment saying so would be enough; otherwise consider validating uniqueness and rejecting dtypes that cannot be reconstructed.


27-28: 🗄️ Data Integrity & Integration | 🔵 Trivial

Consider a wire-format version marker.

The rollout depends on workers and clients agreeing on this encoding, and today a mismatch surfaces only as Unknown tagged value or a KeyError on a missing field. A single top-level version integer in the envelope would let the receiver fail with an explicit "message produced by a newer client" error, which is much easier to triage during a staged upgrade.

packages/climate-ref-core/src/climate_ref_core/providers.py (1)

382-424: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Lookup can import every installed provider, contrary to the docstring.

The name-match ordering only puts the likely candidate first; when the entry-point name does not equal provider.slug, the loop keeps importing the remaining providers until one matches. On a worker that means pulling in heavy packages (ESMValTool, PMP) just to resolve one slug, and the docstring's "only the provider that matches is ever loaded" no longer holds.

Since the name-is-the-slug convention is already assumed for ordering, consider trying the exact name match first and only falling back to a scan (with a debug log noting the fallback), or soften the docstring.

Also note @functools.cache never invalidates: entry points installed or providers reloaded later in the same process are not picked up, which mainly bites in tests that manipulate the entry-point set.

packages/climate-ref-core/tests/unit/test_providers.py (1)

726-747: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Uncovered branch: a non-matching provider that fails to import.

provider_by_slug deliberately swallows InvalidProviderException for entry points whose name differs from the requested slug (providers.py Lines 415-419), and that skip path is the subtle part of the function. Consider a test that patches importlib.metadata.entry_points with one broken entry point plus a good one and asserts the good provider is still returned. Note provider_by_slug is @functools.cached, so such a test needs provider_by_slug.cache_clear().


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2945fa07-8eb2-40b8-b4f4-d88dc3fe5571

📥 Commits

Reviewing files that changed from the base of the PR and between 31edcb8 and fa24d4f.

📒 Files selected for processing (25)
  • .github/workflows/packaging.yaml
  • changelog/850.fix.md
  • changelog/850.improvement.2.md
  • changelog/850.improvement.md
  • helm/README.md
  • helm/values.yaml
  • packages/climate-ref-celery/src/climate_ref_celery/app.py
  • packages/climate-ref-celery/src/climate_ref_celery/celeryconf/base.py
  • packages/climate-ref-celery/src/climate_ref_celery/serialisation.py
  • packages/climate-ref-celery/tests/unit/test_app.py
  • packages/climate-ref-celery/tests/unit/test_celeryconf.py
  • packages/climate-ref-celery/tests/unit/test_serialisation.py
  • packages/climate-ref-core/src/climate_ref_core/cmip6_to_cmip7.py
  • packages/climate-ref-core/src/climate_ref_core/diagnostics.py
  • packages/climate-ref-core/src/climate_ref_core/providers.py
  • packages/climate-ref-core/src/climate_ref_core/serialisation.py
  • packages/climate-ref-core/tests/unit/test_providers.py
  • packages/climate-ref-core/tests/unit/test_serialisation.py
  • packages/climate-ref-pmp/src/climate_ref_pmp/__init__.py
  • packages/climate-ref-pmp/tests/unit/test_provider.py
  • packages/climate-ref/src/climate_ref/datasets/utils.py
  • packages/climate-ref/src/climate_ref/models/base.py
  • packages/climate-ref/tests/unit/models/test_non_finite_json.py
  • packages/climate-ref/tests/unit/test_offline.py
  • stubs/cftime/__init__.pyi

Comment thread .github/workflows/packaging.yaml Outdated
Comment thread packages/climate-ref-celery/src/climate_ref_celery/celeryconf/base.py Outdated
Comment thread packages/climate-ref-core/src/climate_ref_core/diagnostics.py
Comment thread packages/climate-ref-core/src/climate_ref_core/serialisation.py Outdated
Comment thread packages/climate-ref-core/tests/unit/test_serialisation.py
Comment thread packages/climate-ref/src/climate_ref/models/base.py
`_encode_scalar` returned unknown values unchanged, so a `datetime64` or
`bytes` cell reached `json.dumps` and raised a generic `TypeError` far from
the column that caused it. It now raises, and `_encode_values` names the
offending column or the index.

Collapses the duplicate scalar dispatch in `to_wire` onto `_encode_scalar`,
which already covers every case it handled plus `pd.NA` and `pd.NaT`.
Numpy scalars are unwrapped once at the top rather than re-dispatched, which
keeps the function under the return limit.
`SeriesMetricValue.values` is assigned from `TSeries.load_from_json(...)`, so
a value can arrive as a numpy float rather than a Python one. `isinstance`
misses those, and a non-finite float32 reached the JSON column unchanged.
The step runs under errexit, so a failed download or a component with no pod
aborted it before the worker logs were collected.
`CELERY_ACCEPT_CONTENT` is read with `env.list`, so it takes a comma
separated list rather than the JSON array the chart used to inject.
The diagnostic is a live object that no longer crosses the wire, so it takes no part in equality.
Its slug was excluded too, which left two definitions running different diagnostics
over the same datasets comparing equal.

The slug is now derived at construction, so both sides of the wire hold it as a field
and equality compares which diagnostic a definition runs.
A diagnostic that has not been registered with a provider yet still has no slug to derive,
so building a definition from one stays permitted.
NaN was encoded as JSON null while the infinities were tagged and restored.
Inside a dataset frame the recorded column dtype restored it either way,
but a NaN on its own has no dtype to be restored from, so it came back as None.

Tagging it keeps the two layers honest about their different jobs.
The wire stays faithful to the value it was given,
and the database column remains the only place a non-finite float becomes null,
which is what #839 asked for.
CELERY_TASK_SERIALIZER and CELERY_RESULT_SERIALIZER were env overridable and undocumented,
which let a deployment silently start sending pickle again.
That is the thing dropping pickle set out to stop.

What a process sends is now fixed.
What it accepts stays configurable through CELERY_ACCEPT_CONTENT,
because a worker may still meet messages another release encoded.
The pickle drain escape hatch does not work.
The diagnostic field was renamed on ExecutionDefinition,
so an old pickled message restores into an object with neither slot set
and execution_slug raises AttributeError.
Adding pickle back to CELERY_ACCEPT_CONTENT gets the body decoded and then the task fails anyway.

Making it work would need compat shims that resurrect the pickled provider
and the environment snapshot this release removed, so the docs now say what is true.
Purge the queues on upgrade and re-solve to requeue the outstanding work.

Also documents the Celery wire format settings outside the helm chart,
and drops a duplicate CELERY_ACCEPT_CONTENT row from the chart README.
changelog/README.md asks for phrases in the past tense,
and 848.improvement.md already followed it while the 850 fragments did not.
Also collapses them to a single paragraph, which the same file prefers for anything but a feature.
Sorting on a bool to float the name match to the front needed a comment to be readable.
Two explicit lists say the same thing on their own.

Covers the fallbacks the comment describes, none of which had a test:
the named entry point is the only one imported when it matches,
a provider whose name and slug disagree is still found,
a broken provider we were not asked for is skipped rather than masking the wanted one,
and failing to import the provider actually asked for propagates.

provider_by_slug is cached for the life of the process,
so the tests clear it or a mocked set of entry points is answered from an earlier test.
The comment survived the rewrite from a loop into a per-file callback.
There is no next file to move onto now, the callback returns.
Asserting on registry._encoders couples the test to kombu private state.
Encoding and decoding a payload proves the same thing through the public interface.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
helm/README.md (1)

209-221: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider calling out the JSON-array pitfall explicitly.

The comma-separated format is documented correctly, but the smoke test on chart 0.5.0 showed the real-world failure mode: a chart supplying CELERY_ACCEPT_CONTENT as a JSON array string (e.g. ["json", "pickle"]) gets mis-parsed by env.list() into bogus serializer names, causing SerializerNotInstalled. Since the fix for the active default lives in the chart repo (per the PR comments), a short note here warning users with custom overrides not to pass a JSON-array-formatted value would help avoid re-hitting this exact issue.

📝 Suggested addition
 Tasks and results are encoded as JSON (`ref-json`).
 What this release sends is fixed, so `CELERY_ACCEPT_CONTENT` only widens what a worker will decode.
+
+Set `CELERY_ACCEPT_CONTENT` as a comma-separated string (e.g. `json,ref-json`), not a JSON array
+(e.g. `["json", "ref-json"]`). Some Helm chart versions supply the latter, which parses into
+invalid serializer names and causes workers to fail with `SerializerNotInstalled`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 216da35d-d703-4d5c-950d-435c29d05d31

📥 Commits

Reviewing files that changed from the base of the PR and between 4703210 and 315b32a.

📒 Files selected for processing (15)
  • changelog/850.fix.md
  • changelog/850.improvement.2.md
  • changelog/850.improvement.md
  • docs/configuration.md
  • helm/README.md
  • helm/values.yaml
  • packages/climate-ref-celery/src/climate_ref_celery/celeryconf/base.py
  • packages/climate-ref-celery/tests/unit/test_app.py
  • packages/climate-ref-celery/tests/unit/test_celeryconf.py
  • packages/climate-ref-core/src/climate_ref_core/dataset_registry.py
  • packages/climate-ref-core/src/climate_ref_core/diagnostics.py
  • packages/climate-ref-core/src/climate_ref_core/providers.py
  • packages/climate-ref-core/src/climate_ref_core/serialisation.py
  • packages/climate-ref-core/tests/unit/test_providers.py
  • packages/climate-ref-core/tests/unit/test_serialisation.py
🚧 Files skipped from review as they are similar to previous changes (9)
  • changelog/850.improvement.2.md
  • packages/climate-ref-celery/tests/unit/test_app.py
  • packages/climate-ref-core/src/climate_ref_core/diagnostics.py
  • packages/climate-ref-celery/tests/unit/test_celeryconf.py
  • helm/values.yaml
  • packages/climate-ref-celery/src/climate_ref_celery/celeryconf/base.py
  • packages/climate-ref-core/src/climate_ref_core/serialisation.py
  • packages/climate-ref-core/src/climate_ref_core/providers.py
  • packages/climate-ref-core/tests/unit/test_providers.py

Comment thread docs/configuration.md Outdated
Deriving the slug at construction read _provider directly,
which broke every ILAMBStandard because it never calls Diagnostic.__init__
and so has no such attribute to read.

Reading it with a default keeps construction working for a subclass that skips the base init.
Flower runs from the stock mher/flower image with its own celeryconfig,
so it cannot import the REF codec and refused every message once tasks stopped being pickled.
The deployment test logged sixteen ContentDisallowed errors for application/x-ref-json.

A ref-json body is plain JSON, so the chart registers a codec that reads it with the json module.
Tagged values arrive as their raw mappings, which is all the task views need.
Pickle is no longer in the accepted list there either, matching the rest of the deployment.
py-spy returned Permission Denied on every pod, so the failure capture collected no stacks.
Granting SYS_PTRACE to the containers cannot work,
because they run as a non-root user and Kubernetes has no way to set ambient capabilities,
so the capability never reaches the process.

Widening the Yama scope on the node lets a process dump another running under the same user,
which is what the kubectl exec needs.
py-spy never produced a stack. It needs to attach to a process it did not start,
which the pods cannot allow because they run as a non-root user
and Kubernetes has no way to grant the capability that would permit it.

Celery already dumps every thread's stack when it receives SIGUSR1,
and the dump goes to the log the next step collects.
That needs no download, no copy into each pod and no ptrace, so the node sysctl goes too.
The pull request appeared four times in the compiled changelog, twice in each section.

The wire format, the compression that rides on it and the knobs that tune it are one story,
so they are now one entry.
The Flower codec is not a fix from a reader's point of view,
because nothing shipped with Flower broken.
What is left of it is the upgrade note for anyone overriding `flower.celeryConfig`.

The PostgreSQL fix stays on its own, because it is a database bug that has nothing to do with Celery.
@lewisjared
lewisjared merged commit 64faa3a into main Jul 29, 2026
23 checks passed
@lewisjared
lewisjared deleted the json-task branch July 29, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant