swactor-development-history/ci/CI_OUTPUT_IN_FORGEJO.md

140 lines
5 KiB
Markdown
Raw Normal View History

2026-02-23 05:07:59 +00:00
# CI Output Visible in Forgejo — Development History
> Added two mechanisms so CI results are visible directly in the Forgejo web
> UI without SSH-ing into the runner: **enhanced commit status descriptions**
> and **PR comments** with full job output.
>
> *Branch: `spot-instance`*
---
## Problem
The CI pipeline worked end-to-end but job output was only visible in the
runner's stderr log on the Thinkpad. To see why clippy failed, you had to
`ssh thinkpad 'tail ~/local-runner.log'`. Forgejo's commit status descriptions
just said "Job 'clippy' completed" with no output.
Forgejo lacks GitHub's Checks API (no annotations, no log viewer), so we use
two complementary approaches.
## What Was Done
### 1. Enhanced Commit Status Descriptions
On job completion, the status description now includes:
- **On success**: `"Job 'check' passed"`
- **On failure**: `"Job 'clippy' failed: command exited with code 101\n[stderr] error: you should consider..."` — last ~10 lines of output, capped at 250 characters.
This is visible directly on the PR page and commit page in Forgejo without
clicking anything.
### 2. PR Comments with Full Output
When a pipeline reaches terminal state, the StatusReporter:
1. Queries `GET /repos/{owner}/{repo}/pulls?state=open` to find the PR for the branch
2. Builds a markdown comment with `<details>` sections per job (up to 100 lines each)
3. Posts it via `POST /repos/{owner}/{repo}/issues/{pr_number}/comments`
4. Re-posts the pipeline commit status with `target_url` pointing to the comment
Example comment format:
```markdown
## Pipeline `ci` — failure
Commit: `5b7ae7b`
<details>
<summary>clippy — failed: command exited with code 101</summary>
_Showing last 100 of 523 lines_
\```
error[E0599]: ...
\```
</details>
<details>
<summary>check — passed</summary>
\```
$ cargo check --workspace
Compiling ...
\```
</details>
```
### 3. `target_url` on Commit Statuses
Added `target_url: Option<String>` to `StatusUpdate`. When a PR comment is
successfully posted, the pipeline's commit status badge links directly to that
comment. Clicking the status badge on the PR page jumps to the output.
## Files Changed
| File | Change |
|------|--------|
| `crates/ci/src/lib.rs` | Added `target_url: Option<String>` to `StatusUpdate` |
| `crates/ci/src/status_reporter.rs` | Added `JobOutput`, `PostPipelineComment`, `find_pr_for_branch()`, `post_pr_comment()`, `build_pipeline_comment()`, `handle_pipeline_comment()` |
| `crates/ci/src/local_coordinator.rs` | Enhanced `handle_job_complete()` descriptions; added `emit_pipeline_comment()`, called from `try_schedule_next()` |
| `crates/ci/src/coordinator.rs` | Mechanical `target_url: None` at 4 sites |
| `crates/simulation/src/ci/local_sim.rs` | Mechanical `target_url: None` at 3 sites |
| `crates/simulation/src/ci/sim.rs` | Mechanical `target_url: None` at 2 sites |
| `crates/ci/Cargo.toml` | Added `features = ["json"]` to `ureq` for `into_json()` |
## Deployment & Verification
Built and deployed updated `local-runner` to the Thinkpad, pushed to the
`spot-instance` branch (which has PR #42 open), and observed:
**Working:**
- Commit statuses show descriptive output. The clippy failure status reads:
`Job 'clippy' failed: command exited with code 101` followed by the tail of
the clippy output, truncated at 250 chars.
- Passed jobs show `"Job 'check' passed"` / `"Job 'test' passed"`.
- Pipeline-level status correctly reports `ci/ci → failure`.
**Blocked on token scope:**
- PR comment posting returned HTTP 403. The Forgejo API token has
`write:repository` scope (sufficient for commit statuses) but needs
`write:issue` scope to post comments on PRs/issues.
- The code degrades gracefully: logs the error, skips the comment, posts the
pipeline status without `target_url`.
## TODO
- [ ] Regenerate Forgejo API token with `write:issue` scope to enable PR comments
- [ ] After token update, re-deploy and verify the comment + `target_url` flow end-to-end
## Edge Cases Handled
| Case | Behavior |
|------|----------|
| No open PR for branch | Comment silently skipped, status posted without `target_url` |
| API failures (403, network) | Logged via `eprintln!`, degrades gracefully |
| Long output | Capped at last 100 lines per job in PR comment, with `_Showing last N of M lines_` note |
| Long description | Capped at 250 chars for commit status description field |
| All HTTP code | Gated behind `#[cfg(feature = "local")]` — simulation builds unaffected |
## Architecture Note
All new HTTP calls (PR listing, comment posting) happen in the StatusReporter
actor, which is fire-and-forget. The LocalCoordinator never blocks on HTTP.
The flow is:
```
LocalCoordinator StatusReporter
| |
|-- emit_status(StatusUpdate) ----->|-- POST /statuses/{sha}
| |
|-- PostPipelineComment ----------->|-- GET /pulls?state=open
| |-- POST /issues/{n}/comments
| |-- POST /statuses/{sha} (with target_url)
```