feat(ci-logs): runner-side log mirror for agent fix-ci dispatch #1103
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks#1103
Loading…
Add table
Add a link
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?
User story
As a code-lead or dev agent dispatched to fix a red CI run, I want the last ~200 lines of every failed step embedded in my task prompt, so that I can diagnose the failure without trying to fetch logs from a Forgejo API that does not exist — eliminating the "blocked on log access" stall that re-loops the fix-ci dispatch indefinitely.
Background
Forgejo 15 does not expose any REST endpoint to download workflow-run logs or artifacts. Confirmed by hitting
https://forge.jacquin.app/swagger.v1.json(2026-05-11):runs,runs/{run_id},runners,runners/jobs,secrets,variables,workflows/{name}/dispatches,tasks.…/logs, no…/artifacts/download, no…/jobs/{job_id}/logs.dbfssession-cookie route that returnsdbfs open "actions_log/": file does not existto API token callers.Symptom in our loop: PR #1091 / #1080 cycle. Code-lead dispatched, can't read CI logs, comments "blocked on log access", task ends. Next CI failure re-dispatches with same blocker. The fix exists in user-space (claude-hooks owns the runner host) — short-circuit the missing API by mirroring log tails from inside the workflow itself.
Acceptance criteria
Endpoint (claude-hooks server)
POST /internal/ci-logaccepts{ repo, run_id, job_id, step_name, head_sha, conclusion, log_tail }JSON body.repois<owner>/<name>form, validated against the watched-repos table; reject 404 if unknown.log_tailis plain text, ≤64 KiB. Server truncates to last 200 lines or 32 KiB (whichever is smaller) for storage — workflow side is best-effort, server is the cap./internal/*(already gated by the internal-only auth band — no public surface).Auth
CLAUDE_HOOKS_INTERNAL_TOKENenv var (same band as existing/internal/*routes). Workflow reads it from a repo-level secret (CLAUDE_HOOKS_INTERNAL_TOKEN) and sends it viaAuthorization: Bearer ….127.0.0.1already enforced by the systemd unit — no change here, just document).Storage
ci_logs(Drizzle migration):id INTEGER PK,repo TEXT,head_sha TEXT,run_id TEXT,job_id TEXT,step_name TEXT,conclusion TEXT,log_tail TEXT,created_at INTEGER(epoch ms).(repo, head_sha)— fast lookup atdispatch_fix_citime.created_at— used by retention sweeper.Workflow integration (forge-base/qa-bun.yml)
if: failure()toqa-bun.yml(and any sibling reusable workflows we own) that:${{ steps.<id>.outcome }}; iterate over the known step IDs:setup,typecheck,lint,fmt-check,test)./var/run/act/...(path documented in forgejo-runner config) to ~200 lines.${{ secrets.CLAUDE_HOOKS_LOG_URL }}with the bearer token from${{ secrets.CLAUDE_HOOKS_INTERNAL_TOKEN }}.continue-on-error: true.v0.2.3).dispatch_fix_ci integration
domain/workflow/post-ci.ts::dispatchFixCireadsci_logsrows for(repo, head_sha)before constructing the task prompt.If at least one row exists, append a fenced-code block to the task prompt:
If zero rows exist (older runs, mirror endpoint down, workflow without the step), fall back to current behaviour — task prompt without log context. The op must remain functional with no logs.
No behavioural change to
dispatch_fix_ci's dedup map — keyed by head SHA as today.Retention
janitor.ts): deleteci_logsrows older than 14 days. Cheap; runs every 10 min.Tests
dispatchFixCi— fresh-log path injects the fenced block; zero-log path falls through.ci_logswithin 10s of step failure.Out of scope
if: failure(). Successful runs need no fix-ci dispatch.ci_logs. Stored data is for agents, not operators; operators read logs via the existing Forgejo web UI.References
storage.actions_log): https://forgejo.org/docs/latest/admin/actions/docs/architect.md/docs/api.md—/internal/*endpoint band reference.docs/database.md— Drizzle migration runner conventions.forge-baserepo — home ofqa-bun.ymlreusable workflow that ships the new step.