Skip to content
Harness Engineering2026-10-089 min read

A Job ID Is Not a Result: Check the Output Before Marking It Done

A scheduler's job ID, like a deploy hook's 200, says a request was accepted. A five-state model and a small check that marks work done only when the results file exists, from the right checkpoint, with every row.

Key takeaways

  • A job ID confirms that a scheduler accepted the job, not that it ran, finished or wrote a usable result.
  • The number you report must come from an observed artifact: the results file, from the checkpoint you meant, with one row per case.
  • A five-state model (eligible, requested, running, verified, failed) keeps the request lifecycle apart from the observed outcome.
  • Mark work complete only on an observed result, and retry only unmarked work, so recovery neither strands a failure nor repeats a success.
  • Blind retries after a lost reply create duplicates: with a 10% chance of losing the request and 20% of losing the reply, 0.286 extra effects per task.

Rod Rivera

Author

A Job ID Is Not a Result: Check the Output Before Marking It Done

Rod's note: read with a pencil; the margins are for you.

The acknowledgment is not the result

You submit a training or evaluation job to a cluster scheduler and get back a job ID. That ID tells you one thing: the scheduler accepted the job. The job can still wait in the queue, start, run out of memory, be preempted, produce a loss of NaN, or finish and write its checkpoint somewhere the evaluation step never looks. An evaluation harness can crash after writing half its results file, and the half-file still parses.

Each of those is a different fact, and "the job was accepted" establishes none of them. The number you report in a paper table or a work sample must come from an observed artifact: the results file, with the expected number of rows, from the checkpoint you meant to evaluate.

The mistake is easy to build into automation. A script submits the job, gets an ID, and writes "done" to a tracker. Everything downstream treats that marker as proof: no retry gets scheduled, no alert fires, the dashboard shows a green check. This site made the same mistake with a deploy hook, as the case below shows.

A five-state model for the lifecycle

Name the states an attempt can be in, rather than collapsing everything into "done" or "not done." The right-hand column gives the deploy equivalent.

StateMeaning for an experimentWho can leave this stateDeploy equivalent
eligibleConfiguration ready, no verified result yetThe scheduler, by submittingThe date has arrived and no marker exists
requestedJob submitted, ID returnedThe cluster, by starting the jobDeploy hook returned 200
runningThe scheduler reports the identified job startedThe cluster, by finishing or failingThe identified build started
verifiedResults file checked against the expected checkpoint and row countTerminal for this attempt; the result can still be rerunRoute and discovery entry passed a check at a recorded time
failedThe job failed, was preempted, or produced an invalid resultThe scheduler, by retryingThe build errored or the live check did not pass

Written this way, the mistake is visible: a marker written on the job ID claims "verified" when it has only confirmed "requested." A run that crashed halfway looks, from the marker's point of view, identical to one that finished.

Where an acknowledgment-only marker skips the check that matters. Scheduler fires; Submit job; Job ID returned; Marker: requested; Read results file; Marker: verified
Where an acknowledgment-only marker skips the check that matters

The arrow from "Marker: requested" to "Read results file" is the step that gets skipped.

Checking for the result

The check reads the artifact and marks the run complete only if it is the right one. "The right one" must be specific enough that a stale checkpoint or a half-written file cannot slip through. Below is a small, self-contained Python example that models the check as a decision function. It is a teaching simulation with no file system access, and every input is a labeled hypothetical.

python
from dataclasses import dataclass

@dataclass
class Observed:
    file_exists: bool
    checkpoint: str | None
    rows: int

def confirm_result(expected_checkpoint: str, expected_rows: int, seen: Observed) -> str:
    """Return the run's state given one look at its results file.

    This models a single check. In practice you would retry a read that
    failed on a network or storage error before concluding 'failed'.
    """
    if not seen.file_exists:
        return "failed"
    if seen.checkpoint != expected_checkpoint:
        return "failed"
    if seen.rows != expected_rows:
        return "failed"
    return "verified"

# Hypothetical case: the scheduler said "done", but the run evaluated an old checkpoint.
stale = Observed(file_exists=True, checkpoint="ckpt-041", rows=500)
print(confirm_result("ckpt-042", 500, stale))  # -> "failed"

# Hypothetical case: right checkpoint, but the harness crashed partway and the half-file parses.
partial = Observed(file_exists=True, checkpoint="ckpt-042", rows=212)
print(confirm_result("ckpt-042", 500, partial))  # -> "failed"

# Hypothetical case: everything matches.
complete = Observed(file_exists=True, checkpoint="ckpt-042", rows=500)
print(confirm_result("ckpt-042", 500, complete))  # -> "verified"

Run on Python 3.12, it prints failed, failed, verified. The first two cases would have passed an acknowledgment-only check, because the job ID comes back before either problem can be observed. The check asks a different question: not "did the scheduler accept my request" but "does the result I wanted exist where the next step will read it." The inputs are hardcoded, so a passing run proves only that the branching logic is right. It proves nothing about a real scheduler, a real file system or two overlapping submissions.

A local test proving the logic is not a production guarantee

Running confirm_result against hand-built Observed values shows the branching works for those inputs. It says nothing about storage that is eventually consistent, concurrent submissions or timeouts. Treat it as a design check, and exercise the real scheduler and a real read in a staging setup before trusting it.

Retrying without duplicating

The two failure directions are worth putting side by side. A scheduler with no marker resubmits finished work on every pass. A scheduler that marks on acknowledgment silently strands a run that failed. The fix sits between them: mark only on a verified result, and leave everything else eligible for retry.

Retries have a cost you can compute. Chapter 12 of the book derives it: if each attempt loses the request with probability a, and loses the reply after the effect happened with probability b, then retrying blindly until a reply arrives creates on average

text
extra effects per task = b / (1 − a − b)
a = 0.1, b = 0.2  ->  0.2 / 0.7 = 0.286

Only b creates duplicates. A retry after a request that never arrived is harmless; a retry after a lost reply repeats the effect. That is why a job resubmitted after a timeout can leave two runs writing to the same output folder, and why the book gives each intended effect a stable key.

Record acceptance separately from completion

Store the job ID as "requested," not "done." Nothing readable by other systems yet claims success.

Read the result after the job reaches a final state

Wait for the scheduler to report the job finished, then read the results file through the same path the next step (a dashboard, a report, a paper table) will use.

Compare against the expected checkpoint and row count

Check that the file exists, that the checkpoint it records is the one you meant, and that it has one row per case.

Mark verified only when all three checks pass

If the checks do not pass by a deadline, keep the failed attempt and its job ID. Reconcile a job in an unknown state before submitting another, give each submission a stable key, and retry under a bounded policy.

Case: the deploy hook behind this site

What a 200 from a deploy hook taught this site

This site's historical scheduled publication workflow, at site@a901a675a78652db6e613b9f6be223c7d082d221:.github/workflows/scheduled-publication.yml, ran hourly, decided whether a London calendar date was eligible, and called a Vercel deploy hook. It wrote its success marker (an actions/cache entry keyed on the date) as soon as the hook answered without an error status, with no check of the deployed site. This is an observation of what the file does, not a report of an outage. Moving the marker after a live check would stop it stranding failed dates, but a cache marker is still not exactly-once: eviction, overlapping runs and a lost reply can each cause a duplicate request.

A 200 from a deploy hook means the platform queued a build. It does not show that the build finished, that it contains the intended revision, that it was promoted to production, that the article route returns the article, or that the article appears in the site's index. GitHub's documentation on the schedule event (retrieved 2026-09-10) adds that scheduled runs may be delayed under load and some queued jobs dropped.

The site no longer rebuilds to publish. Approved future articles ship in the server bundle and every request checks one shared publication clock; the companion essay on cutoffs and clocks covers how a date boundary is defined. The check still has to observe the result: an error page can return HTTP 200, so it compares article identity and body content, not only the status code.

Build-based publication observes the build, promotion and public content; a request-time gate crosses the date boundary on an unchanged deployment. Both end by checking route, revision, media and discovery.. Build-based publication; Observe build, promotion and public content; Check route, revision, media and discovery; Request-time publication gate; Cross date boundary on unchanged deployment
Build-based publication observes the build, promotion and public content; a request-time gate crosses the date boundary on an unchanged deployment. Both end by checking route, revision, media and discovery.

Testing the states that matter

Three scenarios expose whether a pipeline tells "requested" apart from "verified." First, a missed run: the scheduler is down for a day, so an eligible job is now overdue. It should still run and be verified, not be skipped. Second, duplicate submissions: two scheduler passes overlap, both see no marker, and both submit. A concurrency guard on the scheduler (the site's workflow used cancel-in-progress: false, grouped by scheduled-publication-deploy) covers one queue, not the system doing the work. Third, the case this essay centers on: the job is accepted and the result is bad. An acknowledgment-only pipeline marks it done and never retries. A pipeline that reads the result sees the failure and tries again.

These scenarios are an experiment design, not a result. This site has not run this yet: they need a controlled rehearsal, for example with the workflow_dispatch input the historical file already had (now_utc, for simulating a UTC time), and until someone runs one and records its output, they remain planned tests.

Quick check — The scheduler reports that your evaluation job finished, but the results file has fewer rows than the case file. Should the pipeline mark the run verified?

Check the decision

Why this matters in a lab

Most of the infrastructure an applied-AI or research engineer touches is asynchronous: job queues, batch inference, evaluation harnesses, deploys of a serving stack. Each hands back an acknowledgment long before the effect you care about exists. Name the observed result that means "done," check for it, and mark nothing complete on an acknowledgment. The research engineering course treats this as part of running experiments at scale, and it is daily work for an ML infrastructure engineer.

An interviewer could fairly ask: your nightly evaluation job reported success, but the dashboard shows yesterday's numbers. Where do you look? A good answer walks the states in the table above: was the job accepted, did it run, did it write a results file, is it the file the dashboard reads, and does that file carry the checkpoint you expected.

What would prove this analysis wrong

If your scheduler or hosting provider documents that its submit call blocks until the work is complete and reports the final status of the artifact itself, the gap between acknowledgment and result would not apply to that call. Verify that contract in the provider's documentation rather than assuming it from this essay. For the site case, the argument rests on reading the historical workflow: the marker step ran on hook success, with no live fetch in between. A later version that adds a verification step before the marker closes that specific gap.

Read Chapter 12: a missing reply cannot say what happened

The book derives what retries cost, why only a lost reply creates duplicates, and how a stable operation key removes them.

Learn the mechanism. Test the claim.

Join the Prof Rod newsletter for one educational lesson a week, with worked examples attached. It is free to register for and separate from the Zero Employee community.