Skip to content

fix: chain Speakeasy auto-merge into Generate run#364

Merged
christineschen merged 2 commits into
mainfrom
fix/port-auto-merge-workflow-call
Jun 25, 2026
Merged

fix: chain Speakeasy auto-merge into Generate run#364
christineschen merged 2 commits into
mainfrom
fix/port-auto-merge-workflow-call

Conversation

@christineschen

Copy link
Copy Markdown
Collaborator

Problem

Speakeasy regen PRs (speakeasy-sdk-regen-*) pile up unmerged — 4 currently stuck (#347/#349/#352/#357), all wanting to ship 0.10.1, while PyPI is frozen at 0.10.0 (Jun 17).

Root cause: auto-merge-speakeasy-pr.yaml triggered on pull_request: [labeled, opened]. When github-actions[bot] opens/labels the regen PR, GitHub's recursion guard refuses to run a workflow triggered by another workflow's GITHUB_TOKEN. The runs land as action_required with zero jobs executed (e.g. runs 28067963505, 28032618517) — so the App-token logic inside the job never even gets a chance to run.

Fix

Port the TypeScript SDK's working architecture: make auto-merge a workflow_call reusable workflow chained inside the Generate run (generate → resolve-branch → auto-merge). No separate pull_request-triggered run exists, so there is nothing for GitHub to gate.

  • auto-merge-speakeasy-pr.yamlworkflow_call with inputs (run_started_at, branch_name) and App-token secrets. Resolves the regen PR by branch (with timestamp fallback), checks .author.is_bot == true (robust to the app/github-actions author change), gates direct merges behind wait_for_checks(), writes step summaries. Keeps the SHA-pinned create-github-app-token.
  • sdk_generation.yaml / sdk_generation_for_spec_change.yaml → add concurrency group plus resolve-branch + auto-merge jobs.

Result

Once merged, the next Generate run merges the fresh regen PR and closes #347/#349/#352/#357 as superseded → gen.lock lands on mainsdk_publish.yaml ships 0.10.1.

Verification

  • YAML parses clean (all 3 files).
  • actionlint: reusable workflow passes; the lone github.run_started_at warning is identical in the already-working TS workflow (stale linter schema, valid property, has an API fallback) — not introduced here.
  • Diffed each file against its TS counterpart: only intended differences (pypi_token vs npm_token, SHA pin vs @v3, no pnpm_version, workflow_dispatch clause in spec-change if:).

The auto-merge workflow triggered on `pull_request: [labeled, opened]`.
When github-actions[bot] opened the regen PR, GitHub's recursion guard
refused to run a workflow triggered by another workflow's GITHUB_TOKEN,
so the run landed as action_required with zero jobs executed. Regen PRs
piled up unmerged and sdk_publish never fired (PyPI stuck at 0.10.0).

Port the TypeScript SDK's architecture: make auto-merge a workflow_call
reusable workflow chained inside the Generate run (generate ->
resolve-branch -> auto-merge). No separate pull_request-triggered run
exists, so there is nothing for GitHub to gate.

- auto-merge-speakeasy-pr.yaml: workflow_call inputs (run_started_at,
  branch_name) + App-token secrets; resolve PR via branch/timestamp;
  .author.is_bot check; wait_for_checks before direct merge; step
  summaries. Keeps the SHA-pinned create-github-app-token.
- sdk_generation.yaml / sdk_generation_for_spec_change.yaml: add
  concurrency group, resolve-branch + auto-merge jobs.
perry-the-pr-reviewer[bot]

This comment was marked as outdated.

Speakeasy regen PRs are opened with the workflow GITHUB_TOKEN, which by
design does not trigger downstream on: pull_request runs, so they carry
zero status checks. main has no required checks either, so gh pr merge
--auto no-ops and the direct-merge fallback calls wait_for_checks.

The empty-rollup arm only `continue`d, so TOTAL stayed 0 until the 600s
timeout and the function exit 1'd — failing auto-merge on every regen PR.
Add a 60s grace window for checks to register, then treat a persistently
empty rollup as "no checks to wait for" and return 0 so the direct squash
merge proceeds. Matches the fix already shipped in go-sdk #318.

@perry-the-pr-reviewer perry-the-pr-reviewer 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.

⚠️ APPROVE unavailable on this installation — the maintainer GitHub App lacks
pull_requests: write on OpenRouterTeam, so the ✅ LGTM verdict below is posted
as COMMENT. Event-level approval (for branch-protection) must be added out-of-band.

Perry's Review

Incremental re-review: this push adds a 60s grace window to the wait_for_checks empty-rollup arm so checkless Speakeasy regen PRs proceed to the direct squash-merge instead of burning the full 600s timeout and failing.

Verdict: ✅ LGTM

Details

Risk: 🟢 Low — CI/CD workflow plumbing only; no production SDK code, no secrets added.

CI: no pull_request checks registered on this PR — main carries no required checks (consistent with prior empirical finding).

Scope: incremental — 7 new lines since prior review.

Findings: my only prior open finding is resolved by this push:

  • ✅ The empty-rollup arm now returns 0 after a 60s grace window instead of looping to the 600s timeout and exiting 1. This is exactly the remedy I suggested at head 186c107 (matches the go-sdk #318 fix). One positive nit on the magic-number literal — see inline.

Blast radius: both reusable-workflow callers (the two Generate workflows) correctly invoke the now-workflow_call target; no other workflow still uses the old labeled/opened trigger for regen PRs.

Security: no concerns — no credentials added; the App-token plumbing is unchanged from the prior reviewed head.

Test coverage: N/A — GitHub Actions YAML; validated by reading the function control flow.

Unresolved threads: the prior wait_for_checks suggestion thread is now addressed by this push.

Review: tier=medium · model=claude-opus-latest · score=3.1

# GITHUB_TOKEN-authored regen PRs get no pull_request checks at all,
# so TOTAL stays 0 forever. Wait a short grace window in case checks
# are merely slow to register, then proceed rather than time out.
if [ "$elapsed" -ge 60 ]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] the grace threshold 60 is an inline literal; hoisting it into a named local (e.g. local empty_grace=60) alongside timeout/interval would document intent and keep the wait-tuning knobs in one place. Resolves my prior suggestion — thanks for adding the grace window.

Reviewed at 0680f01

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