DEV Community

Daniel Neata
Daniel Neata

Posted on

GitHub Actions: A Successful Job Is Not Publication Evidence

A scheduled publishing job can finish successfully without creating a public post. That is not necessarily a defect. The job might have selected the other daylight-saving trigger, found no reviewed content, performed a dry run, or recognised a publication that already exists.

The mistake is treating every successful workflow conclusion as the same business outcome. A useful publisher records what happened to the publication attempt, then keeps that evidence separate from the health of the surrounding job.

Separate execution health from delivery

A workflow conclusion answers whether its steps met the runner's success conditions. It does not establish that a provider accepted a post, made it public, or returned the destination you expected.

For example, a guard can deliberately return zero after deciding that a run is outside its publishing window. Reporting that as a successful publication invents an event. Reporting it as a failed publication also misrepresents the system: there may have been no expected publication for that trigger.

Use explicit outcome names such as skipped_outside_slot, no_eligible_content, dry_run_passed, submitted_unverified and published_verified. Attach a reason and observation time rather than relying on the label alone.

Make the publishing step identifiable

Give the actual publishing step an identifier. Preserve its outcome separately from validation, checkout and artifact upload. A successful setup step is useful operational evidence, but it is several stages removed from a public post.

The same applies to dry runs. A dry run can verify account ownership, content selection and media availability. It should say that no publication was attempted. If it returns the next eligible item, record its identity so that someone reviewing the run can see what was checked.

Do not make a live post simply to learn whether an unfamiliar field is required. Read the provider's documentation and validate the prepared content first. Reserve a live publication for the actual authorised item and destination.

Model uncertainty before making the write

Suppose a provider creates a post, but the connection closes before your process receives the response. A timeout alone cannot distinguish that case from a request that never reached the provider.

Persist a pending record before the request. Include a stable local identity, destination, intended source and claim time. Where supported, send the provider's idempotency key. Otherwise, reconcile the pending record against provider history before considering another write.

An unresolved record is a reason to stop that publisher, not a reason to try several alternate routes. Two routes can each appear helpful while creating the same post twice. Continuing unaffected destinations is different from retrying the uncertain destination.

A pending record also needs durable storage. An in-memory flag disappears when the runner exits. If you use a repository file, guard updates with the current file revision and inspect conflicts before retrying. Workflow concurrency and durable state solve related but different problems.

Keep the evidence small and concrete

A useful record might look like this:

{
  "slot": "2026-10-24/08:15",
  "outcome": "submitted_unverified",
  "destination": "example-account",
  "providerId": "831204016708108288",
  "publicUrl": null,
  "observedAt": "2026-10-24T07:15:12Z"
}
Enter fullscreen mode Exit fullscreen mode

This example deliberately leaves the public URL empty. A provider identifier is valuable, but the evidence does not yet support calling the item publicly available. After read-back, append the verified URL and the time of that observation.

Never put access tokens, refresh tokens or authentication headers in these records. Diagnostic convenience is not a reason to copy credentials into artifacts or source control.

Test the decisions that change reporting

Test daylight-saving boundaries, future slots, a previously recorded slot, an empty reviewed queue and an uncertain provider response. Also test the case where a write succeeded but storing the final receipt failed: reconciliation should recover the existing post rather than create another.

A useful daily report states the expected interval, the number of confirmed deliveries and the number still unknown. It does not turn every green run into a delivery, or every deliberate skip into a missed post.

GitHub's workflow syntax reference describes the execution primitives. Your application still needs to define what successful delivery means and which evidence is sufficient to claim it.

Top comments (0)