Skip to content

ci: spike -- Backend Tests shard 1 on the CodeBuild runner as an unprivileged user - #10419

Draft
CrysisDeu wants to merge 1 commit into
mainfrom
ci/codebuild-backend-spike
Draft

ci: spike -- Backend Tests shard 1 on the CodeBuild runner as an unprivileged user#10419
CrysisDeu wants to merge 1 commit into
mainfrom
ci/codebuild-backend-spike

Conversation

@CrysisDeu

Copy link
Copy Markdown
Collaborator

Summary

Spike, not the migration. Routes exactly one backend shard — Backend Tests (3.12, 1) — to the CodeBuild-hosted runner (matrix.group == 1), running the test steps as an unprivileged user through a new composite action. Shards 2–4 stay on ubuntu-latest and exercise the passthrough path of the same action, so the diff is a single set of steps for both runners. If shard 1 is green here, a follow-up PR moves all four; if it is not, the remaining failures tell us what a custom runner image would have to carry.

Why

The pilot's first attempt to route the backend shards (#10328, revision 1) failed 37–42 tests per shard. Causes, all environment, none code:

  1. The CodeBuild runner executes the job as root. GitHub-hosted runners use runner (uid 1001). The suite asserts permission semantics root does not have — test_app_scaffold.py::TestWriteContainment::test_a_read_only_file_site_is_refused (write through 0o444 → DID NOT RAISE), test_governance_distribution.py::…agent_writable… (root owns every chain), test_computer_use_enable_state.py::…test_unreadable_file_is_disabled (root reads chmod 000) — and github_runner.validate_provider_executable refuses root outright ("provider execution is disabled for a root gateway"), which takes test_github_runner.py::TestResolveGh::*, test_issue_radar_gh_bin.py::* and test_source_providers.py::…strict_mode… with it.
  2. jq 1.6 on standard:7.0 vs 1.7 on the hosted image — test_issue_triage_workflow.py (7 tests) uses 1.7 syntax.
  3. libpython not resolvable without LD_LIBRARY_PATH. setup-python's downloaded toolcache build links libpython dynamically; the tests that spawn a sanitized subprocess (test_dev_fleet_backend_only_sync_skip.py, 5 tests) got python: error while loading shared libraries: libpython3.12.so.1.0 (rc 127).

AWS documents no non-root mode for the CodeBuild GitHub Actions runner, so this closes the gap inside the job instead of with a custom image.

What changed

  • .github/actions/run-as-runner/action.yml (new composite action). On a root runner: useradd -m -u 1001 runner, chown -R runner: $GITHUB_WORKSPACE $RUNNER_TEMP, register $RUNNER_TOOL_CACHE/Python/*/x64/lib with ldconfig, install jq 1.7.1 and gh 2.100.0 (sha256-pinned release binaries), and install /usr/local/bin/ci-shell = runuser -m -u runner -- env HOME=/home/runner USER=runner LOGNAME=runner bash -eo pipefail "$1". On a non-root runner: install ci-shell as a bash passthrough and stop. Everything before it (checkout, setup-python, setup-uv, uv pip install --system) still runs as whatever user the runner is — that is fine, the tool cache only needs to be readable.
  • ci.yml / backend-test: runs-on is matrix.group == 1 && needs.changes.outputs.linux_runner_large || 'ubuntu-latest'; the Run tests and Stage shard coverage data steps declare shell: /usr/local/bin/ci-shell {0}; a diagnostics step (CodeBuild only, runner.environment == 'self-hosted') prints the effective user, libpython resolution, jq/gh/git versions, workspace ownership, and whether unshare NEWNS works in the container (answers the e2e/sandbox question for the inventory too). Select test scope keeps the default shell because it writes $GITHUB_OUTPUT.

Not in this PR: docs (the pilot entry still says backend stays hosted — true until the follow-up), the other three shards.

How it was verified

Static only, per repo convention: YAML parses, bash -n on the action's script, brand gate clean against origin/main. The CI run on this PR is the experiment; result goes in a comment.

Rollback

Delete the runs-on expression back to ubuntu-latest; ci-shell is a passthrough there, so the action and the shell: lines are inert.

UX CONCERNS

none (CI-only)

…unprivileged user

The CodeBuild-hosted runner executes jobs as root; the first attempt to
route the backend shards there failed 37-42 tests per shard on exactly
that (read-only refusals, root-owned-ancestor checks, PermissionError
semantics, and the code's own "provider execution is disabled for a root
gateway"), plus two image differences: jq 1.6 where the repo's jq
programs need 1.7, and a setup-python toolcache whose libpython needs
LD_LIBRARY_PATH -- which the tests strip when they spawn a sanitized
subprocess (rc 127).

New composite action .github/actions/run-as-runner: on a root runner it
creates `runner` (uid 1001, the hosted runner's user), hands it the
workspace and job temp, registers the toolcache lib dirs with ldconfig,
installs jq 1.7.1 and gh 2.100.0 (sha256-pinned), and installs
/usr/local/bin/ci-shell -- a `shell:` program that runs a step's script
as `runner` via `runuser -m` with the environment preserved. On
ubuntu-latest ci-shell is a plain bash passthrough, so the test steps
declare `shell: /usr/local/bin/ci-shell {0}` once and behave identically
on both runners. Steps that write $GITHUB_OUTPUT keep the default shell.

Spike scope: only `Backend Tests (3.12, 1)` is routed
(`matrix.group == 1`); shards 2-4 stay on ubuntu-latest and exercise the
passthrough. A diagnostics step on the CodeBuild shard prints the
effective user, libpython resolution, jq/gh versions, workspace owner and
whether `unshare NEWNS` is available. If shard 1 is green all four move
in a follow-up; if not, the remaining reds name what a custom image must
carry.
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of 1746f115bf1ed2b126098996471f98259e18a09a — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

A properly scoped spike: one shard, passthrough-identical on hosted runners, pinned binaries, and a one-line rollback — the right shape for de-risking the runner migration.

[DESIGN-REVIEWED] 1746f11

@github-actions

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 1746f115bf1ed2b126098996471f98259e18a09a — this comment is updated in place on each push.

Review details

No findings.

The sole candidate (ci-shell hardcoding HOME=/home/runner) requires assuming the CodeBuild standard:7.0 image ships a pre-existing runner account whose home is not /home/runner. That input is unverified and speculative — in the actual path (runner absent), useradd -m creates and owns /home/runner, and chown -R runner: "$GITHUB_WORKSPACE" "$RUNNER_TEMP" covers where the tests write. No concrete in-practice input (a) is established, so it fails Step 1's bar. No further groundable defect in this .github/-only diff.

[OPUS-REVIEWED] 1746f11

Verdict parsed from the review's SHA-scoped output markers for commit 1746f115bf1ed2b126098996471f98259e18a09a.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 1746f115bf1ed2b126098996471f98259e18a09a: <one-sentence reason>

@github-actions

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Premise-level review of 1746f115bf1ed2b126098996471f98259e18a09a — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

First-Principles-Verdict: CONCERNS

docs/ci/ci-and-reviews.md still pins "those shards move only once a custom image … exists" — this PR ships the opposite for shard 1 and leaves that record standing.

Not justified as shipped

  • Item 2 — one consumer, generalized: a reusable 91-line action with exactly one uses: site (grep uses: ./.github/actions/run-as-runner → 1, ci.yml:585), and the planned follow-up keeps that count at one because all four shards share the job; the singular form is an inline step.

What this change ships

Inventory (7 items) — 6 justified

Intent: get the backend test shards off the congested hosted queue by proving one shard runs green on the CodeBuild runner — an ADDITION (spike).

  1. Backend Tests shard 1 now runs on the CodeBuild runner instead of ubuntu-latest; forks unaffected — justified
  2. New reusable action provisions an unprivileged runner user on any root runner — one consumer, generalized (see above)
  3. All four shards' test/coverage steps run under ci-shell instead of the default shell — justified
  4. jq 1.7.1 and gh 2.100.0 (sha-pinned) installed on the CodeBuild runner — justified
  5. Toolcache libpython registered with ldconfig on the CodeBuild runner — justified
  6. Workspace and job-temp ownership handed to runner on CodeBuild — justified
  7. A diagnostics step prints user/toolchain/sandbox facts on CodeBuild runs — justified

Watch

  • The description says the pilot doc entry is "true until the follow-up", but docs/ci/ci-and-reviews.md:346-355 ("backend-test stays on ubuntu-latest… shards move only once a custom image exists") is false the moment shard 1 routes — a prior recorded decision this diff overrides in code while the record says otherwise, against AGENTS.md's same-commit doc rule. Clears when: the pilot entry matches shard 1's shipped routing, in this PR or the named follow-up.
  • Item 3 silently changes hosted shards 2–4 from GitHub's default bash -e to -eo pipefail; I read all three step bodies and found no pipelines, so it is inert today — verify the CI run agrees. Clears when: shards 2–4 are green on this PR's run.

Subtractions

  • Inline run-as-runner's script as a step of backend-test — 1 consumer (ci.yml:585), and the shard matrix means a second never arrives; drop the .github/actions/run-as-runner/ directory.

[FIRST-PRINCIPLES-REVIEWED] 1746f11

@github-actions

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — 🔴 changes requested (blocking)

GPT 5.6 found at least one blocking issue that must be resolved before merging 1746f115bf1ed2b126098996471f98259e18a09a. 1 of 1 blocking finding(s) are security-class and were withheld from adjudication, so the blocking verdict stands.

This comment is updated in place on each push.

BLOCKING -- .github/actions/run-as-runner/action.yml:27 -- jq 1.7.1 checksum is incorrect
JQ_SHA256: "5942c9b0934e510ee61eb3e30273f1b3fe2590df93933a93d7c58b81d19c8ff5"
CodeBuild jq 1.6 -> downloads jq 1.7.1 -> checksum verification fails -> shard 1 never runs.
Anchor: residual/crash-data-loss-corruption
Fix: Use the published SHA-256 for jq-linux-amd64 1.7.1.
[BLOCK-MERGE] 1746f11
[GPT-REVIEWED] 1746f11

Adjudication (Opus 4.8) — is blocking on each finding proportionate?

Network access to verify the published checksum is blocked in this environment, so I cannot independently confirm whether the hash is right or wrong. But that does not matter for the FLAG bar, because the triggering condition is not rare.

Conditions confirmed from code opened this run:

  • action.yml:31 set -euo pipefail — any failed step command aborts the step.
  • action.yml:69 the jq-1\.7 version guard is false on the CodeBuild standard:7.0 image (ships jq 1.6, per the comment at line 67), so the download branch is always taken there.
  • action.yml:70-71 downloads jq-linux-amd64 1.7.1 and verifies it against JQ_SHA256 (line 27) via sha256sum -c -.

Recovery path: none — a checksum mismatch fails the step every run until the constant is corrected; shard 1 never runs (self-reported, and consistent with the pipefail/sha256sum -c semantics at 31/71).

Rarity: none available. The failure path is the ordinary CodeBuild root-runner path, hit on every shard-1 execution, not an extreme or contradictory condition combination. A FLAG would require a rarity argument I cannot construct, and I could not verify the checksum this run — the FLAG record cannot be completed. Per the fenced contract, that is UPHOLD-FENCED.

[ADJUDICATION] 1746f11 total=0 uphold=0 downgrade=0
[GPT-ADJUDICATED] 1746f11

[ADJUDICATION-FENCED] 1746f11 fenced=1 flagged=0
UPHOLD-FENCED F1 .github/actions/run-as-runner/action.yml:27 -- triggering path is the ordinary CodeBuild root-runner branch hit every shard-1 run, not a rare condition, and the checksum could not be verified this run, so no FLAG record can be completed.
[GPT-ADJUDICATED-FENCED] 1746f11

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 1746f115bf1ed2b126098996471f98259e18a09a: <one-sentence reason>

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 12, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Spike result — Backend Tests (3.12, 1) on CodeBuild as an unprivileged user (run 34717454028)

4 failed / 25 224 passed / 77 skipped in 41:17 — down from 37–42 failures per shard when the same shard ran as root (#10328 rev 1). The user model is fixed; what remains is two missing binaries and two tests to re-check.

Diagnostics step (CodeBuild build 0d4da484…, large)

check result
effective user in test steps uid=1002(runner) gid=1002(runner) (uid 1001 was already taken in standard:7.0; the action fell back to the next free uid — harmless)
HOME / USER /home/runner / runner
python toolcache 3.12.14; libpython resolves without LD_LIBRARY_PATH: True (the ldconfig step works — the earlier rc 127 class is gone)
jq / gh / git jq-1.7.1 (installed by the action), gh 2.99.0 (already in the image), git 2.55.0
workspace / temp owner runner / runner
unshare --mount --map-root-user Operation not permitted — unprivileged user namespaces are unavailable in the CodeBuild container. This settles two inventory rows: backend-test-sandbox and the e2e precondition cannot run there.
hosted shards 2–4 through the same ci-shell (passthrough) shards 2 and 4 green, 3 still running at the time of writing — the wrapper is inert on ubuntu-latest

Queue → start 204 s (20:40:02 → 20:43:26; longer than the 15–21 s seen on every other build so far — worth watching whether large builds provision slower), execution 42.1 min (hosted shard 1 on the last main run: 41 min), cost ≈ $0.84.

The 4 remaining failures

test reason from the log class
test/test_cli_server_more_coverage.py::TestStopViaService::test_explicit_port_bypasses_the_service `lsof` not found — cannot look up the gateway process on port 8123 env-missing (lsof is on ubuntu-latest, not on standard:7.0)
test/test_app_backend.py::TestBootSpawnLatency::test_survival_check_exits_early_for_a_healthy_child "healthy child burned 1.60s of a 1.60s budget" — platform_compat.listening_pid_tool_available() is false without lsof, so the survival poll can never confirm the child and sleeps the whole window env-missing (same lsof)
test/test_chat_send_agent_model_default.py::TestEagerSpawnDefaultModel::test_eager_session_starts_on_the_global_default Expected get_or_create to have been awaited once. Awaited 0 times. other / undetermined — not seen in the root run; _eager_spawn returned before creating the session; needs one rerun to tell deterministic-on-this-runner from flake
test/test_chat_send_agent_model_default.py::TestEagerSpawnDefaultModel::test_eager_resolve_runs_off_the_loop_and_survives_stop_iteration same assertion other / undetermined (same pair)

No still-root-semantics failures remain: every read-only-refusal, root-owned-ancestor, PermissionError and "provider execution is disabled for a root gateway" test from the root run passes as runner.

What this means for wave 3

  • Add lsof to the run-as-runner toolchain step (apt-get install -y lsof, or the ubuntu package pin) → 2 of 4 go away by construction.
  • The two TestEagerSpawnDefaultModel failures need one more run to classify; if they repeat only on CodeBuild, the cause is in _eager_spawn's early-return gates, not in the runner user model.
  • The sudo/custom-image question is answered: no custom image needed for the user model. (Whether CodeBuild honours a custom image's USER remains untested — no image was involved.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

readiness: action required A blocking check or review needs attention

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant