SIGN IN SIGN UP

fix(analyzer): workflow-permission push handling + gpt-5 max_completion_tokens (#5)

* fix(analyzer): handle workflow-permission push rejection and gpt-5 params

Second end-to-end run succeeded through passes 1-3 and batch building
(60 GOOD / 14 NEEDS_REVIEW / 5 SLOP cherry-picked), then failed at
push with:

  ! [remote rejected] sync/upstream/v3.17.4-good -> sync/upstream/v3.17.4-good
    (refusing to allow a GitHub App to create or update workflow
     .github/workflows/publish-platform.yml without `workflows` permission)

GitHub's default GITHUB_TOKEN cannot push commits that modify files
under .github/workflows/, which upstream frequently touches. The whole
pipeline exited non-zero even though every other stage succeeded.

Also surfaced during run: gpt-5-mini rejected max_tokens with
"Unsupported parameter: use max_completion_tokens instead" — API
contract that differs from gpt-4.x.

Changes:

1. pushBranch() returns PushOutcome discriminated union
   ("ok" | "blocked-by-workflow-permission" | "failed") instead of
   throwing. The pipeline records the outcome on each BatchResult
   without aborting subsequent pushes.

2. New commitTouchesWorkflow() inspection flags commits that modify
   .github/workflows/*. Each batch now tracks these shas so the
   analysis issue can call them out as "needs elevated permissions to
   sync".

3. Issue body surfaces push outcome per batch:
     - Conflict note (existing)
     - Push blocked by workflow permission (new)
     - Workflow-touching commit list (new)

4. PR specs skip batches whose push failed, so createPR doesn't
   attempt to open a PR against a non-existent remote branch.

5. tokenFieldForModel() picks max_completion_tokens for gpt-5 / o-
   series, max_tokens for everything else. Temperature is also
   omitted for the reasoning models since they reject it. Prevents
   the first fallback attempt from wasting a request on a 400.

6. resetWorkingTree() no longer runs `git clean -fdx`; only
   `git reset --hard HEAD` on tracked files. This guarantees the
   .analyzer-output/ directory survives (prior runs showed it was
   being removed during reset), so artifacts are always available
   even on crash.

7. isContextLengthError() narrowed to avoid false positives on the
   old max_tokens error message.

Expected behavior on next run: pipeline completes, issue is created
with push-outcome notes per batch, good-batch draft PR opens unless
all its commits touch workflow files (in which case the issue flags
it for manual sync).

* fix(analyzer): address PR #5 review findings

Four issues surfaced by Copilot + Augment reviewers on PR #5:

1. PUSH_BRANCHES=false → PR creation still attempted (medium)
   When pushBranches is false, pushOutcome stays undefined. The prior
   skip logic treated undefined as success, so PRs were attempted for
   branches that never reached origin. Plumb pushAttempted through
   PipelineResult → buildWorkflowArtifacts → skipped calculation.
   PR now skipped unless pushAttempted && pushOutcome === "ok".

2. tokenFieldForModel matched only prefixed names
   /\/(gpt-5|o[0-9])/i missed bare "gpt-5-mini" (no provider prefix).
   MODEL_* env vars are not validated, so a user could easily pass an
   unprefixed name and get a 400. Broaden to /(^|\/)(gpt-5|o\d+)/i.

3. batches.json written before pushBatches() mutates it
   Persisted artifact was always missing pushOutcome. Move the write
   after pushIfRequested so the on-disk record is consistent with
   pipeline-result.json.

4. Branch: label in issue body misleading when push skipped
   Previously rendered "Branch: <name>" identically whether the branch
   was on origin or only in the runner. Now appends "(on origin)" or
   "(local only — not on origin)" and adds an explicit "push was not
   attempted" note when push_branches=false.

Type surface:
  * PipelineResult now has pushAttempted: boolean
  * IssueReportInput now takes pushAttempted: boolean
  * renderBatchSection / renderBatchNotes take pushAttempted

* fix(analyzer): remove em dashes from generated issue content

AGENTS.md anti-patterns: "Never use em dashes, en dashes, or AI filler
phrases in generated content." The upstream-analyzer produces GitHub
issue bodies and PR bodies, which are generated content and fall under
the ban. Flagged by Devin review on PR #5.

Replace 5 em dashes (U+2014) with semantically appropriate ASCII:
  * "subject — reason" -> "subject -- reason" (commit summary lines)
  * "local only — not on origin" -> "local only; not on origin"
  * "rejected — GITHUB_TOKEN cannot..." -> "rejected. GITHUB_TOKEN..."
  * "(delta) — reasoning" -> "(delta): reasoning"
  * "advisory — no merges" -> "advisory: no merges"

Right-arrow (U+2192 →) is retained; it's semantic (from→to) and not
covered by the em/en dash ban.
V
Vacbo committed
9c5361f5dcc43af9495a38cfc9e8cf94cb51b358
Parent: f95402c
Committed by GitHub <noreply@github.com> on 4/17/2026, 6:06:59 PM