Skip to content

🤖 tests: resolve the sandbox image by stable tag and attestation, else build locally - #7672

Merged
ThomasK33 merged 5 commits into
mainfrom
tests/sandbox-image-runner-upsert
Oct 27, 2026
Merged

ThomasK33 merged 5 commits into
mainfrom
tests/sandbox-image-runner-upsert

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

Every sandbox job now gets its image without a pin. In CI and on a developer machine, the runner looks up the stable tag ghcr.io/coder/xum-bugbash-sandbox:inputs-<key> for its checkout's inputs key. It checks the tag's build attestation with gh, and it uses the image only if main's publish workflow built it. In every other case it builds the image from its own checkout, about 40 s in CI, and warns. tests/bugbash/sandbox/image.json, the BUGBASH_SANDBOX_BUILD_STALE opt-in and the job-kind gate go away. With this PR, an image change takes one normal PR: no manual publish, no pin PR and no stale-key refusal. This is PR 2 of 2. It is stacked on #7671, which makes main publish the stable tag.

Background

The user approved the plan "Sandbox Image Upsert in CI". Today the runner trusts only the digest in image.json and refuses a checkout whose key differs from it. Only the self-check and the exact-step repros can build locally, and only in CI. So a Dockerfile change broke the story lanes until a person published the image and pinned it. After #7362, that kept the merge queue red for 3.5 hours.

Implementation

Session.ensureImage() (tests/bugbash/sandbox/runner.ts) resolves the image in this order:

  1. It looks up the stable tag anonymously, through the runner's private Docker client config, with docker buildx imagetools inspect.
  2. If the tag exists, it runs gh attestation verify oci://IMAGE@digest --repo coder/xum --bundle-from-oci --source-ref refs/heads/main --signer-workflow coder/xum/.github/workflows/bugbash-sandbox-image.yml on the host. gh gets only PATH, HOME, GH_TOKEN and GH_CONFIG_DIR. None of them reaches Docker or the job container.
  3. If the check passes, the runner uses IMAGE@digest and pulls it when Docker does not have it.
  4. In every other case, the runner logs the reason and reuses or builds the local image with build.sh: a missing tag, a lookup error, a bad digest, no gh, no gh token, a failed check or a timeout.
  5. The label check (org.xum.bugbash.inputs must equal the key) still runs for both kinds of image. It still refuses on a mismatch.

The launcher prints the LOCAL IMAGE warning with the reason, as a ::warning:: in Actions, for every job kind.

A local build gives a job no new power. Each job already stages its checkout's git-listed files and mounts its dist/ and node_modules/ in the container. So a job runs either an image that main's workflow built and attested, or one built from its own checkout. A registry tag that someone else pushed is never used.

CI gives the launcher steps GH_TOKEN: ${{ github.token }}: the story lanes in stories.yml and both steps of Test / Bug-bash sandbox in pr.yml. This PR changes build.sh and the Dockerfile, which are comment-only changes, so its key changes. Its own CI therefore runs the local-build path in every sandbox job.

Validation

  • bun test ./tests/bugbash ./tests/bugbash/sandbox: 356 pass.
  • Each path in ensureImage has a test in runner.test.ts. The implementer broke each path in turn and saw its test fail. That was 10 breaks, plus 3 for the env allowlist.
  • GH_TOKEN and GH_CONFIG_DIR reach the fake gh. They never reach the fake docker or the docker run line.
  • make static-check, make lint-eslint, make lint-zizmor, actionlint and make crap-check: pass.
  • Real Docker, $0, no model: make bug-bash-sandbox-check passed, 54 of 54. It built the image once for the new key and reused it for all 3 jobs, with one warning per job ("… is not published"). It passed again with no gh token.
  • gh with the runner's exact arguments and env:
    • The current published digest: exit 0.
    • --source-ref refs/heads/nope: exit 1.
    • No login: exit 4, which the runner reports as "no gh token".

Still to do after #7671 lands and publishes inputs-2bf9…: run the self-check from a checkout with this runner and main's key, and show that it pulls and verifies the image with no build. After this PR merges, its new key gets published, and the next PR's sandbox jobs must pull that image without a build. I will link both runs here.

Risks

  • I did not verify that the job token is enough for gh attestation verify --bundle-from-oci in CI. If it is not, CI builds locally with a warning, about 40 s per job, and does not fail. I will check the first CI logs.
  • The preflight of make bug-bash, make mcp-apps-e2e and make test-stories now builds the image when no verified one exists. That can take up to 25 minutes. Before, it refused.
  • If a stable tag ever named a digest without main's attestation, every job would build locally and warn until a person deleted that tag.

Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: xhigh • Cost: $266.91

@ThomasK33
ThomasK33 added this pull request to stack #7673 October 27, 2026 09:14
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-27T16:35:33.227343Z — Manual request
🔒 Security Review ✅ Completed 2026-10-27T16:34:37.521206Z d840897 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

🟢 e2e stories (Haiku): 14 passed

22 agent steps · 7 replayed from cache · 15 model calls · 46.3k tokens (28% cached)
Spend: $0.08 of the $1.00 cap (proxy ledger)

All 14 tests in 9 files
Test Agent Time
🟢 chatRestart.e2e.ts · 1 passed 1 step · 1 call 5.5s
🟢 a chat survives a restart of the server 1 step · 1 call 5.5s
🟢 compaction.e2e.ts · 1 passed 1 step · 1 call 4.5s
🟢 /compact replaces the old chat with a summary 1 step · 1 call 4.5s
🟢 pluginTrustRestart.e2e.ts · 1 passed 4 steps · 2 calls 10.4s
🟢 a trusted plugin hook stays trusted after the server restarts 4 steps · 2 calls 10.4s
🟢 settingsRoundTrip.e2e.ts · 1 passed 2 steps · 1 call 9.1s
🟢 a chat survives trips through Settings 2 steps · 1 call 9.1s
🟢 sidebarPerWorkspace.e2e.ts · 1 passed 4 steps · 1 call 4.1s
🟢 a new workspace starts with its right sidebar collapsed while another keeps it open 4 steps · 1 call 4.1s
🟢 smoke.e2e.ts · 1 passed 2 steps · 1 call 2.4s
🟢 the agent opens the playground workspace 2 steps · 1 call 2.4s
🟢 streamErrors.e2e.ts · 1 passed 1 step · 1 call 6.7s
🟢 stream errors show in the chat, and the chat recovers 1 step · 1 call 6.7s
🟢 todoNudge.e2e.ts · 1 passed 1 step · 1 call 2.6s
🟢 the agent finishes open TODO items after the turn-end nudge 1 step · 1 call 2.6s
🟢 touchAlignment.e2e.ts · 6 passed 6 steps · 6 calls 23.0s
🟢 the Settings close button is level with the title on a touch screen 390px wide 1 step · 1 call 4.7s
🟢 the Settings close button is level with the title on a touch screen 768px wide 1 step · 1 call 3.4s
🟢 Backup settings checkboxes are small and level with their labels on a touch phone 1 step · 1 call 3.8s
🟢 the heartbeat checkbox is small and level with its label on a touch phone 1 step · 1 call 3.4s
🟢 a reply's model and time stay on one line on a touch phone 1 step · 1 call 3.0s
🟢 the workspace actions menu stays on the title row on a touch phone 1 step · 1 call 4.7s

e2e 0.20.0 · 1m 11s · web · run artifacts

🟢 e2e Electron stories (Haiku): 3 passed

3 agent steps · 3 model calls · 7.9k tokens (32% cached)
Spend: $0.01 of the $1.00 cap (proxy ledger)

All 3 tests in 3 files
Test Agent Time
🟢 electron/deepLink.e2e.ts · 1 passed 1 step · 1 call 7.5s
🟢 a deep link of a second launch starts a chat in the running app 1 step · 1 call 7.5s
🟢 electron/menuBar.e2e.ts · 1 passed 1 step · 1 call 12.6s
🟢 the workspace menu bar is the desktop title bar 1 step · 1 call 12.6s
🟢 electron/smoke.e2e.ts · 1 passed 1 step · 1 call 5.0s
🟢 Xum Electron opens the playground workspace 1 step · 1 call 5.0s

e2e 0.20.0 · 27.4s · electron · run artifacts

🟢 e2e agent repros (Haiku): no tests selected

Spend: $0.00 of the $1.00 cap (proxy ledger)

e2e 0.20.0 · 5.3s · 2 targets (web, phone) · run artifacts

Base automatically changed from tests/sandbox-image-publish-upsert to main October 27, 2026 09:26
…build locally

Every sandbox job (all job kinds, in CI and on developer machines) now looks up
ghcr.io/coder/xum-bugbash-sandbox:inputs-<key> anonymously, verifies its
attestation on the host with `gh attestation verify` (coder/xum, refs/heads/main,
the bugbash-sandbox-image workflow), and pulls name@digest. Any other outcome
(not published, lookup error, no gh, no token, failed verification) logs the
reason and builds the image from the checkout with build.sh, with the LOCAL IMAGE
warning. The inputs label check stays for both.

Removes image.json, readImageLock, the stale-key refusal, the localBuild option
and the BUGBASH_SANDBOX_BUILD_STALE opt-in. CI passes GH_TOKEN to the launcher
steps only (host side; the container env drops it). gh also gets GH_CONFIG_DIR
when set, for a gh login in a custom config folder. The publish workflow writes
its record to $RUNNER_TEMP/publish.json.

---

_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high` • Cost: `$4.06`_

<!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=high costs=4.06 -->
@mintlify

mintlify Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
Mux 🟢 Ready View Preview Oct 27, 2026, 9:27 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 692161f046

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/pr.yml
Comment thread tests/bugbash/sandbox/runner.ts Outdated
The first push publish (run 40480740229) warned that creating the storage record needs artifact-metadata: write. Runners verify the attestation bundle in GHCR and never read a storage record, so the step creates none.
The verified path printed nothing, so a job log could not show which image ran. It now logs the name@digest, as the fallback logs its reason.
The verify step gave gh only PATH, HOME, GH_TOKEN and GH_CONFIG_DIR. A login under a custom XDG_CONFIG_HOME, or a token in the desktop keyring (gh auth login's default, reached over the session bus), then looked like no login, and the launcher built the image locally. gh now also gets XDG_CONFIG_HOME, DBUS_SESSION_BUS_ADDRESS and XDG_RUNTIME_DIR. Docker and the job container still get none of them.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: d84089776a

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: d84089776a

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33

Copy link
Copy Markdown
Member Author

Readiness record

  • Final commit: d84089776a. Required and Codex Comments pass on it, and wait_pr_ready.sh reports ready. One Test / Unit shard hit its 20-minute limit because the runner spent 15 minutes setting up bun. I reran it, and it passed.
  • Review rounds: 7 Codex rounds (code and security together), plus one advisor pass. My two small pushes after round 2 each started a round, so the count went past the limit of 6. The maintainer (@ThomasK33) approved round 7.
    • 56adeb5: no findings.
    • 692161f: 2 P2 findings, both answered and resolved.
      • "Grant the token packages: read": I rejected it. The package is public, and the verified path works with contents: read. Run 40482887460 (job 122117432637) proved it: sandbox verified image for inputs key 2bf9e12d3ac7dcb1: …@sha256:fb7221e7…, no build, self-check 54/54 passed.
      • "Keep XDG_CONFIG_HOME for gh": fixed in d84089776a. gh also gets DBUS_SESSION_BUS_ADDRESS and XDG_RUNTIME_DIR now, so a token in the desktop keyring is found.
    • c85bf67, c22071a, d840897 (automatic): no new findings.
    • d840897 (manual @codex review, round 6): the security review finished clean. The code review stayed on "Running" for 6 hours.
    • d840897 (manual @codex review, round 7, approved by the maintainer): "Didn't find any major issues", and the security review is clean. No review threads are open.
  • Commits after the first review: c85bf67236 sets create-storage-record: false on Attest. The first real publish run warned that a storage record needs artifact-metadata: write. c22071a5cc logs the verified image name@digest, so a job log names the image that ran.
  • Local checks: tests/bugbash bun tests (356 pass), make static-check, make lint-eslint, make crap-check, actionlint and make lint-zizmor pass. Each new env name and the new log line has a test that fails when the name or the line is removed.
  • Dogfood:
  • Advisor (clean context): ready with tracked follow-ups.
  • Follow-ups:
    • I tested the keyring path only with a fake gh. Trigger: a developer with a keyring login sees no gh token in the fallback warning.
    • In CI, a failed verify costs a local build of up to 25 minutes, and the ::warning:: is the only signal. Trigger: the warning shows up on a PR that does not change the image inputs.
    • After this PR merges, main publishes key 56c0…. I will check that the next PR's sandbox jobs pull it without a build, and link the run here.
    • Pixel / Review is pending. It is not a required check, and this PR changes no UI.
  • Decision: ready. Landing through the merge queue with gh stack merge 7672 --yes.

Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: xhigh

@ThomasK33
ThomasK33 added this pull request to the merge queue Oct 27, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

Status

The readiness record is consistent with the supplied PR state: the local checkout is clean and at the stated final commit, d84089776a.

The merge could not be started because this environment does not have the official gh stack extension installed. It also has no authenticated GitHub session or configured Git remote, so installing the extension alone would not make the merge actionable. No repository changes were made, so there was nothing to commit and no follow-up PR was created.

Checks

  • ✅ git status --short --branch && git rev-parse --short=10 HEAD — clean checkout at d84089776a.
  • ⚠️ gh stack merge 7672 --yes — unavailable because gh stack is not installed.
  • ⚠️ gh auth status — no authenticated GitHub host in this environment.
  • ✅ git remote -v — confirmed that this checkout has no configured remote.

View task →

Merged via the queue into main with commit d021e51 Oct 27, 2026
74 of 77 checks passed
@ThomasK33
ThomasK33 deleted the tests/sandbox-image-runner-upsert branch October 27, 2026 16:49
@ThomasK33

Copy link
Copy Markdown
Member Author

Post-merge check on main (d021e513d2):

  1. Push run 40544661098 built and pushed the image for the new inputs key, attested sha256:1608904f29786279802deba4bd0b1bed1e007ccbfa3296dfadc96a1264e9e1f3, and pointed ghcr.io/coder/xum-bugbash-sandbox:inputs-56c0e0377834e02e81bd9a808fea08d925b6a5a532fca50b11f4092c8f9fd398 at it. The storage-record warning is gone (create-storage-record: false). gh attestation verify … --source-ref refs/heads/main --signer-workflow …/bugbash-sandbox-image.yml exits 0, and the certificate names that run.
  2. The push run of the PR workflow on main skips Test / Bug-bash sandbox, as designed. The merge-queue run had already tested that commit, but it ran before the image was published, so it built the image locally. To test the published path, I dispatched the PR workflow on main (run 40545086147). Its Test / Bug-bash sandbox job logged sandbox verified image for inputs key 56c0e0377834e02e: ghcr.io/coder/xum-bugbash-sandbox@sha256:1608904f… five times. It built nothing. The self-check passed 54 of 54, and the repros passed 118 and 2 of 2. The job took 6 minutes 39 seconds. I cancelled the rest of that run after this job finished.

So a job whose inputs key main has published now pulls the attested image, and a job with a new key builds the image locally and warns.

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