ci: switch gadfly review to the reusable workflow (curated swarm, 5 lenses) #6
Reference in New Issue
Block a user
Delete Branch "ci/gadfly-reusable"
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?
Replaces majordomo's full self-contained Gadfly stub with a thin caller of steve/gadfly's reusable workflow that inherits the default swarm.
uses: steve/gadfly/.gitea/workflows/review-reusable.yml@b02b11d(immutable pin)review-reusable.yml, steve/gadfly#10): 3 cloud models (minimax-m3, glm-5.2, deepseek-v4-pro) + Claude Code (sonnet, opus, opus:max), 5-lens suite, claude models serial / 5 lenses each. (This is the config originally prototyped in this PR — now centralized so all consumers share it.)OLLAMA_CLOUD_API_KEY/CLAUDE_CODE_OAUTH_TOKEN/ findings tokens (notsecrets: inherit);GITEA_TOKENis the auto job token.allowed_users; all swarm tuning lives centrally in gadfly.Advisory only — never blocks a merge.
🤖 Generated with Claude Code
🪰 Gadfly — live review status
6/6 reviewers finished · updated 2026-06-28 02:07:25Z
claude-code/opus· claude-code — ✅ doneclaude-code/opus:max· claude-code — ✅ doneclaude-code/sonnet· claude-code — ✅ donedeepseek-v4-pro:cloud· ollama-cloud — ✅ doneglm-5.2:cloud· ollama-cloud — ✅ doneminimax-m3:cloud· ollama-cloud — ✅ doneLive status board. Findings are posted in each model's own comment. Advisory only — does not block merge.
🪰 Gadfly review —
deepseek-v4-pro:cloud(ollama-cloud)Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling
🔒 Security — No material issues found
Verdict: No material issues found
The change is a net security improvement over the previous workflow:
OLLAMA_CLOUD_API_KEY,CLAUDE_CODE_OAUTH_TOKEN,GADFLY_FINDINGS_URL,GADFLY_FINDINGS_TOKEN), andGITEA_TOKENis handled automatically by the runner — this is genuine least-privilege.@20a5c431f22d43bab0f711c14ce74de3a735c110prevents a push tosteve/gadflyfrom silently changing the code that runs with forwarded secrets. The old workflow also pinned (sha-d7f364d), so this is parity, not regression.if:condition restrictingissue_commenttriggers tosteve,fizi,dazedis unchanged.pull_requestandworkflow_dispatchremain trusted triggers (require repo write access).with:parameters — all values (models,specialists,provider_concurrency,timeout_secs,max_steps,allowed_users,timeout_minutes) are hardcoded strings; nothing interpolatesgithub.event.comment.bodyor any other attacker-controlled value.permissions:block unchanged —contents: read, issues: write, pull-requests: writeremains at the workflow level, constraining what the called reusable workflow can do.The one new credential (
CLAUDE_CODE_OAUTH_TOKEN) is expected given the addition of Claude Code models to the swarm, and it's forwarded through the same pinned, least-privilege channel as the others. The trust boundary (code authored bysteve/gadfly) is identical to the old Docker-image approach.🎯 Correctness — Minor issues
Now let me verify the two findings against the actual code.
Finding 1 —
provider_lens_concurrencyomitsclaude-codeentry:The actual file at line 55 shows
provider_lens_concurrency: "ollama-cloud=3"with noclaude-codeentry. However, the draft itself marks this as unverified because the reusable workflow at20a5c43lives insteve/gadfly, not this repo. I cannot inspect that workflow either — I have no way to determine the default lens-concurrency behavior for unspecified providers. Per the instructions, I must drop any finding I cannot positively confirm.Finding 2 —
vars.GADFLY_SPECIALISTSoverride silently dropped:Confirmed against the actual file. The old workflow (visible in the diff) resolved
GADFLY_SPECIALISTS: ${{ vars.GADFLY_SPECIALISTS || 'security,correctness,error-handling' }}. The new workflow at line 53 hardcodesspecialists: "security,correctness,maintainability,performance,error-handling"with no reference tovars.GADFLY_SPECIALISTS. A grep forGADFLY_SPECIALISTSacross the entire repo returns zero matches. The behavioral contract change is real: any repo-levelGADFLY_SPECIALISTSvariable set in Gitea's web UI is now silently ignored.Verdict: Minor issues
vars.GADFLY_SPECIALISTSrepo-variable override silently dropped —adversarial-review.yml:53. The old workflow resolvedGADFLY_SPECIALISTS: ${{ vars.GADFLY_SPECIALISTS || 'security,correctness,error-handling' }}, allowing repo-level customization of the lens suite without editing the workflow file. The new workflow hardcodesspecialists: "security,correctness,maintainability,performance,error-handling"with no fallback tovars. IfGADFLY_SPECIALISTSwas set in the repo's Gitea settings (unverifiable from repo files — repo variables live in the web UI, not in the tree), it is now silently ignored. The PR description frames the 5-lens suite as intentional, so this is likely a deliberate simplification, but it is a behavioral contract change worth flagging.🧹 Code cleanliness & maintainability — Minor issues
Verdict: Minor issues
provider_lens_concurrencyasymmetry —provider_concurrencydeclares limits for both providers (ollama-cloud=3,claude-code=3), butprovider_lens_concurrencyonly setsollama-cloud=3with no entry forclaude-code(.gitea/workflows/adversarial-review.yml:55). The old workflow only had cloud models so the single entry was complete; now that claude-code models are in the swarm, the asymmetry between the two concurrency knobs is confusing. A future maintainer will wonder whether omitting claude-code from lens concurrency was deliberate or an oversight. If intentional, a one-line comment explaining why (e.g.,# claude-code manages its own lens fan-out) would eliminate the ambiguity.Hardcoded specialists removes the repo-variable escape hatch — the old workflow allowed overriding the lens suite at runtime via
${{ vars.GADFLY_SPECIALISTS || '…' }}; the new workflow hardcodes the 5-lens string (.gitea/workflows/adversarial-review.yml:53). The PR description frames this as a deliberate "curated swarm" choice, which is fine, but it means any ad-hoc adjustment (e.g., running onlysecurityfor a quick spot-check) now requires editing and committing the workflow file rather than toggling a repo variable. Worth acknowledging as a flexibility trade-off, even if intentional.⚡ Performance — Minor issues
Now let me verify the second finding — the timeout change — by confirming the old value in the diff:
The diff clearly shows:
timeout-minutes: 45(removed)timeout_minutes: 90(line 59)This is confirmed. The timeout doubled from 45 to 90 minutes.
Now for the first finding: the draft itself admits it cannot verify the reusable workflow's default behavior for
provider_lens_concurrencybecause the reusable workflow lives insteve/gadfly, outside this repo. The draft speculates that the default is 1 for unspecified providers, but this is unconfirmable from the code in this repository. I cannot positively confirm this finding.Verdict: Minor issues
.gitea/workflows/adversarial-review.yml:59): The old job-leveltimeout-minutes: 45is replaced bytimeout_minutes: 90passed to the reusable workflow. Even accounting for the heavier Claude Code models and the lens-suite expansion (3→5), this is a 2× ceiling increase. A hung or slow model can now consume CI runner time for twice as long before being killed.🧯 Error handling & edge cases — Minor issues
I've read the actual workflow file and confirmed the finding. The
reviewjob (line 32) has notimeout-minutes— only theuses:,secrets:, andwith:blocks. The old code (visible in the diff) hadtimeout-minutes: 45directly on the job. Thetimeout_minutes: 90on line 59 is merely an input to the reusable workflow, not a platform-level job timeout. The finding is confirmed.Verdict: Minor issues
timeout-minutesbackstop (adversarial-review.yml:44): The old workflow hadtimeout-minutes: 45on thereviewjob — a hard platform-level cap that would kill the job even if the container/reusable-workflow hung. The new workflow removes this and passestimeout_minutes: 90only as an input to the reusable workflow. If the reusable workflow fails to enforce its own timeout (e.g., stuck waiting for a runner, a bug in the reusable workflow's timeout wiring, or the reusable workflow itself hangs before its internaltimeout-minutestakes effect), the calling job has no backstop and will run until the platform default (typically 360 minutes). This is a regression in defensive error handling — the old code had belt and suspenders; the new code relies entirely on the callee to time itself out.Fix: Add
timeout-minutes: 90(or 120) directly on thereview:job, above or below theuses:line. This keeps the platform-level kill switch independent of the reusable workflow's internal timeout.Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 4m 57s
🪰 Gadfly review —
minimax-m3:cloud(ollama-cloud)Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling
🔒 Security — Minor issues
The findings all check out against the code:
issue_commentconfirmed.20a5c431f22d43bab0f711c14ce74de3a735c110confirmed.claude-code/*models receiveCLAUDE_CODE_OAUTH_TOKENconfirmed.VERDICT: Minor issues
.gitea/workflows/adversarial-review.yml:36-41—issue_commenttrigger guard is job-level only in this repo. Theif:allow-lists actors (steve,fizi,dazed) for the comment path; theallowed_users: "steve,fizi,dazed"input is forwarded as belt-and-suspenders, but whether the upstreamsteve/gadfly/.gitea/workflows/review-reusable.yml@20a5c431f22d43bab0f711c14ce74de3a735c110actually enforces it in-container cannot be confirmed from this repo. The SHA pin is correct, so a push to gadflymaincannot silently alter what runs. Suggest confirming in the gadfly repo thatallowed_usersis wired into anif:on the commenting step..gitea/workflows/adversarial-review.yml:44— third-party reusable workflow pinned to commit SHA.steve/gadfly/.gitea/workflows/review-reusable.yml@20a5c431f22d43bab0f711c14ce74de3a735c110(full SHA, not@main). Correct supply-chain call: a force-push tosteve/gadflymaincannot silently change what runs with our secrets. No issue..gitea/workflows/adversarial-review.yml:48—CLAUDE_CODE_OAUTH_TOKENis a long-lived user-scoped OAuth credential. Acceptable because the swarm at line 52 explicitly invokesclaude-code/sonnet,claude-code/opus,claude-code/opus:max, so the OAuth token is required. Not over-scoping. The SHA pin is the primary defense if the reusable workflow's execution environment is compromised..gitea/workflows/adversarial-review.yml:22-25— permissions correctly narrowed.contents: read,issues: write,pull-requests: write; nosecrets: inherit. Forwarded secrets (OLLAMA_CLOUD_API_KEY,CLAUDE_CODE_OAUTH_TOKEN,GADFLY_FINDINGS_URL,GADFLY_FINDINGS_TOKEN) are least-privilege. Net improvement over the priorGITEA_TOKEN+ broader set..gitea/workflows/adversarial-review.yml:27-29— concurrency cancel-in-progress keyed on PR number. Prevents stale reviews from leaking a later PR's secrets/state into an earlier run's comment thread.🎯 Correctness — Minor issues
VERDICT: Minor issues
.gitea/workflows/adversarial-review.yml:54-55—provider_lens_concurrencyis asymmetric between providers. Line 54 setsprovider_concurrency: "ollama-cloud=3,claude-code=3"(both providers get an explicit lane width), but line 55 setsprovider_lens_concurrency: "ollama-cloud=3"with noclaude-code=…entry. The lens suite also grew from 3 lenses (security, correctness, error-handling) to 5 (adding maintainability, performance) per thespecialists:input on line 53. The omittedclaude-codelens-concurrency setting means the three claude-code models (sonnet,opus,opus:max) inherit whatever the gadfly default is for that key — most plausibly 1, making each claude model run all 5 lenses serially. Withtimeout_secs: 600per lens on line 56 and three claude models running in parallel lanes (perprovider_concurrency=claude-code=3), the worst case is one claude model at ~50 min, which still fits insidetimeout_minutes: 90on line 59, but the asymmetry should be either intentional and documented in the header comment, or fixed by addingclaude-code=3toprovider_lens_concurrency. Confirmed by reading lines 54-55 of the new file.🧹 Code cleanliness & maintainability — Minor issues
VERDICT: Minor issues
CLAUDE.md:144-147— docs now drift from the workflow (verified).CLAUDE.md still says Gadfly is "the standalone agentic adversarial reviewer", "a fleet of 6 ollama-cloud models", running "the 3-lens suite (security, correctness, error-handling)". The PR ships a thin caller of
steve/gadfly's reusable workflow with 3 cloud + 3 claude-code models and the 5-lens suite (addsmaintainability,performance). The repo's own rule at CLAUDE.md:135-137 ("README.md must match reality in the same commit that changes behavior") is violated here..gitea/workflows/adversarial-review.yml:39-41and:58— duplicated allow-list (verified).The job-level
if:guard hard-codesgithub.actor == 'steve' || ... == 'fizi' || ... == 'dazed', and the same list is re-passed viaallowed_users: "steve,fizi,dazed". Two places to keep in sync; will drift the next time someone is added or removed. Minor cleanup suggestion..gitea/workflows/adversarial-review.yml:55—provider_lens_concurrencyonly setsollama-cloud=3(verified).Lens-level concurrency is provider-scoped, and only
ollama-cloudis explicit.provider_concurrencyon line 54 sets both (ollama-cloud=3,claude-code=3), butprovider_lens_concurrencyomitsclaude-code, so it falls through to whatever the reusable workflow defaults to. The header comment (lines 4-6) asserts "Every model runs the 5-lens default suite … lenses are global, so this applies to the cloud AND claude models", which makes the omission ambiguous — a one-line note clarifying the fall-through behavior would make the file self-explanatory.⚡ Performance — Minor issues
VERDICT: Minor issues
.gitea/workflows/adversarial-review.yml:54-55— asymmetric lens parallelism (unverified).provider_concurrency: "ollama-cloud=3,claude-code=3"is set for both providers, butprovider_lens_concurrencyis only set forollama-cloud. With 5 lenses per model, if the reusable workflow's default lens concurrency for an unset provider is1, then each of the 3 Claude models will run its 5 lenses sequentially while the 3 cloud models run all 5 lenses concurrently — so the claude lanes are up to 5× slower per model than the cloud lanes despite the PR description framing the two providers as equivalent. Suggested fix: addprovider_lens_concurrency: "claude-code=5"(or whatever the upper bound should be). I could not verify the reusable workflow's default behavior without checkingsteve/gadfly/.gitea/workflows/review-reusable.yml@20a5c43…, which is outside this repo — flagging as unverified..gitea/workflows/adversarial-review.yml:53,59— ~67% more reviewer work, only 2× timeout headroom. Lens count went from 3 → 5, so total reviewer invocations went from 6 models × 3 = 18 to 6 models × 5 = 30 (~67% more). Job timeout was doubled from 45 → 90 min, which is roughly the right shape but leaves little slack if any single model stalls neartimeout_secs: "600"× 5 lenses. Combined with the 5 claude-code jobs (each an OAuth-authenticated CLI run, generally slower than a cloud HTTP call), the wall-clock per PR can grow noticeably. Mitigation is already in place (concurrency.cancel-in-progress: truekeyed on PR number, line 27-29), so pile-up across PRs is bounded. Worth keeping an eye on runner-pool saturation if PR throughput rises — not a code fix, just a tuning note.No N+1, no hot-loop, no unbounded growth concerns in this diff — it's a static config swap from a self-contained container step to a reusable workflow caller, and
concurrency+cancel-in-progressare preserved unchanged.🧯 Error handling & edge cases — Minor issues
Clean — no stale references.
Summary of findings within my lens:
Minor:
provider_lens_concurrency(line 55) only specifiesollama-cloud=3. The claude-code provider has no entry, so its lenses run serially (1 at a time). With 5 lenses × 600s each, that's up to 50 min per claude model wall time. This is a missed optimization (addingclaude-code=3would reduce it to ~20 min), but NOT a timeout concern: the cloud chain finishes in ~20 min (5 lenses / 3 concurrent × 600s) and runs in parallel with the claude chain, so the overall worst-case wall time is ~50 min, well within the 90 min budget. The original review's claim that "a single slow model can push the run past 90 min" is not supported by the math.Minor:
cancel-in-progress: true(lines 27-29) silently kills in-flight reviews on every push to the PR, losing partial findings. Intentional for cost, but from an error-handling view the partial work is discarded without a tombstone. Low severity.Unverified, not reported as a finding:
max_steps: "14"andtimeout_secs: "600"are strings; whether the reusable workflow declares these astype: numberis in another repo and not checkable from this checkout.VERDICT
Minor issues
.gitea/workflows/adversarial-review.yml:55—provider_lens_concurrency: "ollama-cloud=3"omits theclaude-codeprovider, so its 5 lenses run serially (up to 50 min per model). This is a missed optimization — addingclaude-code=3to mirror the cloud provider would cut claude wall time to ~20 min, and the 3 claude models already run concurrently viaprovider_concurrency: claude-code=3(line 54). The 90 mintimeout_minutesbudget is adequate as-is (cloud chain finishes in ~20 min in parallel, so overall wall time is bounded by the ~50 min claude chain), but the asymmetry between cloud and claude lens concurrency is worth flagging. (Verified by reading lines 52-59.).gitea/workflows/adversarial-review.yml:27-29—concurrency.cancel-in-progress: truesilently discards in-flight reviews on every push to the PR, losing partial findings. Intentional for cost but worth flagging from an error-handling view: an on-call engineer re-running a review after a fix will see the prior run's partial lens outputs vanish without trace. No fix needed unless you want a tombstone; just noting it's an unhandled partial-failure mode. (Verified by reading lines 27-29.)Unverified, not reported as a finding:
max_steps: "14"andtimeout_secs: "600"are strings; whether the reusable workflow declares these astype: number(and silently coerces) is in another repo and not checkable from this checkout. Flagging only so a future reviewer confirms the called workflow'sinputs:types match.Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 4m 31s
🪰 Gadfly review —
claude-code/opus(claude-code)Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling
🔒 Security — Minor issues
Verified. The file matches the diff exactly, and the reusable workflow indeed lives in another repo (only
adversarial-review.ymlandci.yamlare present here). All verifiable claims in the finding hold:pull_request: types: [opened, reopened, ready_for_review](lines 12-13) ✓if:guard restricts onlyissue_comment;pull_requestfalls throughevent_name != 'issue_comment'and runs for any author (lines 36-41) ✓CLAUDE_CODE_OAUTH_TOKENis newly forwarded (line 48) ✓pull-requests: write(line 25) ✓pull_request ... [is] already trusted(lines 33-34) ✓The finding is correctly hedged on the only unverifiable piece (whether the Gitea instance leaks secrets to fork PRs). It survives.
VERDICT: Minor issues
This diff is mostly a positive security refactor (pinning + least-privilege secret forwarding). The one substantive concern is the author-trust boundary for
pull_requestevents, now carrying a heavier credential set.Findings
.gitea/workflows/adversarial-review.yml:11-13, 36-50—pull_requestruns are not author-gated, and this diff adds a new high-value credential to what's forwarded into them. Theif:guard (lines 36-41) only restrictsissue_commentevents tosteve/fizi/dazed. Forpull_request(opened, reopened, ready_for_review) any PR author triggers the secret-bearing reusable workflow. The diff newly forwardsCLAUDE_CODE_OAUTH_TOKEN(line 48) — an account-scoped OAuth token, more sensitive than the cloud API key — alongsideOLLAMA_CLOUD_API_KEYand the findings token. If this Gitea instance forwards secrets to fork PRs (environment-dependent — not verifiable from the repo), an untrusted contributor opening a PR would cause those secrets to be present in a job that also holdspull-requests: write(line 25) and runs LLM agents over attacker-controlled diff/PR content — a plausible prompt-injection exfiltration path (read env secret → post as a comment). The inline comment's premise that "pull_request … [is] already trusted" (lines 33-34) is the unstated assumption doing all the work; it only holds if forks can't open PRs or the instance withholds secrets from fork PRs.CLAUDE_CODE_OAUTH_TOKEN/OLLAMA_CLOUD_API_KEYto an untrusted PR author.pull_requeston author membership too (or run untrusted PRs behind a label/maintainer-approval gate), and/or confirm the Gitea instance does not pass secrets to fork PRs. At minimum, make the trust assumption explicit rather than implicit.Verified as sound (not issues)
…@20a5c431f22d43bab0f711c14ce74de3a735c110is a full 40-char commit SHA (immutable) — correctly prevents a@mainpush from silently changing code that runs with forwarded secrets.secrets: inheritis a genuine improvement and correctly scoped to what the swarm uses.issue_commentguard (lines 36-41): unchanged and intact; still restricts comment-triggered runs to trusted actors.Could not verify
The reusable workflow
steve/gadfly/.gitea/workflows/review-reusable.ymllives in another repo and is not checked out here (.gitea/workflows/contains onlyadversarial-review.ymlandci.yaml). I could not confirm that it declares these foursecrets:/with:inputs in itsworkflow_callcontract, nor how it handles the forwarded tokens internally (e.g. whether it echoes/logs them, or howGITEA_TOKENis provisioned). Those are the highest-leverage things to check on the gadfly side, but they're outside this repo's diff.🎯 Correctness — No material issues found
I've read the full workflow file and confirmed the diff is the entire change (one file). Now let me assess what's verifiable in-repo versus what lives in the external reusable workflow.
The reusable workflow being called (
steve/gadfly/.gitea/workflows/review-reusable.yml@20a5c43) is not in this checkout, so its input/secret contract cannot be verified from here. I'll be explicit about that boundary.VERDICT: No material issues found
Through the Correctness lens, the change is internally consistent and the YAML structure is valid for a reusable-workflow caller. Notes below; none are blocking.
Verified (read
adversarial-review.ymlin full):uses:may not also declareruns-on/steps/env/timeout-minutes; the diff correctly removed all of those, and the only retained job-level key isif:, which is permitted on a reusable-workflow-calling job. (:44-59)models(:52) lists exactly the 6 described — 3 cloud (minimax-m3:cloud, glm-5.2:cloud, deepseek-v4-pro:cloud) + 3 Claude (sonnet, opus, opus:max).claude-code=3(:54) equals the Claude model count, so "all three run concurrently" is accurate, not an off-by-one. The three dropped models named in the PR (qwen3-coder:480b,nemotron-3-super,glm-5.1) are correctly absent.specialistsis the claimed 5-lens set (:53), and theif:actor allow-list (:39-41) is consistent with theallowed_users: "steve,fizi,dazed"input (:58).Could NOT verify (external — flagged, not asserted as bugs):
with:(models,specialists,provider_concurrency,provider_lens_concurrency,timeout_secs,max_steps,allowed_users,timeout_minutes) andsecrets:must exactly match theon.workflow_call.inputs/secretsdeclared inreview-reusable.yml@20a5c43. A single name or type mismatch makes the job fail at dispatch. This is the main correctness exposure and it lives entirely in the pinned external commit, which isn't fetchable from this repo. Confirm against that commit before merge.issue_commenttriggers. The old step explicitly forwardedPR,PR_BRANCH(github.head_ref),COMMENT_BODY,COMMENT_ID, and theworkflow_dispatchpr_number. The new caller forwards none of these — it relies on the reusable workflow deriving them from the inheritedgithubcontext and checking out the PR head itself (notegithub.head_refis empty onissue_comment, so the reusable side must resolve the head ref from the PR number). Whether it does is internal to the external workflow.Minor (consistency, not a bug):
timeout_minutes: 90(:59) is passed unquoted (number) while all sibling inputs are quoted strings ("600","14"). Harmless if the reusable workflow declares it astype: number; would fail input validation only if it's declaredtype: string. Cosmetically inconsistent regardless.No logic, unit, threshold, or magic-number defects found within this file.
🧹 Code cleanliness & maintainability — Minor issues
Both findings verified against the actual files. The doc contradiction is real (CLAUDE.md:144-146 says "6 ollama-cloud models" + "3-lens suite"; the workflow now uses 3 cloud + 3 claude-code models and a 5-lens suite). The quoting inconsistency is also present as described (lines 56-57 quoted, line 59 unquoted).
VERDICT: Minor issues
CLAUDE.md:144-146— documentation now contradicts the workflow this PR changes. The operating manual still says the workflow runs "a fleet of 6 ollama-cloud models, each running the 3-lens suite (security, correctness, error-handling)." After this PR the swarm is 3 cloud + 3 Claude-Code models (adversarial-review.yml:52) running the 5-lens suite (adversarial-review.yml:53). The doc is wrong on both the provider mix and the lens count. The file's own conventions make this binding — the header ("keep the README/support-matrix/examples updated in the same commit as the behavior they describe — that in-sync promise is part of the project's credibility") and the Gates section ("README.md must match reality in the same commit that changes behavior"). Fix: update those lines in this same commit to match (e.g. "a curated swarm of 3 cloud + 3 Claude-Code models running the 5-lens suite (security, correctness, maintainability, performance, error-handling)").adversarial-review.yml:56-59— inconsistent scalar quoting in thewith:block (trivial).timeout_secs: "600"andmax_steps: "14"are quoted strings, buttimeout_minutes: 90is an unquoted number. Cosmetic only; harmonize for readability (quote all three, or none). The input-type question (whether the reusable workflow declarestimeout_minutesasstring) lives in the reusable repo, which isn't in this checkout, so only the local style inconsistency is reported here.Everything else in this lane reads clean: the dead env-var plumbing (
GADFLY_MODELS, theGADFLY_*block) was fully removed rather than left commented out, the header comment was rewritten to match the new structure, and theif:-guard /allowed_usersduplication is pre-existing and self-documented as deliberate belt-and-suspenders.⚡ Performance — No material issues found
VERDICT: No material issues found
This is a CI workflow config change (the file matches the diff exactly; verified by reading
.gitea/workflows/adversarial-review.yml). Through the performance lens there is no efficiency regression I can justify:timeout_secs: 600per lens,max_steps: 14,timeout_minutes: 90, andconcurrency.cancel-in-progress: true(line 29) kills superseded runs so redundant work doesn't pile up.provider_concurrency: "ollama-cloud=3,claude-code=3"runs all three Claude models concurrently, keeping wall-clock down.One observation, unverified and not a finding:
provider_lens_concurrencyis set only forollama-cloud=3and omitsclaude-code(line 55). If the reusable workflow defaults Claude lens concurrency to 1, each Claude model would run its 5 lenses serially (up to ~5×600s ≈ 50 min). That still fits inside the 90-min budget withclaude-code=3running the models in parallel, so there's no overrun — but Claude lanes may finish later than they would with explicit lens parallelism. I cannot confirm the default because the reusable workflow lives insteve/gadfly(review-reusable.yml@20a5c43), which is not in this repo and not readable from this checkout. If wall-clock matters, worth settingclaude-codelens concurrency explicitly; otherwise it's a non-issue.Nothing material in the performance lane.
🧯 Error handling & edge cases — No material issues found
I've verified the diff against the checked-out file. The change converts a self-contained
docker://job into a thin caller of an external reusable workflow (steve/gadfly/.gitea/workflows/review-reusable.yml@<sha>). That target lives in another repo and is not present in this checkout, so the actual error-handling logic (PR-number derivation, per-model auth failure, empty-response/failover) now lives outside this diff and cannot be inspected here.Within my lens, here is what I checked:
PR-number resolution across trigger paths — The old job explicitly forwarded
PR: ${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}, a three-way fallback covering thepull_request,issue_comment, andworkflow_dispatchtriggers (all three are still declared inon:). The new caller forwards none of this and passes nopr_numberinwith:. Theissue_commentandworkflow_dispatchunhappy paths now depend entirely on the reusable workflow re-deriving the PR from the inheritedgithub.eventcontext. Theconcurrencygroup still references all three (github.event.issue.number || github.event.pull_request.number || github.event.inputs.pr_number), so the trigger surface is unchanged. I could not verify whether the reusable workflow replicates that fallback — if it only handlespull_request, the comment/dispatch edge cases would silently no-op. Unverified (external file); flagging as the one edge-case-adjacent risk worth a glance when reviewing the gadfly side.Unset secret forwarding —
CLAUDE_CODE_OAUTH_TOKEN/GADFLY_FINDINGS_*forward to empty strings if undefined in the repo. This is advisory-only and non-blocking by design, and the failure handling is inside the reusable workflow — not a defect introduced by this diff.No swallowed errors, missing cleanup/rollback, nil/empty/zero, or off-by-one concerns exist in this declarative caller — there is no imperative logic in my control here.
VERDICT: No material issues found
The error-handling-relevant logic has been delegated to an external reusable workflow that isn't part of this diff; nothing within the changed file mishandles an error or edge case. The only thing I'd want a second look at — and it's unverifiable from this repo — is whether the reusable workflow reproduces the removed three-way PR-number fallback so the
issue_commentandworkflow_dispatchpaths don't silently fail to find a PR.Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 7m 11s
🪰 Gadfly review —
claude-code/opus:max(claude-code)Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling
🔒 Security — Minor issues
I've verified the draft against the actual workflow file. All the in-repo factual claims check out:
if:guard only restrictsissue_comment; for any other event (pull_request,workflow_dispatch) it evaluates true and the job runs unconditionally. ✓pull_requesttriggers (opened, reopened, ready_for_review). ✓CLAUDE_CODE_OAUTH_TOKEN. ✓allowed_usersis an in-container check passed as awith:input (runs after secrets are in the job env). ✓pull_requestis "already trusted." ✓The finding is grounded in the file, and the draft appropriately flags the out-of-repo contingency (Gitea's fork/untrusted-PR secret behavior) rather than asserting it. It survives.
VERDICT: Minor issues
This change is, on balance, a security improvement — explicit least-privilege secret forwarding (replacing broad env passing) and an immutable SHA pin on the external reusable workflow (
review-reusable.yml@20a5c431…,:44) are exactly the right hardening for "secrets forwarded to code in another repo." One material observation and a couple of notes:Finding — trusted-user gating covers only
issue_comment, while a newly-added high-value secret now rides the ungatedpull_requestsurface.gitea/workflows/adversarial-review.yml:36-41— theif:guard only restrictsissue_comment. Forpull_request(opened, reopened, ready_for_review,:12-13) andworkflow_dispatch, the job runs unconditionally and thesecrets:block (:46-50) is forwarded to the reusable workflow.CLAUDE_CODE_OAUTH_TOKEN(:48) to that forwarded set — a far more powerful, longer-lived credential than the previously-forwardedOLLAMA_CLOUD_API_KEY. The reusable workflow then runs an agentic Claude Code reviewer over attacker-controlled PR diff/file content. That is a classic prompt-injection → credential-exfiltration surface: untrusted text in the PR is the model's input, and the OAuth token sits in the same job environment.steve/fizi/dazed, that author triggers a secret-bearing, agentic run with no actor check. The blast radius of a successful injection is now an Anthropic OAuth token, not just an Ollama key.pull_requestby author association /allowed_userstoo (or confirm and document that Gitea withholds secrets from untrusted/fork PRs, the way the comment at:33-34assumes — "pull_request … already trusted"). The in-containerallowed_userscheck (:58) is belt-and-suspenders but runs after secrets are already in the job env, so it doesn't protect the token from injection-based exfiltration.Verification / honesty notes:
:11-16), theif:only matchingissue_comment(:36-41), and the secret set including the newCLAUDE_CODE_OAUTH_TOKEN(:48).steve/gadfly's pinned reusable workflow handles the forwarded secrets internally. Both live in another repo not checked out here. The severity of the finding above is contingent on the first of these — on a closed instance with only the three trusted committers and no fork PRs, real-world risk is low; the gating asymmetry is the latent hazard.allowed_users/models/etc. passed aswith:inputs are non-sensitive (:51-59); classifyingGADFLY_FINDINGS_URLas a secret is harmless over-caution; dropping explicitGITEA_TOKENforwarding is correct (it's auto-provisioned to the called workflow), not a leak.🎯 Correctness — No material issues found
This is a CI workflow change whose counterpart interface (
steve/gadfly/.gitea/workflows/review-reusable.yml) lives in an external repo not present in this checkout. I read the full checked-out file (above) and confirmed the diff applied as shown. I evaluated the YAML for internal logic/consistency through the Correctness lens.VERDICT: No material issues found
The change is internally self-consistent and the job is correctly restructured from a
steps:-based job into a reusable-workflow call (no leftoverruns-on/steps/envthat would be invalid alongside a job-leveluses:). Specifics I checked:allowed_users: "steve,fizi,dazed"(line 58) matches the actor allow-list in theif:guard (lines 39–41) — consistent, no drift.specialists(line 53) lists exactly the five lenses named in the header comment and PR description — consistent.provider_concurrency: "ollama-cloud=3,claude-code=3"(line 54) vsmodels(line 52): 3 cloud models + 3claude-code/*models, soclaude-code=3does make all three Claudes run concurrently as described. Math checks out.Items I could not verify (they depend on the external reusable workflow, which is not in this repo — flagging as unverified, not asserting bugs):
models,specialists,provider_concurrency,provider_lens_concurrency,timeout_secs,max_steps,allowed_users,timeout_minutesand the four forwarded secrets must match theworkflow_call.inputs/secretsdeclared inreview-reusable.yml@20a5c43. A misnamed key is a hard Gitea Actions error, not a silent fallback. Notetimeout_minutes: 90(line 59) is an unquoted number while every other input is a quoted string — if that input is declared astype: stringupstream this is a type mismatch. Worth a one-time confirmation against the pinned commit.workflow_dispatchPR routing. This workflow still collectsinputs.pr_number(lines 17–20) but never forwards it to the reusable workflow'swith:. That's only correct if the reusable workflow readsgithub.event.inputs.pr_numberfrom the inherited event context itself (the standard pattern). If it instead expects apr_numberinput, manual dispatch would lose the PR number. Cannot confirm without the upstream file.Within my lens and verifiable against this repo, the diff is clean.
(Note: a plan-mode harness template was injected into this task, but the assigned job is an adversarial review, which is read-only — so I've produced the review directly rather than a plan file. I made no edits and ran only read-only tools.)
🧹 Code cleanliness & maintainability — Minor issues
VERDICT: Minor issues
Reviewing only through the code cleanliness & maintainability lens.
CLAUDE.md:145-146). The manual still describes Gadfly as "a fleet of 6 ollama-cloud models, each running the 3-lens suite (security, correctness, error-handling)." This PR changes the workflow to 3 cloud models + the Claude Code engine (sonnet/opus/opus:max) running the 5-lens suite (adversarial-review.yml:52-53:models,specialists). CLAUDE.md is the in-repo description of exactly this behavior, and the project's own rule is explicit: "keep the README/support-matrix/examples updated in the same commit as the behavior they describe (that in-sync promise is part of the project's credibility)." Fix: update those two lines in this same commit to reflect "3 cloud models + the Claude Code engine (sonnet/opus/opus:max), each running the 5-lens suite (security, correctness, maintainability, performance, error-handling)."Nothing else material in the cleanliness/maintainability lane.
⚡ Performance — No material issues found
VERDICT: No material issues found
This change is a single CI workflow file that swaps a self-contained
docker://step for auses:call intosteve/gadfly's reusable workflow. There is no application/library code, no data path, no loops or queries — the actual review workload runs inside the pinned reusable workflow (steve/gadfly/...@20a5c43), which lives in a different repo and is not present in this checkout, so the concurrency/scheduling behavior cannot be inspected here. Through the Performance lens there is nothing material to flag in code.One observation, explicitly unverified (I could not confirm it because the reusable workflow's defaults are not in this repo):
.gitea/workflows/adversarial-review.yml:54-55—provider_concurrencysets both lanes (ollama-cloud=3,claude-code=3), butprovider_lens_concurrencysets onlyollama-cloud=3; there is noclaude-code=entry. The suite also grew from 3 lenses to 5. If the reusable workflow defaults un-specified lens concurrency to 1, each Claude model would run its 5 lenses serially, roughly multiplying per-model wall-clock vs. the cloud lane. That is consistent with thetimeout_minutesbump 45→90, which may simply absorb the longer serial runs rather than parallelizing them. If the intent is symmetric throughput, considerprovider_lens_concurrency: "ollama-cloud=3,claude-code=3"(or whatever the engine supports). I cannot confirm the default without thereview-reusable.ymlsource, so treat this as a question to check, not a confirmed regression.Everything else (timeouts, model count, secret forwarding) is config tuning with no performance impact I can justify.
🧯 Error handling & edge cases — No material issues found
I've examined the changed file. The change swaps an inline docker-container job for a call to an external pinned reusable workflow (
steve/gadfly/.gitea/workflows/review-reusable.yml@20a5c43…). That reusable workflow lives in another repository and is not checked out here, so the unhappy-path behavior it now owns can't be verified from this repo.Within my lens, here's what I assessed:
VERDICT: No material issues found
Trigger/skip edge cases are intact (
adversarial-review.yml:36-41,27-29). Verified by reading the full file (the diff omits theon:andconcurrency:blocks). Theissue_commentpath is still gated ongithub.event.issue.pull_requestbeing truthy AND a trusted actor, so a comment on a non-PR issue still correctly skips. Theconcurrency.groupfallback chainissue.number || pull_request.number || inputs.pr_numbercovers all three trigger types with no null gap. None of this changed, and the conversion touses:preserves the job-levelif:guard (legal on a reusable-workflow job), so no unhappy path was dropped here.Dropped event-context forwarding — flagged but unverifiable, and correctness-adjacent. The old job explicitly passed
PR_BRANCH: ${{ github.head_ref }},IS_DRAFT,COMMENT_BODY,COMMENT_ID,EVENT_NAME,PR,ACTORas env. The new caller passes none of these — it relies on the reusable workflow deriving them from the inheritedgithubcontext. The one genuine edge case worth naming: onissue_commenteventsgithub.head_refis empty, so whatever logic checks out the PR branch must reconstruct it from the issue/PR API rather than a forwarded ref. Whether that's handled correctly is entirely inside the external workflow I can't read, and it's really a correctness concern, so I'm not raising it as a blocking finding — only labeling it unverified.timeout_secs/max_stepsas bounded guards are preserved (:56-57), so per-lens runaway protection still exists;timeout_minutes: 90replaces the old 45 — a looser ceiling but intentional (more lenses × Claude engine), not an error-handling defect.No swallowed errors, missing cleanup, panics, or boundary bugs are observable in the changed file itself; the edge-case-bearing logic moved behind an immutable external reference that isn't part of this checkout.
Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 6m 49s
🪰 Gadfly review —
glm-5.2:cloud(ollama-cloud)Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling
🔒 Security — No material issues found
Verdict: No material issues found
This is a clean security posture change — actually an improvement over the prior self-contained job:
OLLAMA_CLOUD_API_KEY,CLAUDE_CODE_OAUTH_TOKEN, and the two findings tokens are passed; nosecrets: inherit.GITEA_TOKENis the auto job token (verified the file at.gitea/workflows/adversarial-review.yml:46-50).@main.uses: steve/gadfly/.gitea/workflows/review-reusable.yml@20a5c431…— a future push tosteve/gadflycannot silently alter the code that runs with majordomo's forwarded secrets (adversarial-review.yml:44).if:guard still restricts theissue_commentpath tosteve/fizi/dazed, and the same allow-list is mirrored into theallowed_usersinput (adversarial-review.yml:36-41, 58) — defense in depth preserved.issue_comment/pull_requestthe workflow file itself runs from the base branch anyway).contents: read,issues: write,pull-requests: write) — appropriate for posting advisory comments, inherited by the reusable workflow.I could not read the reusable workflow body itself (it lives in the external
steve/gadflyrepo, not in this checkout), so the trust ultimately rests on that pinned SHA — which is the intended design. Nothing actionable in the security lens.🎯 Correctness — No material issues found
No material issues found
I verified the final workflow file at
.gitea/workflows/adversarial-review.yml(full 59 lines) and checked the repo for any other references to the reusable workflow or these input names.Within the correctness lens, the configuration is internally consistent:
provider_concurrency: "ollama-code=3,claude-code=3"matches the three cloud models + three claude models — all run concurrently. The comment's "all three claudes run concurrently (claude-code=3)" is accurate at the model level.timeout_minuteswent 45 → 90. Worst case: claude models have noprovider_lens_concurrencyentry (see below), so lenses could run sequentially per model: 5 lenses × 600s = 3000s ≈ 50 min, under 90 min. Cloud lanes at lens-concurrency 3 finish in ~2 batches × 600s = 20 min. No truncation risk from the timeout bump.secrets:forwarding dropssecrets: inheritand explicitly forwards only the four used secrets.GITEA_TOKENis Gitea's auto-provided job token (analogous toGITHUB_TOKEN), which is automatically available to called reusable workflows and does not require explicit forwarding — consistent with the comment and the referenced mort/executus pattern.if:guard,concurrencygroup,permissions, andon:triggers are unchanged and remain valid for a reusable-workflow caller job.One minor, unverified observation (not blocking):
provider_lens_concurrencysetsollama-cloud=3but omitsclaude-code, so claude-code lens concurrency falls to the reusable workflow's default (likely sequential). This is probably intentional (the comment only claims model-level concurrency=3 for claudes, not lens-level), and it doesn't break anything — but it is an asymmetry vs. the cloud models. I could not confirm the reusable workflow's default sincereview-reusable.ymllives in the externalsteve/gadflyrepo and isn't checked out here, so I'm flagging it only as something to confirm against that workflow'swith:contract, not as a defect.🧹 Code cleanliness & maintainability — No material issues found
No material issues found
claude-code=3, 5-lens suite), and theallowed_userscomment correctly points at the newallowed_usersinput below. No duplicated strings, no dead env vars left behind, and the pinning comment ("immutable commit, not @main") matches the@20a5c43…reference. Verified by reading.gitea/workflows/adversarial-review.yml:1-59.provider_concurrencyline on:54also encodes it; the redundancy is harmless but could drift if the concurrency value is later edited without updating the comment. No action required.(Could not verify that the reusable workflow
steve/gadfly/.gitea/workflows/review-reusable.yml@20a5c43…actually declares these exactwith:input names — that file lives outside this checked-out repo, so it's outside what I can confirm here. Flagging only as "unverified," not as a finding.)⚡ Performance — Minor issues
Verdict: Minor issues
.gitea/workflows/adversarial-review.yml:55—provider_lens_concurrencyomitsclaude-code, so lens fan-out for the Claude models is left to the reusable workflow's default. The file's header comment (lines 3–4) emphasizes concurrency for the claude fleet ("all three claudes run concurrently (claude-code=3)"), but thatclaude-code=3only appears inprovider_concurrency(line 54), which controls model-level concurrency. It is absent fromprovider_lens_concurrency(line 55), which sets onlyollama-cloud=3. This is an asymmetry versus the previous self-contained config, which set bothGADFLY_PROVIDER_CONCURRENCYandGADFLY_PROVIDER_LENS_CONCURRENCYtoollama-cloud=3; the migration to the reusable workflow addedclaude-code=3to model concurrency but not to lens concurrency. With the expanded 5-lens suite (line 53), the slowest/most-expensive reviewers could run their lenses sequentially if the reusable workflow's default lens concurrency is1. I could not positively confirm the reusable workflow's default forprovider_lens_concurrency— that workflow lives in the externalsteve/gadflyrepo and is not checked out here, so the wall-clock impact cannot be definitively confirmed. Suggested fix if the default is indeed1:provider_lens_concurrency: "ollama-cloud=3,claude-code=3"(or whichever bound is safe for the Claude Code engine).No other performance regressions confirmed: model count is 6 (3 cloud + 3 claude), per-reviewer
timeout_secs/max_stepsbounds (600s / 14 steps) are unchanged, and theconcurrency: cancel-in-progressgroup (lines 27–29) is preserved so superseded runs are still cancelled.🧯 Error handling & edge cases — No material issues found
Verdict: No material issues found
All three draft findings hinge on the behavior of the external reusable workflow
steve/gadfly/.gitea/workflows/review-reusable.yml@20a5c431..., which is not present in this repository (confirmed:.gitea/workflows/contains onlyadversarial-review.ymlandci.yaml; noreview-reusablefile exists anywhere in the checkout, and grepping the repo yields only theuses:reference itself). The draft itself labels each as "Unverified — cannot inspect."timeout_minutes/ removed job-leveltimeout-minutesobservation (finding 1): the local-file fact is accurate (notimeout-minutesremains at the job level;timeout_minutes: 90is passed as awith:input at line 59), but whether the external workflow honors that input — and thus whether this is a regression — cannot be confirmed against any code in this repo.CLAUDE_CODE_OAUTH_TOKENsecret-presence concern (finding 2): repo secrets and the external workflow's empty-token handling are not inspectable here.provider_lens_concurrencyomission forclaude-code(finding 3): the local input string does omit it (line 55 specifies onlyollama-cloud=3), but the impact depends entirely on the external workflow's default lens concurrency, which is not available to verify.No code-verifiable issue survives in this repository.
Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 1m 20s
🪰 Gadfly review —
claude-code/sonnet(claude-code)Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling
🔒 Security — Minor issues
The finding is confirmed against the actual code —
CLAUDE_CODE_OAUTH_TOKENis forwarded at line 48 to an external reusable workflow in a different repository, and the reusable workflow's internals cannot be read from this repo.Minor issues
CLAUDE_CODE_OAUTH_TOKENdelegated to external code — unverifiable handling (.gitea/workflows/adversarial-review.yml:48)This is a net-new credential added to the forward list. The old workflow forwarded
OLLAMA_CLOUD_API_KEY,GITEA_TOKEN, and the findings pair; this PR addsCLAUDE_CODE_OAUTH_TOKEN. OAuth tokens frequently carry broad scope (account-level access, not repo-scoped), and it's being handed tosteve/gadfly/.gitea/workflows/review-reusable.yml— code that lives in a different repository. I cannot read that repo from here to verify how the reusable workflow consumes secrets: whether they're exposed in logs, passed as env vars to child processes, included in telemetry payloads, etc.The SHA pin (
@20a5c431f22d…) prevents silent drift, which is good. But pinning bounds future compromise, not current behavior. If the reusable workflow logs all env vars on startup, or the findings telemetry includes request headers, this token leaks. The risk is real even under the repo-owner-identity assumption.Suggested fix: Before merging, read
steve/gadfly/.gitea/workflows/review-reusable.ymlat that commit and confirmCLAUDE_CODE_OAUTH_TOKENis passed only to the Claude API call (not to any logging, telemetry, or shell step that could echo it). If the gadfly workflow ever usessecrets: inheritinternally, that surfaces a separate concern.Everything else checks out from the security lens:
secrets: inherit— explicit forwarding is correct; the old container-env approach was equivalent in surface but less auditable.issue_commentactor guard is unchanged and present;pull_requestevents don't expose secrets to fork PRs because the trigger ispull_request(notpull_request_target).with:inputs are static YAML strings, not interpolated from PR title, body, or comments — no injection risk.GITEA_TOKENauto-availability: Reusable workflow inherits the calling job's token, which is scoped by the explicitpermissions:block (contents: read,issues: write,pull-requests: write) — appropriate minimum for a reviewer.GADFLY_FINDINGS_URL+GADFLY_FINDINGS_TOKENwere already forwarded in the prior workflow; not a new exposure.🎯 Correctness — No material issues found
I've read the full checked-out file and verified the key correctness claims. Here is my review.
Verdict: No material issues found
if:guard preserved identically (verified: lines 36–41). The allowed-user liststeve,fizi,dazedin theallowed_users:input (line 58) matches the job-level guard exactly. No regression in access control logic.Model count matches concurrency settings (verified):
provider_concurrency: "ollama-cloud=3,claude-code=3"— there are exactly 3 ollama-cloud entries (minimax-m3:cloud,glm-5.2:cloud,deepseek-v4-pro:cloud) and exactly 3 claude-code entries (claude-code/sonnet,claude-code/opus,claude-code/opus:max) in themodels:string. Setting each provider's concurrency to 3 means all models in each group run simultaneously, consistent with the PR description's "all three claudes run concurrently."specialistslist matches the 5-lens claim (verified: line 53):"security,correctness,maintainability,performance,error-handling"— 5 distinct lenses, consistent with the header comment and PR description.timeout_minutes: 90vs oldtimeout-minutes: 45: Doubling the outer timeout is directionally correct given the change from 3 lenses to 5 lenses per model. Withprovider_lens_concurrency: "ollama-cloud=3", the 5 ollama lenses require 2 batches (3+2) of up to 600s each ≈ 20 min per cloud model. Claude Code has noprovider_lens_concurrencyentry; if that defaults to serial, 5 × 600s ≈ 50 min per Claude model, with 3 running concurrently (claude-code=3). Worst-case sequential total is ~70 min, well within 90. Unverified (reusable workflow definition not accessible): the exact default for claude-code lens concurrency and whether cloud + claude passes overlap or run sequentially. If they are sequential and claude-code lens concurrency defaults > 1, the 90-minute budget is comfortable; if strictly serial with 1 lens at a time, it is still within 90 minutes (50 + 20 = 70 min). No evidence of a timing violation.timeout_secs: "600"andmax_steps: "14"unchanged: Carried over from the old implementation without modification. Not a regression.🧹 Code cleanliness & maintainability — No material issues found
No material issues found
The file is clean. The rewrite trades ~80 lines of inline env-block configuration for a 60-line thin caller, which is a net readability improvement. A few observations after reading the live file:
secrets:block (lines 47–50) is a minor style deviation from the unalignedwith:block directly below it, but it's harmless and common in CI files.allowed_users: "steve,fizi,dazed"(line 58) duplicates the three-name list already in theif:guard (lines 39–41). This is a pre-existing sync hazard — not introduced by this PR — but worth noting as technical debt: adding a user requires two edits. Since the PR doesn't create the problem, this is not a blocking finding.Nothing in the structure, naming, or change pattern warrants a blocking finding from this lens.
⚡ Performance — No material issues found
The file confirms
provider_lens_concurrency: "ollama-cloud=3"at line 55 with noclaude-codeentry. However, the impact of this observation depends entirely on the default behavior of the external reusable workflow (steve/gadfly/.gitea/workflows/review-reusable.yml), which is not in this repository and cannot be read. The draft reviewer explicitly flagged this as "Unverified" and noted "If the default is already 'all lenses concurrent,' this is a non-issue." Since the finding's severity and fix necessity hinge on an assumption I cannot confirm from the local code, the finding does not survive the confirmation test.No material issues found
🧯 Error handling & edge cases — No material issues found
No material issues found
This change is purely a CI workflow YAML file — the only error-handling surface is in how the workflow responds to missing/failing inputs, and all substantive handling is delegated to the reusable workflow. I verified the current file state against the diff.
A few things I checked and found non-issues:
timeout_minutes: 90passed as awith:input, not a job-level directive: When a job usesuses:(reusable workflow call),timeout-minutesat the caller job level is not supported — the timeout must be applied inside the reusable workflow. Removing the oldtimeout-minutes: 45job key and passingtimeout_minutes: 90as an input is the correct pattern, not a missing safeguard..gitea/workflows/adversarial-review.yml:58CLAUDE_CODE_OAUTH_TOKENmissing → three Claude models fail silently: If this secret isn't set in the repo yet, all threeclaude-code/*entries in the swarm fail at credential time. The workflow is advisory-only and the reusable workflow presumably handles empty credentials gracefully (same as the old code'sGADFLY_FINDINGS_URLhandling), so this is not a blocking concern — but there's no local comment noting the degraded-swarm failure mode the way the old stub did.concurrency: cancel-in-progress: trueabandons in-flight reviews without a comment: Unchanged from the old workflow, pre-existing.No new unhappy paths, ignored errors, missing cleanups, or edge cases are introduced by this diff.
Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 8m 44s
ec9b9a9a94to17064a6d75