Skip to content

ci(stlc): add the stlc codegen workflow and setup action - #438

Open
aringuyen3 wants to merge 4 commits into
mainfrom
aringuyen/stlc-ci
Open

aringuyen3 wants to merge 4 commits into
mainfrom
aringuyen/stlc-ci

Conversation

@aringuyen3

@aringuyen3 aringuyen3 commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Follows #433, which landed the stainless/ workspace. This adds the CI that actually drives
generation from it.

What's here

File
.github/workflows/stlc-generate.yml A guard loop-breaker plus a generate job: preview on PRs, push-to-staging on merge to main, and a scheduled custom-code tracking sync
.github/actions/setup-stlc/action.yml Composite action installing the stlc CLI family
stainless/custom-code/typescript/2026-09-21T...json Re-anchors the typescript seal to main

Both workflow files derive from upstream stainless/stlc's examples/config-repo. The changes
below are specific to this repo, and each one is load-bearing.

Changes from upstream, and why

paths watches agentex/openapi.yaml in both the pull_request and push filters. Our
spec lives outside the workspace, and the copied file had it in neither list in a valid form — so
merges to main would never have triggered a build. 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.

The generate job 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-comment replaced with actions/github-script@v7. The former
is 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 --marker emits.

setup-stlc trimmed 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 allowlist
would have blocked. It installs uv, not rye — this python target resolves to use_uv
(the generated SDK ships uv.lock, no noxfile.py, and every scripts/* shells out to uv).
astral-sh/setup-uv is pinned to @v4, the version the allowlist permits.

DEFAULT_TARGETS is temporarily typescript-only

Python is fully reconciled and its build integrates cleanly, but pushing to
scale-agentex-python-staging's protected main is rejected (GH006) because we hold only
MAINTAIN there. Including it would only produce red runs. Restore python,typescript once that
repo 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 stlc refuses to build from later.

Before merging

STLC_READ_TOKEN and SDK_WRITE_TOKEN should exist as repo secrets first, or the first
push-to-main run fails at Setup stlc without telling you anything useful.

The generate check on this PR is red, and that is expected right now. It fails at
Setup stlc with Invalid username or token because STLC_READ_TOKEN does not exist yet — the
install URL resolves to https://x-access-token@github.com/... with an empty token. It goes
green once the secret is set.

(I originally wrote here that this PR would not trigger a run at all. That was wrong:
pull_request evaluates the workflow from the PR's merge commit, so a workflow added by the PR
does run on it.)

RetriggerConfidence Score: 4/5

This PR is not ready to merge until the private repo and credential details are removed from public files.

Fix All in CursorFindings

  1. P2 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 .
Fix with agent prompt
### Issue 1
.github/workflows/stlc-generate.yml:312-327
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.

Summary

This PR adds the CI workflow that installs stlc, builds Python and TypeScript SDKs from the stainless/ 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.

  • Watches the API spec and workspace changes.
  • Builds preview branches for pull requests and staging branches for main and sync runs.
  • Installs the required stlc tools and SDK dependencies.
  • Updates custom-code tracking through a seal-back pull request.
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
    end
Loading

Reviews (3) · Last reviewed commit: "fix(ci): scope the codegen hold, pin the..."

aringuyen3 and others added 2 commits September 21, 2026 12:55
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>
@aringuyen3
aringuyen3 requested a review from a team as a code owner September 21, 2026 19:56
Comment thread .github/workflows/stlc-generate.yml
Comment thread .github/workflows/stlc-generate.yml Outdated
Comment on lines +296 to +301
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 });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 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.

Fix in Cursor Fix in Claude Code Fix in Codex

@github-actions

github-actions Bot commented Sep 21, 2026

Copy link
Copy Markdown

✱ stlc build

python code · compare

Your SDK build resulted in a merge conflict between your custom code and the newly generated changes. Run stlc build --continue after resolving conflicts.

generate ⚡

typescript code · compare

Your SDK build resulted in a merge conflict between your custom code and the newly generated changes. Run stlc build --continue after resolving conflicts.

generate ⚡

Diagnostics: 💡 29 note
LevelCodeMessageTargets
💡 noteSchema/IsAmbiguousMissing type for schema
#/paths//agents/forward/name/{agent_name}/{path}/get/responses/200/content/application/json/schema
python, typescript
💡 noteSchema/IsAmbiguousMissing type for schema
#/paths//agents/forward/name/{agent_name}/{path}/post/responses/200/content/application/json/schema
python, typescript
💡 noteSchema/IsAmbiguousMissing type for schema
#/paths//tasks/{task_id}/stream/get/responses/200/content/application/json/schema
python, typescript
💡 noteSchema/IsAmbiguousMissing type for schema
#/paths//tasks/name/{task_name}/stream/get/responses/200/content/application/json/schema
python, typescript
💡 noteSchema/IsAmbiguousMissing type for schema
#/paths//slack/commands/post/responses/200/content/application/json/schema
python, typescript
💡 noteSchema/IsAmbiguousMissing type for schema
#/paths//slack/interactions/post/responses/200/content/application/json/schema
python, typescript
💡 noteSchema/IsAmbiguousMissing type for schema
#/paths//integrations/slack/link/get/responses/200/content/application/json/schema
python, typescript
💡 noteSchema/IsAmbiguousMissing type for schema
#/paths//integrations/slack/link/post/responses/200/content/application/json/schema
python, typescript
💡 noteEndpoint/IsIgnored`post /agents/register` is in `unspecified_endpoints`, so code will not be
post /agents/register
python, typescript
💡 noteEndpoint/IsIgnored`get /agents/forward/name/{agent_name}/{path}` is in `unspecified_endpoints`, so code will not be
get /agents/forward/name/{agent_name}/{path}
python, typescript
…19 more note diagnostics omitted.
Build metadata
Buildbd_76PDNwlc-fond-dill
Timestamp2026-09-22T01:56:50.903Z
stlc021fb13
Spec hash4ecd8d496f05
Config hashdad01409eebd

This comment is auto-generated by stlc and is kept up to date as you push.
If you push new commits, re-run this workflow to update this comment.
Last updated: 2026-09-22 01:56:55 UTC

aringuyen3 and others added 2 commits September 21, 2026 14:58
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>
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