ci(stlc): add the stlc codegen workflow and setup action - #438
aringuyen3 wants to merge 4 commits into
Conversation
The existing seal was scoped to branch aringuyen/stainless, whose integrated SHA is not reachable from main, so stlc reported no integration for main at all. This records the seal produced by a real build against the reconciled main (base 61f04722, integrated b53e3f16), which matches scale-agentex-typescript-staging main on origin. Python's equivalent is deliberately not included: its build integrated cleanly but the push to staging main is rejected (GH006, protected branch, MAINTAIN access only), so its tracking file would record a commit that exists in no remote -- the state stlc refuses to build from later.
Adds the two CI files that drive SDK generation from the stainless/ workspace: a guard + generate workflow, and a composite action that installs the stlc CLI family. Both are derived from upstream stainless/stlc's examples/config-repo, with these changes for this repo: - paths: watch agentex/openapi.yaml in BOTH the pull_request and push filters. The spec lives outside the workspace, and the copied file listed it in neither push nor a valid glob -- so merges to main would never have built. Deliberately not agentex/**: the spec is a committed artifact regenerated by the pre-commit hook and freshness-checked by ci.yml, so watching the whole backend tree would fire full SDK builds on changes that cannot affect the SDK. - Guard the generate job against fork PRs. This repo is public and does receive them; forks get no secrets, so the job would fail with a red check an external contributor can neither understand nor fix. Not fixed with pull_request_target, which would run fork code with full secrets in a workflow that pushes to four other repos. - Replace marocchino/sticky-pull-request-comment with actions/github-script, which this repo's Actions allowlist permits. Same update-in-place behavior, keyed on the marker stlc show --marker emits. - Trim setup-stlc from ten language toolchains to Node + uv, and from twelve stlc packages to the three this workspace needs. uv rather than rye: the python target resolves to use_uv, and every generated script shells out to uv. astral-sh/setup-uv pinned to @v4, the version the allowlist permits. DEFAULT_TARGETS is temporarily typescript-only -- see the comment in the workflow. Python is reconciled and builds cleanly, but pushing to its staging main is rejected until that repo grants ADMIN. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| const comments = await github.paginate(github.rest.issues.listComments, { | ||
| owner, repo, issue_number, per_page: 100, | ||
| }); | ||
| const mine = comments.find((c) => c.body && c.body.includes(marker)); | ||
| if (mine) { | ||
| await github.rest.issues.updateComment({ owner, repo, comment_id: mine.id, body }); |
There was a problem hiding this comment.
The lookup treats the first comment containing the public marker as workflow-owned. It never checks the author, so a matching user comment can be replaced with the manifest. Match the marker at the start and require the expected bot author before calling
updateComment.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/stlc-generate.yml
Line: 296-301
Comment:
The lookup treats the first comment containing the public marker as workflow-owned. It never checks the author, so a matching user comment can be replaced with the manifest. Match the marker at the start and require the expected bot author before calling `updateComment`.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
✱ stlc build⚡ python code · compare
⚡ typescript code · compare
Diagnostics: 💡 29 note
Build metadata
This comment is auto-generated by stlc and is kept up to date as you push. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ADMIN on scale-agentex-python-staging landed, so the push that was rejected with GH006 now succeeds (enforce_admins is false there, same as typescript). A real `stlc build --push --branch main` integrated and pushed python: staging main is 567abffb, matching the seal's integrated SHA. Both targets' tracking files now point at branch=main and at commits that exist on origin. The one conflict was pyproject.toml, resolved as before: take HEAD's version (0.28.0 -- release-please owns it) and keep the custom description, which is what production actually ships. Taking HEAD's version also drops the version line out of the sealed delta, so it stops re-conflicting on every release. Restores DEFAULT_TARGETS to python,typescript; the temporary typescript-only setting has served its purpose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nt matching Three review findings on #438, all valid. 1. The hold loop read every target in stainless.yml while the build uses --targets "$DEFAULT_TARGETS", which may be narrower. A production repo ahead for a target we are NOT building would still exit 1 and block the ones we are. This was live an hour ago, when DEFAULT_TARGETS was typescript-only and an ahead python production would have blocked typescript. Now filtered, with `all` still meaning no filter, and comma-anchored so "typescript" does not match a target named "py". 2. BRANCH was `github.event.pull_request.head.ref || github.ref_name`. On workflow_dispatch the operator picks the ref, so ref_name would send a feature branch through the non-PR path: pushing an SDK preview branch instead of syncing the trunk, and then letting the seal-back step raise a tracking-file PR against config main derived from that preview. Both BRANCH and the checkout are now pinned to main outside pull requests. On pull_request, `github.ref` is the merge ref, which is the default checkout, so PR behavior is unchanged. 3. The manifest comment lookup matched `includes(marker)` on any author. The marker is public, so a human comment quoting the manifest could be silently overwritten. Now requires the comment be authored by a Bot -- a type a user account cannot spoof -- and anchors the marker at the start, which is where the renderer emits it. Verified: the target filter against four DEFAULT_TARGETS values plus the substring edge case, and the comment matcher against eight shapes including a human quoting the marker and a human comment preceding the bot's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follows #433, which landed the
stainless/workspace. This adds the CI that actually drivesgeneration from it.
What's here
.github/workflows/stlc-generate.ymlguardloop-breaker plus ageneratejob: preview on PRs, push-to-staging on merge tomain, and a scheduled custom-code tracking sync.github/actions/setup-stlc/action.ymlstlcCLI familystainless/custom-code/typescript/2026-09-21T...jsonmainBoth workflow files derive from upstream
stainless/stlc'sexamples/config-repo. The changesbelow are specific to this repo, and each one is load-bearing.
Changes from upstream, and why
pathswatchesagentex/openapi.yamlin both thepull_requestandpushfilters. Ourspec lives outside the workspace, and the copied file had it in neither list in a valid form — so
merges to
mainwould never have triggered a build. Deliberately notagentex/**: the spec isa committed artifact regenerated by the pre-commit hook and freshness-checked by
ci.yml, sowatching the whole backend tree would fire full SDK builds on changes that cannot affect the SDK.
The
generatejob is guarded against fork PRs. This repo is public and does receive them(five of the last thirty). Forks get no secrets, so the job would fail with a red check an
external contributor can neither understand nor fix. Not solved with
pull_request_target—that runs fork-authored code with full secrets in a workflow that pushes to four other repos.
marocchino/sticky-pull-request-commentreplaced withactions/github-script@v7. The formeris not on this repo's Actions allowlist (
allowed_actions: selected,verified_allowed: false),so the first preview run would have failed at the comment step. Same update-in-place behavior,
keyed on the marker
stlc show --markeremits.setup-stlctrimmed from ten language toolchains to two, and twelve stlc packages to three.Only python and typescript are configured. The trim also removes three
uses:the allowlistwould have blocked. It installs uv, not rye — this python target resolves to
use_uv(the generated SDK ships
uv.lock, nonoxfile.py, and everyscripts/*shells out touv).astral-sh/setup-uvis pinned to@v4, the version the allowlist permits.DEFAULT_TARGETSis temporarily typescript-onlyPython is fully reconciled and its build integrates cleanly, but pushing to
scale-agentex-python-staging's protectedmainis rejected (GH006) because we hold onlyMAINTAIN there. Including it would only produce red runs. Restore
python,typescriptonce thatrepo grants ADMIN — no other change needed. The comment in the workflow says so.
For the same reason python's re-anchored tracking file is not in this PR: it records a
commit that exists in no remote, which is the state
stlcrefuses to build from later.Before merging
STLC_READ_TOKENandSDK_WRITE_TOKENshould exist as repo secrets first, or the firstpush-to-
mainrun fails atSetup stlcwithout telling you anything useful.The
generatecheck on this PR is red, and that is expected right now. It fails atSetup stlcwithInvalid username or tokenbecauseSTLC_READ_TOKENdoes not exist yet — theinstall URL resolves to
https://x-access-token@github.com/...with an empty token. It goesgreen once the secret is set.
(I originally wrote here that this PR would not trigger a run at all. That was wrong:
pull_requestevaluates the workflow from the PR's merge commit, so a workflow added by the PRdoes run on it.)
This PR is not ready to merge until the private repo and credential details are removed from public files.
Fix with agent prompt
Summary
This PR adds the CI workflow that installs
stlc, builds Python and TypeScript SDKs from thestainless/workspace, and pushes the results to staging repositories. It also keeps custom-code tracking files synced and shows pull-request build results in a comment.stlctools and SDK dependencies.Diagram
sequenceDiagram participant E as GitHub event participant G as guard participant W as generate job participant S as SDK repos participant C as Config repo E->>G: PR, main push, schedule, or dispatch G->>G: Check for tracking-only changes alt Tracking-only change or fork PR G-->>W: Skip generation else Pull request G->>W: Run on PR merge ref W->>S: Push preview branches W->>E: Create or update manifest comment else Main or sync run G->>W: Check out main W->>S: Build selected targets and push main W->>S: Check pushed seals W->>C: Open and merge seal-tracking PR endReviews (3) · Last reviewed commit: "fix(ci): scope the codegen hold, pin the..."