Skip to content

fix: propagate the API run id to the tracker - #1327

Merged
benoit-cty merged 8 commits into
masterfrom
fix/api-run-id-never-propagates
Nov 11, 2026
Merged

benoit-cty merged 8 commits into
masterfrom
fix/api-run-id-never-propagates

Conversation

@davidberenstein1957

@davidberenstein1957 davidberenstein1957 commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator

Description

EmissionsTracker.run_id used to start as a locally generated uuid and never got updated once the API created its own run id, because CodeCarbonAPIOutput creates the API run lazily and nothing wrote the resulting id back onto the tracker. This PR moves the API run creation into start() and makes run_id switch to the API run id as soon as it exists. If the API is down at start(), run_id switches as soon as a later emission creates the run, with a warning that earlier records used the local id.

Related Issue

Fixes #1303

Motivation and Context

With OutputMethod.API enabled, tracker.run_id stayed None and _prepare_emissions_data recorded the literal string "None" in every row. This broke joining CSV data with API data, broke on_csv_write="update" (it matched on "None" and overwrote the previous run's row), and caused per-task / BOAMPS filenames to collide on ..._None.csv.

How Has This Been Tested?

tests/test_emissions_tracker.py::TestCarbonTracker::test_run_id_with_api_output_is_never_none asserts tracker.run_id is not None and that the run id written to emissions.csv matches the API run id. It fails on master and passes with this change.

uv run pytest tests/test_emissions_tracker.py tests/output_methods tests/test_tracking_inference.py tests/test_api_call.py -q passes locally.

Screenshots (if appropriate):

N/A

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

AI Usage Disclosure

  • 🟥 AI-vibecoded: You cannot explain the logic. Car analogy : the car drive by itself, you are outside it and just tell it where to go.
  • 🟠 AI-generated: Car analogy : the car drive by itself, you are inside and give instructions.
  • ⭐ AI-assisted. Car analogy : you drive the car, AI help you find your way.
  • ♻️ No AI used. Car analogy : you drive the car.

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the docs/how-to/contributing.md document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@codecov

codecov Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.95%. Comparing base (ab8627a) to head (b6a0a4b).
⚠️ Report is 9 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1327      +/-   ##
==========================================
+ Coverage   93.86%   93.95%   +0.09%     
==========================================
  Files          51       51              
  Lines        5734     5757      +23     
==========================================
+ Hits         5382     5409      +27     
+ Misses        352      348       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@davidberenstein1957
davidberenstein1957 marked this pull request as ready for review August 12, 2026 19:14
@davidberenstein1957
davidberenstein1957 requested a review from a team as a code owner August 12, 2026 19:14
@davidberenstein1957
davidberenstein1957 force-pushed the fix/api-run-id-never-propagates branch from 08cdabf to 0221c73 Compare August 19, 2026 14:01
@benoit-cty

Copy link
Copy Markdown
Contributor

🤖 This review comment was written and posted by Claude Opus 5.5 (AI assistant), at the request of @benoit-cty. Findings were checked by reading the code and running tests locally (merged with current master where relevant), but please double-check before acting on them.

Verdict: ✅ Approve with one fix suggested

The bug is real. With API output, tracker.run_id stays None, so every CSV row gets the string "None". The new tests fail on master. The API run is now created in start(), with a fallback to a locally generated id if that fails. Each tracker has its own API output, so multiple trackers are fine.

Issue to fix: CSV and API ids can still differ (medium)

  • If the API is down at start(), run_id keeps the local id (emissions_tracker.py ~L712-719).
  • Later, _emit() calls _ensure_api_run() (output_methods/http.py L60-63), which creates the API run once the API is back, with a different id from every CSV row. This happens silently, and defeats the goal of "a single id everywhere".
  • Suggested fix: when the API run is eventually created, sync self.run_id from _api_output.run_id, e.g. in _prepare_emissions_data, and log once that earlier CSV rows used the local id. Or, if the API accepts a client-supplied run id, create the run with the local id.
  • Please add a test: API fails at start(), recovers at the first _emit(), then assert the ids agree afterwards.

Nits:

  1. start() now makes a blocking HTTP POST (2 s timeout) on the caller's thread. That's acceptable, but worth a line in the docs.
  2. A ConnectionError is logged twice, once in _create_run and once in start().
  3. run_id read before start() differs from the one after. Document this or keep it consistent.
  4. The description mentions a run_id property that isn't in the code; please update it.
  5. The branch is behind master; please update it.

@davidberenstein1957

Copy link
Copy Markdown
Collaborator Author

Made the changes in d53e2de: run_id now syncs to the API run once it's created late (with a test for API down at start then recovering), the start() fallback logs once as a warning, docs mention the blocking call and the run_id switch, PR description fixed, and master merged in.

@benoit-cty
benoit-cty force-pushed the fix/api-run-id-never-propagates branch from d53e2de to f75bb46 Compare October 31, 2026 14:03

Copy link
Copy Markdown
Contributor

Rebased onto master and reviewed (requested by @benoit-cty).

  • Rebase: dropped the merge-from-master commit. Resolved one conflict in tests/test_emissions_tracker.py by keeping master's 4 new BoAmps metadata tests and this PR's run_id tests. Head is now f75bb46 (f180ff2, b778b2c by David, f75bb46 added).
  • Review follow-up: the medium issue (ids differing after the API comes back) is handled in b778b2c, with a test. Nits 1, 3, 4 and 5 are done.
  • Fixed in f75bb46: nit 2 was not actually done. A failed run creation was still logged twice: an ERROR from the API client, then again in the start() warning. The warning now only says that the local run_id is used, and the test was updated.
  • Coverage: 100% of the changed lines in codecarbon/ (output_methods/http.py at 100%). Pre-commit passes. Full package suite: 831 passed. The 4 failures in test_rapl_mmio_scanning.py / test_rapl_permissions.py also fail on master (root sandbox).
  • Docs: docs/how-to/cloud-api.md already covers the blocking call and the run_id switch. No further change.

Open items:

  1. If the API comes back mid-run, the first CSV row after recovery still has the local id, because the switch happens on the next emission. With on_csv_write="update", that gives 2 rows for the run. A warning is logged; maintainer call whether that's acceptable.
  2. This overlaps feat(api): reuse connections, add timeouts, and stamp emissions with measurement time #1340 (_ensure_api_run / _create_run signature). Whichever merges second needs a small conflict fix: start() would pass final=, and the tests' create_run(experiment_id) mocks need the new bypass_cooldown argument.

Generated by Claude Code

@davidberenstein1957

Copy link
Copy Markdown
Collaborator Author

@benoit-cty @inimaz #1327, #1340 and #1334 are rebased on current master, mergeable, and CI is green. Could you review them?

Suggested merge order:

  1. fix: propagate the API run id to the tracker #1327 (this PR): smallest change, fixes tracker.run_id stays None when OutputMethod.API is enabled, CSV rows record run_id="None" #1303.
  2. feat(api): reuse connections, add timeouts, and stamp emissions with measurement time #1340: touches the same _ensure_api_run / _create_run code, so it needs a small follow-up fix after fix: propagate the API run id to the tracker #1327 merges. I will push that fix.
  3. fix: raise on offline tracker configuration errors #1334: independent of the other two. It changes behaviour at construction time, so it targets the v3.4.0 minor release.

davidberenstein1957 and others added 5 commits November 11, 2026 21:52
The tracker generated its own run_id and never picked up the one the API
created, so API-backed runs persisted a local uuid (or the string "None")
instead of the run id the server knows about.

Create the API run in start(), where the output handler is already wired,
and adopt its run id when there is one. run_id stays a plain writable
attribute.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Log the start() fallback once as a warning, and document the blocking
run creation and the run_id switch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ning

The API client already logs why run creation failed, so the start()
warning only states the consequence (local run_id is used).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JfRKgUd281URY6fMyNAQmh
start_task()/stop_task() never call start(), so with the API output the run was only created by the API handler in stop(), after the CSV rows and task files were written. Task records and emissions_base_<run_id>.csv carried a local uuid that never matched the API run id. start() and start_task() now share _adopt_api_run().

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

_prepare_emissions_data() only synced run_id with a run created earlier: the run itself was created afterwards, inside the API handler. When the API was down at start() and came back at stop(), every CSV row and tracker.run_id kept the local id while the API stored the data under a new run, and nothing was logged. The record written at recovery also got the local id. The tracker now tries to create the run before building each record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@benoit-cty
benoit-cty force-pushed the fix/api-run-id-never-propagates branch from f75bb46 to 1da1650 Compare November 11, 2026 21:34
@github-actions github-actions Bot added size/L and removed size/M labels Nov 11, 2026
benoit-cty and others added 2 commits November 11, 2026 22:40
…sk()

With the API unreachable, start_task() tried to create the run twice (explicitly, then in _prepare_emissions_data), even when refused because a task was already active, and stop_task() tried again before computing the task duration, so a blocking request of up to the 2 s timeout was counted in the task duration. start_task() now tries once, after the active-task check, and stop_task() does not try: its record is not sent anywhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_prepare_emissions_data() tries to create the missing API run, then CodeCarbonAPIOutput._emit() tried again for the same record. With the API unreachable, every flush(), stop() and live update waited twice for the timeout (the live update while holding the measurement lock) and logged the error twice. The tracker now builds its API output with create_run_on_emit=False: the output skips the emission when the run is still missing, as master did after a failed creation.

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

Copy link
Copy Markdown
Contributor
Commit Bug it fixes
fix(tracker): do not repeat the API error in the start() fallback warning A failed run creation was logged twice: an ERROR from the API client, then again in the start() warning.
fix(tracker): create the API run in start_task() too start_task()/stop_task() never call start(), so the run was only created at stop(). Task records and emissions_base_<run_id>.csv kept a local uuid that never matched the API run.
fix(tracker): adopt the API run id before writing the record that creates it The run was created inside the API handler, after the record was built. If the API came back at stop(), the CSV and tracker.run_id kept the local id, the API stored the data under a new run, and nothing was logged.
fix(tracker): try the API run creation once per task, outside stop_task() API down: start_task() tried twice (even when refused because a task was active), and stop_task() tried again before computing the task duration. The blocking request, up to the 2 s timeout, was added to the task duration.
fix(api): try the API run creation once per record when the API is down API down: the tracker tried before building each record, then CodeCarbonAPIOutput._emit() tried again. Each flush(), stop() and live update waited twice for the timeout (the live update while holding the measurement lock) and logged the error twice.

Behaviour changes

  • start() and start_task() make a blocking HTTP call when the API output is enabled. It is documented in docs/how-to/cloud-api.md.
  • tracker.run_id changes during the run: local uuid at construction, then the API run id once the run exists. A run_id set by the caller is replaced by the API run id.
  • If the API comes back in the middle of a run, records written before keep the local id. With on_csv_write="update" the CSV then has one row per id, and a warning is logged.
  • CodeCarbonAPIOutput has a new create_run_on_emit argument (default True, unchanged behaviour). The tracker passes False because it creates the run itself. With False and no run, the emission is skipped, which is what happened on master after a failed run creation.

Overlap with #1340: it adds a cooldown to _create_run and a bypass_cooldown/final argument. Whichever PR merges second needs a small fix: _adopt_api_run() and the create_run_on_emit=False branch of _emit(), plus the create_run(experiment_id) mocks in the tests.

@benoit-cty benoit-cty 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.

Thanks

Without a codecov.yml, Codecov computed codecov/project from the first uploads. While the Windows job was still running, Windows-only code (windows_emi.py, hardware.py) looked uncovered and codecov/project failed at -0.31% on a PR that does not touch it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@benoit-cty
benoit-cty enabled auto-merge November 11, 2026 22:01
@benoit-cty
benoit-cty merged commit 0f66973 into master Nov 11, 2026
13 checks passed
@benoit-cty
benoit-cty deleted the fix/api-run-id-never-propagates branch November 11, 2026 22:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tracker.run_id stays None when OutputMethod.API is enabled, CSV rows record run_id="None"

3 participants