Features¶
Three object layers, one tool each¶
There is exactly one path from a failing run to the failing step:
get_run(run_id) → the run and its job roster
get_job(job_id) → one job with every step (status, conclusion, has_log)
get_job_log(job_id, step=N) → that step's text
get_job_log(job_id) → the failed steps of a failed job, or the merged document
get_job_log(job_id, whole_job=true) → the merged document, explicitly
get_run_failed_logs(run_id) → the failed steps of every failed job, grouped by text
- Steps belong to
get_jobonly:get_runhas noinclude_stepsand no failure block. failed_steps_only=trueis a batch convenience, not a second way to decide which step failed.- A failed job read without a scope starts at its failed steps (
scoped_to_failed: true); the scope comes from the step conclusions, not from the text. - A green job is not pushed to read logs —
has_logis still honestlytrue.
Two log sources¶
| Source | Content | Availability |
|---|---|---|
GET /actions/jobs/{id}/logs |
a job's merged text | any time within 90 days, stored in ghat_job_logs |
GET /actions/runs/{id}/logs |
zip: merged file + <job>/<step no>_<name>.txt |
GitHub serves the split for about an hour after the run finishes |
The hour is a fetching deadline, not a retention limit. Text fetched into
ghat_step_logs is ordinary data, readable forever.
Naming a workflow: path, file name or id¶
A display name is not a reference. name: lives in the workflow file, so the same
file can carry a different name on every branch. Use the path or the id.
Failure¶
Only FAILED_CONCLUSIONS count. conclusion = null is GitHub still materializing
metadata, not a failure.
Webhooks lower latency; they are not the truth¶
A delivery can be lost and a run is often created outside this service, so every run-level read asks GitHub again. When GitHub cannot answer, the service reports that — it never degrades to a cached answer.