Read GitHub job logs from the raw REST endpoint; decode JSON with luanil #786

Merged
barrettruth merged 1 commit from feat/github-raw-job-logs-and-luanil into main 2026-06-08 21:49:21 +00:00
Owner

Follow-up to #784 (exploring guh.nvim's CI-log approach). Implements the two items worth borrowing; declines the rest.

1. GitHub single-job logs via the raw REST endpoint

check_log_cmd now fetches gh api repos/{owner}/{repo}/actions/jobs/{id}/logs when a job id is known, instead of gh run view --job <id> --log. The raw endpoint omits gh's {workflow} / {job} {step} per-line prefix.

  • New capability ci_job_log_raw (GitHub only). The whole-RUN path keeps gh run view --log, because the whole-run raw endpoint returns a ZIP rather than text.
  • parse_github gains a column-less single_job mode: it synthesizes the job header from the check/job name and derives structure from ##[group] markers + the steps JSON, instead of the now-absent JOB\tSTEP columns. The tab-format (run-level) path is unchanged.
  • columnless + job_name are threaded through log/init.lua, ci/actions.lua, and pr/checks.lua, gated on the capability and a present job id.
  • The raw fetch buffers to a tempfile under set -eu (not gh ... | tail) so a non-zero gh exit (404 / expired logs) surfaces as an error instead of rendering gh's error JSON as log lines.

Deliberate behavior change (failed checks)

The raw endpoint has no server-side step filter, so opening a failed check now shows the whole job log instead of only the failed steps. Measured on a real failed job (run 25129091893, job Format):

  • old gh run view --job --log-failed: 262 lines
  • new raw gh api .../jobs/{id}/logs: 828 lines (== --log)

Accepted intentionally: in the structured/folded view the ##[error] line is still highlighted, the failed step header renders red (from the steps JSON conclusion), and the cursor lands at the bottom near the failure. We trade "failed steps only" for uniform, prefix-free logs + full context.

2. luanil JSON-decode hardening

All 25 vim.json.decode sites now pass { luanil = { object = true } }, so JSON null object fields decode to nil instead of truthy vim.NIL. This is the exact footgun that crashed guh.nvim's CI-job sort (a.conclusion or a.status kept vim.NIL). object-only is used deliberately to avoid any sparse-array length hazard.

Test plan

  • busted suite green (pre-existing tree-sitter-yaml env failures aside)
  • selene 0/0, stylua clean, lua-language-server clean
  • Added 7 tests: column-less parser (header synthesis, BOM, step-metadata durations, no-name, embedded tabs) + check_log_cmd raw vs run-view fallback
  • Verified empirically: raw 404 -> exit 1 + empty stdout (clean error); valid job -> logs; failed-job line counts above
Follow-up to #784 (exploring guh.nvim's CI-log approach). Implements the two items worth borrowing; declines the rest. ## 1. GitHub single-job logs via the raw REST endpoint `check_log_cmd` now fetches `gh api repos/{owner}/{repo}/actions/jobs/{id}/logs` when a job id is known, instead of `gh run view --job <id> --log`. The raw endpoint omits gh's `{workflow} / {job} {step}` per-line prefix. - New capability `ci_job_log_raw` (GitHub only). The whole-RUN path keeps `gh run view --log`, because the whole-run raw endpoint returns a ZIP rather than text. - `parse_github` gains a column-less `single_job` mode: it synthesizes the job header from the check/job name and derives structure from `##[group]` markers + the steps JSON, instead of the now-absent `JOB\tSTEP` columns. The tab-format (run-level) path is unchanged. - `columnless` + `job_name` are threaded through `log/init.lua`, `ci/actions.lua`, and `pr/checks.lua`, gated on the capability and a present job id. - The raw fetch buffers to a tempfile under `set -eu` (not `gh ... | tail`) so a non-zero gh exit (404 / expired logs) surfaces as an error instead of rendering gh's error JSON as log lines. ### Deliberate behavior change (failed checks) The raw endpoint has no server-side step filter, so opening a **failed** check now shows the whole job log instead of only the failed steps. Measured on a real failed job (run 25129091893, job Format): - old `gh run view --job --log-failed`: 262 lines - new raw `gh api .../jobs/{id}/logs`: 828 lines (== `--log`) Accepted intentionally: in the structured/folded view the `##[error]` line is still highlighted, the failed step header renders red (from the steps JSON conclusion), and the cursor lands at the bottom near the failure. We trade "failed steps only" for uniform, prefix-free logs + full context. ## 2. luanil JSON-decode hardening All 25 `vim.json.decode` sites now pass `{ luanil = { object = true } }`, so JSON `null` object fields decode to `nil` instead of truthy `vim.NIL`. This is the exact footgun that crashed guh.nvim's CI-job sort (`a.conclusion or a.status` kept `vim.NIL`). `object`-only is used deliberately to avoid any sparse-array length hazard. #### Test plan - [x] `busted` suite green (pre-existing tree-sitter-yaml env failures aside) - [x] selene 0/0, stylua clean, lua-language-server clean - [x] Added 7 tests: column-less parser (header synthesis, BOM, step-metadata durations, no-name, embedded tabs) + `check_log_cmd` raw vs run-view fallback - [x] Verified empirically: raw 404 -> exit 1 + empty stdout (clean error); valid job -> logs; failed-job line counts above
Read GitHub job logs from the raw REST endpoint; decode JSON with luanil
Some checks failed
quality / Format (pull_request) Has been cancelled
quality / Lint (pull_request) Has been cancelled
quality / Test (pull_request) Has been cancelled
a81d923ce1
Switch GitHub single-job CI log fetching to the raw
`gh api repos/{owner}/{repo}/actions/jobs/{id}/logs` endpoint, which omits
gh's "{workflow} / {job} {step}" per-line prefix. parse_github gains a
column-less mode that synthesizes the job header (from the check/job name)
and derives structure from ##[group] markers plus the steps JSON; the
run-level path keeps `gh run view --log` since the whole-run endpoint is a
ZIP. The raw fetch buffers to a tempfile under `set -eu` so a non-zero gh
exit (404/expired logs) surfaces as an error instead of rendering gh's
error JSON as log lines.

Decode every vim.json.decode call with `{ luanil = { object = true } }`
so JSON null object fields become nil instead of truthy vim.NIL, which
otherwise survives `x or default` and crashes later sorts/compares.
barrettruth deleted branch feat/github-raw-job-logs-and-luanil 2026-06-08 21:49:21 +00:00
Sign in to join this conversation.
No description provided.