Skip to content

ci: add the check that makes "all green" mean something (#27) - #31

Merged
linhdmn merged 2 commits into
mainfrom
ci/github-actions
Sep 21, 2026
Merged

linhdmn merged 2 commits into
mainfrom
ci/github-actions

Conversation

@linhdmn

@linhdmn linhdmn commented Sep 21, 2026

Copy link
Copy Markdown
Member

Closes #27.

This repo had no .github/ directory at all. The only thing that ran on a pull request was the gitStream app, which reports skipping — so every "tests pass" claim in #24/#25/#26 meant a human ran go test ./.... PRD §13 marked M6 closed with "deploys blocked on the full-suite gate" and §13.1's move 8 makes the eval suite the deploy gate. Neither was enforced by anything. That is now a check's verdict, not a sentence in a PR description.

What runs on every PR

Job Steps
build-test gofmt check → go build → go vet → go test ./... -count=1 → golangci-lint
prd python3 docs/check-prd.py and --selftest

--selftest is in there deliberately: it asserts the checker can still fail, which is what stops check-prd.py rotting into a script that always prints OK. It had already rotted once — it could not run at all until #29.

make check now runs the same five steps locally, so a green local run and a green CI run mean the same thing. make fmt-check was verified to fail on a deliberately unformatted file, not just to pass on a clean tree.

Getting lint to a clean baseline found real defects

Not style — three of these are bugs:

  • currentTier (LoopRunner) was dead. Written by nothing, read by nothing; RunResult.CurrentTier is the field that actually carries the tier.
  • maxLandmarkTokens was a 20% budget nothing enforced. internal/memory's own comment claimed landmarks' "cap makes this unreachable" — that cap was the unused constant. Deleted rather than documented: landmarks are never evicted by design (P32), so a threshold for them is a number no code could honour. What it costs is now §9.1's fourth accepted ceiling: the 70% rule is a working-tier guarantee, not a whole-store one, because enforceCeiling() stops rather than evict a landmark.
  • Unchecked writes in the HTTP layer. json.Encoder.Encode, the SSE/console fmt.Fprintf, and http.ListenAndServe now go through writeJSON/writeConsole, which log the failure — a broken client cannot be told (headers are already sent) but it should not be silent.
  • Plus m4_test.go's defer cp.Close() and the two HTTP clients' resp.Body.Close().
  • Categorize's switch → tagged switch (staticcheck QF1002); behaviour-identical.

.golangci.yml

Pins the linter set so CI and a local run agree, and states its one exclusion: errcheck inside _test.go, where defer resp.Body.Close() is conventional and its failure mode is a test that fails anyway. Production code gets no exclusion — those were fixed instead.

Also

.gitignore: the bare agentloop pattern needed its !cmd/agentloop/ un-ignore (it was listed twice, which is what the duplicate was for — one line with the reason beats two without).

Verification

  • make check green: gofmt clean, vet clean, 13/13 packages, lint 0 issues, PRD OK, selftest 12/12.
  • The workflow itself runs on this PR — this is the first PR in this repo where CI is the evidence.

Not in this PR

The eval endpoint is still not called by CI. The gate is "a green suite", not "a passing suite" — wiring GET /admin/api/v1/evals into the workflow is the M6 follow-through, and the PRD's M6 row says so rather than claiming otherwise.

linhdmn added a commit that referenced this pull request Sep 21, 2026
The workflow is correct and runs the right steps, but FreePeak/agentloop is a
private repo and GitHub-hosted runners are billed — until the org's spending
limit is raised, both jobs fail at dispatch with

  The job was not started because recent account payments have failed or your
  spending limit needs to be increased.

which is a red check that says nothing about the code (seen on PR #31). Written
down in the three places someone would look — USAGE §2, the PRD's M6 row, and
todo.md — so the next person reads the reason instead of re-deriving it.

make check still runs the identical five steps locally and is green.
This repo had no `.github/` at all. The only thing that ran on a pull request
was the gitStream app, which reports `skipping` — so every "tests pass" claim in
"deploys blocked on the full-suite gate" and §13.1's move 8 makes the eval suite
the deploy gate; neither was enforced by anything.

`.github/workflows/ci.yml` runs two jobs on every PR:
  - build-test: gofmt check, go build, go vet, go test, golangci-lint
  - prd: docs/check-prd.py **and** its --selftest, so the checker cannot rot
    into a script that always prints OK
`make check` now runs the same five steps locally, so a green local run and a
green CI run mean the same thing.

Getting the lint job to a clean baseline found real defects, not just style:

  - `currentTier` (LoopRunner) was written by nothing and read by nothing —
    `RunResult.CurrentTier` is the field that actually carries the tier.
  - `maxLandmarkTokens` (internal/memory) was a 20% budget that **no code
    enforced**. The file's own comment claimed landmarks' "cap makes this
    unreachable", which was the cap itself, unused. Deleted rather than
    documented: landmarks are never evicted by design (P32), so a threshold for
    them is a number no code could honour. What that costs is now recorded as
    §9.1's fourth accepted ceiling — the 70% rule is a **working-tier**
    guarantee, not a whole-store one, because enforceCeiling() stops rather than
    evict a landmark.
  - `Categorize` had a switch staticcheck flagged as a tagged switch; rewriting
    it is behaviour-identical and reads better.
  - Unchecked writes in the HTTP layer (`json.Encoder.Encode`, `fmt.Fprintf`
    for the SSE and console pages, `http.ListenAndServe`) now go through
    `writeJSON`/`writeConsole`, which log the failure — a broken client cannot
    be told about it (headers are already sent) but it should not be silent.
  - `m4_test.go`'s `defer cp.Close()` and the two clients' `resp.Body.Close()`.

`.golangci.yml` pins the linter set and states the one exclusion: errcheck in
`_test.go`, where `defer resp.Body.Close()` is conventional and its failure mode
is a test that fails anyway. Production code gets no exclusion.

Also fixes `.gitignore`: the bare `agentloop` pattern needed its `!cmd/agentloop/`
un-ignore (it was there twice, which is what the duplicate was for — one line
with the reason beats two without).

Checks: `make check` green — gofmt clean, vet clean, 13/13 packages, lint
0 issues, PRD OK and selftest 12/12. `make fmt-check` verified to fail on a
deliberately unformatted file, not just to pass.
The workflow is correct and runs the right steps, but FreePeak/agentloop is a
private repo and GitHub-hosted runners are billed — until the org's spending
limit is raised, both jobs fail at dispatch with

  The job was not started because recent account payments have failed or your
  spending limit needs to be increased.

which is a red check that says nothing about the code (seen on PR #31). Written
down in the three places someone would look — USAGE §2, the PRD's M6 row, and
todo.md — so the next person reads the reason instead of re-deriving it.

make check still runs the identical five steps locally and is green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI: nothing runs go test on a PR (M6's "deploys blocked on the suite" is not enforced)

1 participant