Skip to content

ci(nitpick): resync inlined workflow with nitpick-flow 2d8a694 - #41

Merged
queso merged 1 commit into
mainfrom
ci/nitpick-model-refresh
Sep 18, 2026
Merged

queso merged 1 commit into
mainfrom
ci/nitpick-model-refresh

Conversation

@queso

@queso queso commented Sep 18, 2026

Copy link
Copy Markdown
Owner

What broke

Nitpick has failed on every PR since 2026-08-29. Two different causes, in sequence:

When Error Status
2026-08-29 HTTP 412 — Fireworks account suspended (spend limit / unpaid invoice) resolved; today's runs bill real tokens
2026-09-18 HTTP 404 — Model not found, inaccessible, and/or not deployed this PR

The 404 hits at the coordinate station. The post-mortem localizes it: 7 model calls succeeded, all on REVIEWER_MODEL, and the entry card's lane journey ends at gather_evidence → coordinate (forward) with zero calls recorded for any coordinate station.

COORDINATOR_MODEL was still accounts/fireworks/models/qwen3p7-plus, which Fireworks sunset in 2026-09. nitpick-flow retired it on 2026-09-05 by changing a workflow_call input default — and this repo inlines the job (public repo cannot call a private reusable workflow), so it never saw the change. flow.yaml and the scripts come from the floating :main image and do update themselves; only the env: block is frozen. That asymmetry is the whole bug.

nitpick-flow's own comment predicted it:

Predecessors: qwen3p7-plus (sunset 2026-09), qwen3p6-plus (delisted — runs died with HTTP 404 at the coordinate station).

The reviewer model was also stale, and that was worse

REVIEWER_MODEL was gpt-oss-120b, replaced upstream the same day. It never hard-failed; it had ~zero recall above ~12k tokens (empty findings 48/50 runs on the DevTrack #23 fixtures, 0/11 human ground-truth findings vs glm's 4/11). This repo's six review stations ran on ~12k-token prompts and returned 48–270 output tokens. The green checks through 2026-08-24 were passing because they found nothing.

Both models now go to glm-5p3-flash, matching upstream's defaults exactly.

Other drift ported

Executable code is byte-identical to upstream; only comments are localized.

  • diff fetch — paginated per-file rebuild when the one-shot diff media type hard-fails (diff too_large past 20k lines), plus a 60k-line safety valve that prioritizes source over tests and emits an omission manifest.
  • spider step — graph.json (coupling graph for shard planning) and related.txt (RELATED CODE excerpts from unchanged files the diff reaches, where contract-class bugs live). continue-on-error, so a failure degrades the review instead of blocking it.
  • whole-run retry — 3 attempts with a fresh state volume each, for gateway stalls (fatal to a conduit run today), gated so a deterministic token-budget andon is not retried three times.
  • related.txt guard — the review stations declare it as an input and the spider step can skip.

Verification

  • yq parses the workflow; bash -n clean on all 6 run: blocks.
  • Every ported run: body diffed against upstream with comments stripped: identical.
  • All four env values now equal upstream's input defaults.
  • Spider smoke-tested against this repo from the local nitpick-flow checkout: files=46 routes=0 edges=99 (import=99), 9.5 KB graph.json. The related-pack comes back empty on a docs-only diff, which is correct, and spider.mjs emits the same (no related code identified) string as the fallback guard.
  • /flow/src/spider.mjs exists in the image (Dockerfile copies src/ to /flow/src/).

Not verified locally: no Fireworks key in the environment, so I could not confirm glm-5p3-flash against the live catalog. It is upstream's current default and upstream is presumably green on it. This PR's own Nitpick run is the real test.

Kept local

Must survive the next sync, and the header now says so: the fork guard, secret names FIREWORKS_AI_API_KEY / GHCR_READ_PAT, job id nitpick (the check name branch protection matches), and the env: block. The header tells the next syncer to diff that env block against upstream's input defaults first.

Note on #40

PR #40 will keep showing the old failure until it picks this up — pull_request runs the workflow from the PR's own merge commit. Merge this, then rebase #40.

🤖 Generated with Claude Code

Nitpick has failed on every PR since 2026-08-29. The live error is

  fatal: Model gateway returned HTTP 404: Model not found, inaccessible,
  and/or not deployed

at the `coordinate` station: COORDINATOR_MODEL was still qwen3p7-plus,
which Fireworks sunset in 2026-09. Upstream retired it on 2026-09-05 by
changing a workflow_call input default, and this repo inlines the job
(public repo, private reusable workflow), so it never saw the change.

Both models go to glm-5p3-flash, matching upstream's defaults. The
reviewer swap matters as much as the coordinator one: gpt-oss-120b never
hard-failed, it just had ~zero recall above ~12k tokens, so the last
green runs on this repo were empty verdicts on ~12k-token prompts.

Also ports the rest of the drift, code byte-identical to upstream:

- diff fetch: fall back to a paginated per-file rebuild when the one-shot
  diff media type hard-fails ("diff too_large" past 20k lines), plus a
  60k-line safety valve that keeps source files over tests and emits an
  omission manifest.
- spider step: coupling graph (graph.json) for shard planning and the
  reviewers' RELATED CODE pack (related.txt) from unchanged files the
  diff reaches. Smoke-tested against this repo: 46 files, 99 import
  edges. continue-on-error, so a failure degrades rather than blocks.
- whole-run retry (3 attempts, fresh state volume each) for gateway
  stalls, which are fatal to a conduit run today, gated so a
  deterministic token-budget andon is not retried three times.
- related.txt existence guard, since the review stations declare it as an
  input and the spider step may skip.

Kept local and must survive the next sync: the fork guard, secret names
FIREWORKS_AI_API_KEY / GHCR_READ_PAT, job id `nitpick` (the check name
branch protection matches), and the env block itself. The header now says
to diff that env block against upstream's input defaults first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick review — comment

This rewrite substantially expands the PR-review workflow: a new spider step, a diff-truncation safety valve, and a retry loop with a token-budget short-circuit, all executing a cross-org container image on every pull request. The main risk is that the flow image is pinned to the mutable :main tag while the diff deepens reliance on it, so an upstream push silently changes what runs with repo credentials; the retry loop also re-runs the full flow (roughly 3x token spend) on deterministic failures that can never succeed on retry. Human review is recommended before merge given the CI and supply-chain surface; verdict is comment, not block.

5 inline comment(s).

@@ -20,12 +27,29 @@ on:

env:
FLOW_IMAGE: ghcr.io/queso/nitpick-flow:main

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning · Flow image pinned to the mutable main tag while the diff deepens reliance on it

FLOW_IMAGE is ghcr.io/queso/nitpick-flow:main — a floating tag the publisher can move at any time, and a cross-org image (the GHCR login comment confirms GITHUB_TOKEN cannot see queso's package). This diff materially increases what that image does in the pipeline: it now also executes the spider step (graph.json / related.txt generation, run as --entrypoint bun), donates the baked flow.yaml that gets rendered and re-injected, and its report-failure.mjs runs in the post-mortem step. Whatever is at :main when a PR opens is what executes, and that code receives CONDUIT_API_KEY, GITHUB_TOKEN-scoped permissions, and a read-only mount of the full checkout. The diff documents careful pinning discipline elsewhere (sync commit hashes, model versions) and even records being burned by upstream drift (the qwen3p7-plus default change); the image is the one unpinned input.

Suggested change
FLOW_IMAGE: ghcr.io/queso/nitpick-flow:main
Resolve the image to a content digest (`ghcr.io/queso/nitpick-flow@sha256:<digest>`) and update it via a deliberate bump alongside the existing 'last synced' comment, or at minimum pin an immutable version tag instead of `main`.

"split it, or raise budgets.run.max_tokens with the shard/fan-out sizing it implies."
break
fi
echo "conduit run attempt $attempt failed (exit $rc); retrying with a fresh state volume"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning · Retry loop re-runs the whole flow (3x spend) on deterministic non-token failures too

The retry loop correctly treats the token-budget andon as terminal, but every other non-zero exit is retried up to 3 times with a fresh volume — including deterministic failures the diff itself calls out as 'real failures': scrap/hold lanes and doctor preflight rejections. A flow that is 'a pure function of the diff' (the comment's own words) will produce the same scrap/hold/preflight result on attempts 2 and 3, costing the measured ~1.7M input tokens per attempt (~5M total) plus ~3x wall-clock latency for zero additional review — exactly the waste the token-andon guard was written to prevent, via a different terminal lane. The log-matching here only distinguishes the token case.

Suggested change
echo "conduit run attempt $attempt failed (exit $rc); retrying with a fresh state volume"
Extend terminal-lane detection beyond the token andon: match the engine's other deterministic halt markers (scrap/hold verdicts, doctor preflight failure lines) and break on those too, reserving retries for transient stalls. Alternatively, gate retries on the absence of any terminal-lane marker rather than the presence of one specific string.

run: |
# World-writable scratch dir: the image runs as its non-root `conduit` user, whose uid
# does not match the runner's, so a plain bind-mounted dir is EACCES on first write.
mkdir -p .spider-out && chmod 777 .spider-out

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion · World-writable scratch directory (chmod 777) shared with the container

mkdir -p .spider-out && chmod 777 .spider-out makes the output directory writable by every user and process on the runner, and it is then bind-mounted into the flow container. On an ephemeral GitHub-hosted runner the practical risk is low, but a 777 directory feeding graph.json/related.txt into the review pipeline is an unnecessary loosening; a group-writable mode matching the container uid, or a named volume, achieves the same EACCES fix more tightly.

Suggested change
mkdir -p .spider-out && chmod 777 .spider-out
Prefer `chmod 1777` (sticky) or create the dir with a uid/gid matching the image's `conduit` user instead of 777.

#
# Fork guard: fork PRs don't receive secrets, so the job only runs for same-repo PRs —
# external contributions skip review instead of failing on a missing key.
# Last synced against nitpick-flow 2d8a694 (2026-09-18). What is DELIBERATELY local, and so

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion · Deliberately-local divergence list is incomplete relative to the new local-only additions

The rewritten header establishes an explicit sync contract with nitpick-flow's nitpick.yml, enumerating what is "DELIBERATELY local ... must survive the next sync" (fork guard, secret names, job id, env block). This diff adds three substantial local-only behaviors not in that list: the per-file diff-rebuild fallback for >20k-line diffs, the Node safety-valve truncation script, and the whole-run retry loop with the andon short-circuit (the retry-loop:begin/end markers note upstream slices that region into retry-gate.test.mjs, but the header's must-survive-sync list doesn't mention it). Since the file still instructs keeping steps in sync with upstream, a future sync trusting the header's list as complete could silently drop the fallback, the valve, or the retry loop.

Suggested change
# Last synced against nitpick-flow 2d8a694 (2026-09-18). What is DELIBERATELY local, and so
Extend the "DELIBERATELY local" header list to cover the diff-rebuild fallback, the 60k-line safety valve, and the retry loop / andon gate (noting the retry-loop:begin/end lockstep markers).

# keep whole files up to the budget, SOURCE files before tests, and close with an
# explicit manifest of what was left out — a partial review with honest coverage
# beats a dead job.
node -e '

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion · Inline diff-truncation script ships with no test, unlike its sibling retry-loop logic

The node -e block contains real, failure-prone logic: the isTest classifier (which decides which files get dropped first), the greedy budget packing, and the omitted-files manifest. A bug here silently removes source files from review. The retry loop below it is explicitly carved out for upstream testing (retry-gate.test.mjs slices the retry-loop:begin/end region verbatim), demonstrating the project's established pattern for testing inline workflow script; the truncation valve has no equivalent marker or test. The isTest regex also only recognizes __tests__/, tests?/ dirs, and .test.[jt]sx? — .spec. files and pytest-style test_*.py would be classified as source and prioritized, which may be intended but is untested either way.

Suggested change
node -e '
Add a retry-gate.test.mjs-style test that feeds the valve a synthetic multi-file diff (mixed test/source, over-budget, exactly-at-budget) and asserts which files survive, or mark the script region with a slice comment like the retry loop's so it can be tested where it lands.

@queso
queso merged commit f5e71fb into main Sep 18, 2026
2 checks passed
@queso
queso deleted the ci/nitpick-model-refresh branch September 18, 2026 19:20
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.

1 participant