Retrieve Actions logs on private repos (token-accepting path or cookie/CSRF auth) #103
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Make
fj run view <n> --log/--log-failedactually retrieve step logs on private repos. Today the only working log path is the Forgejo web frontend route under/{owner}/{repo}/actions/runs/{run}/jobs/{job}/attempt/{attempt}(see the module header insrc/api/workflow_view.rs), which authenticates with a session cookie + CSRF token, not an API token. On a private repo that route 404s under token auth, so logs are unreadable throughfj.Find a path that works under the credentials
fjactually holds (an API token in the OS keychain). Options, roughly in order of preference:/api/v1Actions log endpoint, if the target Forgejo version exposes one. rasterstate/fj#91 reports that on the observed Forgejo (11.0.x / gitea-1.22 era)/api/v1/.../actions/runs/{n}/jobs,/api/v1/.../actions/jobs/{id}/logs, and/api/v1/.../actions/runs/{n}/logsall 404, so this may simply not exist there. Re-verify per version before building on it.Client::request/is_trusted_urlcasually; design it deliberately./actions/tasksfor pass/fail, web UI for logs).Why
fj run view --logis the primary way to read CI output without leaving the terminal, and most of our repos are private (rasterstate/fjord-ios, rasterstate/flux, ...). rasterstate/fj#91 and its duplicate rasterstate/fj#92 both hit this during real CI triage and had to fall back to the/actions/tasksAPI for pass/fail and to server-side sqlite (action_task_step.log_length) for ground truth. The error-message fix in rasterstate/fj#91 stops the misdiagnosis, but it does not restore the feature: private-repo logs still cannot be read throughfj.Acceptance
fj run view <n> --logand--log-failedprint real step logs for a private repo run that has logs, against a Forgejo version where a supported path exists.Client::requestand respectsis_trusted_url(no second, unguarded HTTP/credential path).cargo fmt --check,cargo clippy --all-targets --all-features -- -D warnings,cargo test --allpass.Dependencies
Out of scope
Size
L (uncertain; may reduce to "document the limitation" if no auth path exists)
Product-validation + feasibility: private-repo Actions log retrieval
Posting this as the operator-facing decision record for this parked item. Two parts: (1) is the pain worth the work, and (2) if so, by which path. TL;DR up front: the acute pain is already cured by rasterstate/fj#91, the remaining pain is a recurring papercut, and the only client-side path (cookie/CSRF) costs far more than the papercut. Pursue a server-side/gateway endpoint and an upstream request instead; keep this parked behind the shipped documented-limitation.
1. Product validation
Who needs it, and how often. This is a real, recurring need, not a hypothetical.
fj run watch/fj run view --logis the headline reason to usefjover the web UI at all: read CI output without leaving the terminal. Most of our repos are private (rasterstate/fjord-ios, rasterstate/flux, and the rest), so "private-repo logs" is not an edge case, it is the common case for anyone triaging CI here. Frequency tracks how often private-repo CI goes red and someone wants the log in-terminal, which for active repos is daily-ish. So on raw frequency this scores high.The cost of today's fallback. After rasterstate/fj#91 shipped, the fallback is three layers, and they are better than they sound:
no logs for that run/job/attempt, which reads as "the job died before producing logs." rasterstate/fj#92 documents that exact error sending a triage on a multi-step detour through runner provisioning and autoscaler limits for what was an ordinary step failure whose logs existed the whole time. rasterstate/fj#91 replaced that with a message that names the real cause (web log route rejects token auth; private-repo logs need session auth) and points here. That converts a wrong answer into a correct "go look in the web UI" signpost. The expensive part of the pain (chasing a nonexistent cause) is already gone./actions/taskslist. Still readable under the PAT, sofj run listand pass/fail status work in-terminal on private repos. You lose the log body, not run state.Does the pain justify the work? Honest read: the remaining pain is a recurring papercut, not a fire, and it does not justify an expensive fix. Two of our own issues (rasterstate/fj#91, rasterstate/fj#92) were filed from real CI triage that this blocked, which is genuine evidence the feature matters, but both predate the rasterstate/fj#91 error fix. The thing that actually hurt (misdiagnosis) is cured. What's left is "on a private repo,
--logcan't print the body, click through to the web UI instead," correctly signposted. That justifies a cheap fix (an upstream ask, or a server-side endpoint we get nearly for free) and the already-shipped documentation. It does not justify forking the auth model to chase it. The work is only worth doing if the path is cheap.2. Feasibility
Technical baseline (re-confirmed against the code).
src/api/workflow_view.rsis explicit that/api/v1exposes no Actions log surface: single-run, per-run/jobs,/jobs/{id}/logs, and/runs/{id}/logsall 404. Both the run summary and the logs live only behind the human web routes under/{owner}/{repo}/actions/.... On a public repo those routes answer a token-bearing request; on a private repo they 404 because they want a session cookie + CSRF token, not a PAT. So the only client-side path to private logs is to authenticate as the browser does.What browser-session auth concretely requires. This is a second, heavier auth system, not a tweak:
fj auth logintoday prompts for a PAT and stores one opaque string per host (src/auth/mod.rs). Cookie auth means prompting for a password, a regression in credential hygiene (PATs are scoped and revocable; a password is not)._csrftoken from a form/meta tag or cookie and replay it on every state-changing web request. Brittle, tied to the HTML, unversioned.reqwestis currently used statelessly through oneClient::request; we'd add cookie-store handling and a parallel notion of "logged-in session" alongside the token.src/auth/mod.rsdeliberately stores only tokens, never to disk).Security + maintenance burden. High and permanent. We'd be storing a second class of credential, prompting for passwords, parsing CSRF out of HTML, and depending on unversioned web routes plus a login form that upstream can restyle or re-auth at any release, with no API contract. The module header already flags these routes as "stable in practice but can shift between Forgejo releases." Every one of those shifts becomes a
fj-breaks-on-upgrade incident. For a papercut-sized payoff, this is a bad trade.Alternatives, ranked.
/api/v1/.../actions/jobs/{id}/logs(or equivalent). If it lands,fjdeletes the whole web-route hack and private logs become an ordinary typed/api/v1wrapper, the cleanest possible end state. Cost to us is a writeup; risk is timeline (not ours to control). Low effort, high upside, no downside, so it's worth filing regardless of what else we do.src/fjord/mod.rsshows Fjord-Account traffic routing through fjord-platform's/api/v1/forge-gateway/:instance-id/api/v1/..., and the platform already performs a credentialedsign-in(it even handles a 2FA recovery code). Crucially,workflow_view.rsnotes the gateway only proxies/api/v1, so it doesn't forward the web log routes today, which is exactly the gap. The server side can hold the session (or readaction_task_step.log_length/ the log blobs directly, the same ground truth rasterstate/fj#91 and rasterstate/fj#92 had to reach via sqlite) and expose a clean token-or-bearer log endpoint thatfjcalls like any other gateway route. This keeps all the session/CSRF/2FA/expiry burden server-side where it's already solved, instead of shipping it to every client. Effort is real but bounded, and it only helps Fjord-Account users (PAT-direct users still need option 1 or the web UI). This is where to invest if the papercut proves worth investing in./actions/tasksstill gives in-terminal pass/fail. Add a troubleshooting note ("private-repo log bodies aren't retrievable via PAT; use the web UI or a Fjord-Account instance") and this is a defensible resting state indefinitely.Recommendation
Do not build client-side cookie/CSRF auth. It forks the auth model (passwords, cookie jar, CSRF scraping, 2FA, expiry, second keychain secret) against unversioned web routes, a permanent maintenance and security tax, to fix a papercut that rasterstate/fj#91 already de-fanged.
Instead:
No production code written and no labels changed here; leaving the parked/backlog state for you to set.
Contract pinned: no token-accepting Actions-log endpoint on Forgejo 15.0.3
This issue was parked on: "blocked on confirming whether the target Forgejo version exposes ANY token-accepting Actions-log endpoint... Do not start until that contract is pinned down for a concrete Forgejo version." Pinning it now.
The prior negative observation (#91, #92) was made on Forgejo 11.0.x / gitea-1.22 era. The live instance has since moved to 15.0.3+gitea-1.22.0 (
GET /api/v1/version), so that result could not simply be carried forward across four majors of drift. It had to be re-probed.Method: read the running server's own OpenAPI spec, which is authoritative for what the binary actually serves:
Every Actions path the live server declares:
Findings
No log endpoint exists. There is no
/actions/jobs/{job_id}/logs, no/actions/runs/{run_id}/logs, and no/actions/runs/{run_id}/jobs. Option (1) in this issue is confirmed dead on 15.0.3 -- not "may not exist", but does not exist.No rerun/cancel endpoint either. Filtering the whole spec for
rerun|retry|cancel|approvereturns onlyDispatchWorkflow, which fires a freshworkflow_dispatch(not a rerun of an existing run, and only for workflows that opt into that trigger). Relevant torasterstate/fleet#178, whose acceptance includes rerunning a failed run.New since 11.0.x:
ActionRunandListActionRunsare now typed API endpoints. They give run-level metadata/status under token auth, but no step log content.Consequence
This issue resolves to option (2) (cookie/CSRF session auth against the web log route) or option (3) (document the limitation). It does not reduce to a typed API wrapper.
The "Size: L (uncertain)" uncertainty is now removed: the uncertainty was entirely about whether (1) existed, and it does not. The blocker in Dependencies is cleared and this is ready to start.
Recommend proceeding with (2), scoped exactly as the acceptance criteria already require: the session path must funnel through
Client::requestand respectis_trusted_url, rather than opening a second unguarded HTTP/credential path.This conclusion is wrong, and it has been costing us CI diagnosis for weeks. An API token does read Actions logs. There are two working endpoints; fj is using neither.
Measured today on rasterhub.com (Forgejo 16.0.2, fj 0.4.1), with an ordinary API token and no browser session:
The zip case, on a real run:
.MISSINGentries carry the bodyjob has not been executed yet, so the format even distinguishes "no log" from "never ran", which is exactly the distinction we have been unable to make from the outside.What fj does instead
fj run view --log-failedandfj run rerunboth fail with the message this issue records:The premise is accurate about the web route and false as a general claim.
fj run rerunis worse off thanfj run view, because it reads logs as a precondition for an operation that does not need them, so a working rerun is blocked by a broken log path.Why this matters beyond convenience
fleet#267 has eight occurrences of paragon CI jobs dying mid-compile, and every investigation round has ended at "the job output is not retrievable through the API path". That was never true. The most recent round spent a full lane session concluding it from this issue's premise, and the negative result it produced is sound but was reached the long way.
Suggested fix
fj run log/fj run view --log*use/actions/runs/{id}/logs(zip, all jobs) or/actions/jobs/{id}/logs(single job) with token auth. Both accept the same token fj already holds.fj run rerunstops reading logs entirely. It does not need them.POST /actions/runs/{id}/rerunand/actions/jobs/{id}/rerunboth 404, so rerun may need to stay a web-route operation. That is a separate question from log access and should not be conflated again.