From 518f4aa3dd81f178c00523dd12b1de4a6d7d9631 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Oct 2026 14:22:46 -0700 Subject: [PATCH 1/3] automation: build x86_64 on the Edera host config The acceptance build judged x86_64 against defconfig with the Edera options forced on, which is not the kernel we ship. Merge the latest configs/x86_64/host.config from edera-dev/linux-kernel-oci over defconfig first, the way linux-kernel-oci itself applies it (the file is a fragment against x86_64_defconfig and omits SMP, NET and the rest of what matches it), and log the commit it came from. The Edera options are still forced on as built-ins afterwards: the host config builds OpenPaX out and 9P/Xen, virtio-gpu and the Hyper-V network drivers as modules, which a vmlinux build would never compile. AGP joins that list on x86_64 because host.config has it as a module, which caps DRM at m. arm64 is unchanged. --- .automation/skills/upstream-rebase/SKILL.md | 3 ++- .github/scripts/kernel-build.sh | 24 ++++++++++++++++++++- .github/workflows/upstream-rebase.yml | 5 +++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/.automation/skills/upstream-rebase/SKILL.md b/.automation/skills/upstream-rebase/SKILL.md index c23286bb72c4a9..96bd0ae54dfa70 100644 --- a/.automation/skills/upstream-rebase/SKILL.md +++ b/.automation/skills/upstream-rebase/SKILL.md @@ -26,7 +26,8 @@ Your report can only stop it. When you finish, the workflow independently: alone that the series sits on the upstream tip, is linear, lost or gained no commit, and changed the tree by exactly the upstream delta; 2. builds x86_64 and arm64 `vmlinux` with `kernel-build.sh`, which turns on - every downstream feature as a built-in. + every downstream feature as a built-in. x86_64 starts from the latest + Edera host config in edera-dev/linux-kernel-oci; arm64 from defconfig. If all of that passes **and your report's "Needs a decision" section is `None.` with nothing marked UNSURE anywhere** (checked by diff --git a/.github/scripts/kernel-build.sh b/.github/scripts/kernel-build.sh index 2a4c38dd68cb80..5920e581871522 100755 --- a/.github/scripts/kernel-build.sh +++ b/.github/scripts/kernel-build.sh @@ -7,6 +7,12 @@ # replay that breaks any of them, or links against a symbol upstream removed, # fails here. Building only a subdirectory would miss those link errors. # +# x86_64 adds Edera's shipping host config, fetched from the tip of +# edera-dev/linux-kernel-oci each run, over defconfig, so the branch is +# judged against the kernel we actually ship. That config builds +# OpenPaX out and several Edera drivers as modules, so the options below are +# still forced on as built-ins over it. arm64 starts from defconfig. +# # The rebase skill runs this same script before they finish, so # what Claude checks and what the workflow checks cannot drift apart. # @@ -18,7 +24,8 @@ # aarch64-linux-gnu-) unless LLVM is set, in which case it builds with clang. # # Exit status: make's on a build failure; 3 if an option it asked for did not -# survive olddefconfig (a Kconfig dependency the series no longer satisfies). +# survive olddefconfig (a Kconfig dependency the series no longer satisfies); +# git's if the host config cannot be fetched. set -euo pipefail @@ -51,6 +58,8 @@ x86_64) HYPERVISOR_GUEST PARAVIRT XEN_PV XEN_PVHVM_GUEST PCI_XEN XEN_PCIDEV_FRONTEND XEN_PCIDEV_BACKEND XEN_IOMMU HYPERV HYPERV_VMBUS HYPERV_NET PCI_HYPERV PCI_HYPERV_INTERFACE + # host.config builds AGP as a module, which caps DRM at m. + AGP ) ;; arm64) @@ -72,6 +81,19 @@ mkdir -p "$objdir" mk() { make -s ARCH="$karch" "${cross[@]}" O="$objdir" "$@"; } mk defconfig +if [ "$arch" = x86_64 ]; then + # host.config is a fragment against x86_64_defconfig: it leaves out + # whatever matches defconfig (SMP and NET among them), so it only means + # something merged over defconfig, the way linux-kernel-oci applies it. + oci=https://github.com/edera-dev/linux-kernel-oci + cfgrepo=$(mktemp -d) + trap 'rm -rf "$cfgrepo"' EXIT + git init -q --bare "$cfgrepo" + git -C "$cfgrepo" fetch -q --depth 1 "$oci" main + git -C "$cfgrepo" show FETCH_HEAD:configs/x86_64/host.config >"$cfgrepo/host.config" + echo "kernel-build: merging host.config from $oci@$(git -C "$cfgrepo" rev-parse FETCH_HEAD)" + scripts/kconfig/merge_config.sh -m -O "$objdir" "$objdir/.config" "$cfgrepo/host.config" >/dev/null +fi enable=() for o in "${opts[@]}"; do enable+=(--enable "$o"); done scripts/config --file "$objdir/.config" "${enable[@]}" diff --git a/.github/workflows/upstream-rebase.yml b/.github/workflows/upstream-rebase.yml index 75056019338fdf..1e2fdad1169381 100644 --- a/.github/workflows/upstream-rebase.yml +++ b/.github/workflows/upstream-rebase.yml @@ -18,7 +18,8 @@ name: Upstream rebase # linear, lost or gained no commit, and changed the tree by exactly the # upstream delta; # - kernel-build.sh builds x86_64 and arm64 vmlinux with every downstream -# feature built in. +# feature built in, x86_64 on top of the latest Edera host config from +# edera-dev/linux-kernel-oci. # # If both pass and Claude's report asks for no decision # (report-needs-decision.sh), the tree is force-pushed to the result, pinned @@ -646,7 +647,7 @@ jobs: echo echo "## Independent checks" echo - echo "Builds: **${BUILD}** (x86_64 and arm64 vmlinux with the Edera features built in; per-build results in the run)." + echo "Builds: **${BUILD}** (x86_64 vmlinux on the Edera host config and arm64 vmlinux, both with the Edera features built in; per-build results in the run)." echo if [ -s "$verify" ]; then cat "$verify"; else echo "The checker did not run."; fi echo From ada53b8c53259ba3f30de8432e85fa1b8c06a158 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Oct 2026 14:33:48 -0700 Subject: [PATCH 2/3] automation: repair a rebase whose build fails instead of proposing it A rebase whose independent build failed went straight to a pull request, and in practice that was a run Claude never finished: the Bash tool's two-minute default timeout cannot hold a kernel build, so the build went to the background and the session ended "waiting for the x86 build" (#47, and #35 before it). Nobody saw the error until a reviewer did. Give the rebase step a 90-minute Bash timeout, and tell the skill to run builds in the foreground and to keep fixing and rebuilding until both pass rather than treat a failing build as an outcome to report. If the independent build still fails, a new read-only fix job hands Claude its branch and the build logs for one repair round, following a new "Repairing a failed build" section of the skill. A repaired branch is verified and built again from scratch before publish judges it; the checker now runs on whichever branch is final. Publish takes the repaired branch and report when there is one, says so in the pull request, and keeps the repair round's report when it repaired nothing. The build job moves to a reusable rebase-build.yml so the first build and the rebuild are the same job, and it now keeps each build's log as an artifact for the repair round to read. A repair changes downstream commits, so a repaired branch still shows drift and still goes to review rather than being pushed: what changes is that the reviewer gets a branch that builds. --- .automation/skills/upstream-rebase/SKILL.md | 47 ++++- .github/workflows/automation-selftest.yml | 1 + .github/workflows/upstream-rebase.yml | 217 +++++++++++++++++--- 3 files changed, 235 insertions(+), 30 deletions(-) diff --git a/.automation/skills/upstream-rebase/SKILL.md b/.automation/skills/upstream-rebase/SKILL.md index 96bd0ae54dfa70..752b3fdb97d317 100644 --- a/.automation/skills/upstream-rebase/SKILL.md +++ b/.automation/skills/upstream-rebase/SKILL.md @@ -29,6 +29,10 @@ Your report can only stop it. When you finish, the workflow independently: every downstream feature as a built-in. x86_64 starts from the latest Edera host config in edera-dev/linux-kernel-oci; arm64 from defconfig. +If the build fails, you get your branch back with the build logs and one +chance to repair it (see "Repairing a failed build"), and the repaired branch +is checked and built again from scratch. + If all of that passes **and your report's "Needs a decision" section is `None.` with nothing marked UNSURE anywhere** (checked by `report-needs-decision.sh`), the downstream branch is force-pushed to your @@ -139,6 +143,12 @@ The workflow re-runs these builds itself, but run them here so you can fix what your resolutions broke. Each takes a while; run them once the rebase is complete, not after every commit. +Run each build in the foreground and wait for it: the shell allows a command +up to 90 minutes, which is enough for a full build. Never start a build in +the background, and never end your session while one is running. A run +that stops to wait for a build ends there, and nobody fixes what it would +have shown. + ```sh bash .rebase-tools/scripts/kernel-build.sh x86_64 .rebase-scratch/obj-x86 4 bash .rebase-tools/scripts/kernel-build.sh arm64 .rebase-scratch/obj-arm64 4 @@ -150,9 +160,10 @@ the build script names). Find out which, and say so in the report; if the build script's list is what is stale, that is a **Needs a decision** item, not something to work around. -If a build fails because of the rebase (a resolution, or an upstream change a -downstream commit has not caught up with), fix it **in the downstream commit -that is wrong**. Interactive editors do not work here, so drive the todo list +A failing build is not a finished rebase. If a build fails because of the +rebase (a resolution, or an upstream change a downstream commit has not +caught up with), fix it, build again, and keep going until both builds pass. +Fix it **in the downstream commit that is wrong**. Interactive editors do not work here, so drive the todo list with sed: `git -c sequence.editor="sed -i 's/^pick /edit /'" rebase -i UPSTREAM`, amend, then `git rebase --continue`. Do not add a fix-up commit on top unless there is no single commit it belongs to; if you must, its @@ -239,6 +250,36 @@ x86_64, arm64: pass, fail (with the first error), or pre-existing failure Then stop. Leave RESULT checked out or not; the workflow reads the branch, not the working tree. +## Repairing a failed build + +The workflow sends you here, in a fresh session, when the branch you produced +failed its independent build. The prompt gives you `BUILD_LOGS`, a directory +holding the log of each arch the workflow built, and your branch is checked +out as `RESULT`. `REPORT` already holds the report you wrote for the rebase. + +1. Read the logs. Find the first error in each failing one; later errors + usually follow from it. A log that ends with `kernel-build: ... built` + passed. +2. Reproduce the failure with `kernel-build.sh` exactly as in step 3, then + fix it the same way: in the downstream commit that is wrong, with the + same rules on fix-up commits. If it fails on pristine `UPSTREAM` too, it + is not yours: say so and leave the branch alone. +3. Build both arches again, not only the one that failed, and keep fixing + and rebuilding until both pass. Do not stop at the first fix. +4. Run `verify-upstream-rebase.sh` as in step 5. Every new drift line must be + one of your build fixes. +5. Update `REPORT` in place. Add each fix to **Build fixes** (what broke, the + first error, which commit you changed, why), and rewrite **Builds** with + the results you now have. Leave the rest as it is, except to add an + **UNSURE** item if a fix needed a judgement call. A reviewer will read + this report, not your session. + +Exit status 3 is still not something to work around here: a Kconfig symbol +the build script names no longer surviving `olddefconfig` goes to **Needs a +decision**, as in step 3. If you cannot make the build pass, leave `RESULT` +at the best branch you have and say in **Builds** exactly what still fails +and why. + ## If you cannot finish If the rebase cannot be completed (a conflict you cannot resolve at all, a diff --git a/.github/workflows/automation-selftest.yml b/.github/workflows/automation-selftest.yml index fc7305654ec509..d4a7a3d84570a3 100644 --- a/.github/workflows/automation-selftest.yml +++ b/.github/workflows/automation-selftest.yml @@ -19,6 +19,7 @@ on: paths: - '.github/scripts/**' - '.github/workflows/upstream-rebase.yml' + - '.github/workflows/rebase-build.yml' - '.github/workflows/kernel-nightly.yml' - '.github/workflows/rebase-land.yml' - '.github/workflows/automation-selftest.yml' diff --git a/.github/workflows/upstream-rebase.yml b/.github/workflows/upstream-rebase.yml index 1e2fdad1169381..313c1f85b7ae61 100644 --- a/.github/workflows/upstream-rebase.yml +++ b/.github/workflows/upstream-rebase.yml @@ -10,9 +10,13 @@ name: Upstream rebase # # Nothing Claude says about its own result decides what happens to it. The # model runs in a job that holds a read-only token and cannot push. Its branch -# leaves that job as a git bundle, and two things then judge it -# independently, with scripts taken from the commit this run started from -# (the default branch), never from the tree under test: +# leaves that job as a git bundle and is built independently +# (rebase-build.yml). If that build fails, Claude gets one repair round: a +# second read-only job hands it the build logs, it fixes the branch in the +# downstream commit that broke, and the repaired branch is built again from +# scratch. The final branch is then judged, with scripts taken from the +# commit this run started from (the default branch), never from the tree +# under test: # # - verify-upstream-rebase.sh proves the series sits on the upstream tip, is # linear, lost or gained no commit, and changed the tree by exactly the @@ -233,6 +237,10 @@ jobs: anthropic_organization_id: '${{ steps.ids.outputs.org }}' anthropic_service_account_id: '${{ steps.ids.outputs.svc }}' anthropic_workspace_id: '${{ steps.ids.outputs.ws }}' + # A kernel build takes far longer than the Bash tool's default + # timeout of two minutes. Without this, a build has to go to the + # background, and the run ends while Claude waits for it. + settings: '{"env": {"BASH_DEFAULT_TIMEOUT_MS": "5400000", "BASH_MAX_TIMEOUT_MS": "5400000"}}' prompt: | Rebase the downstream kernel series onto upstream by following .rebase-tools/skills/upstream-rebase/SKILL.md exactly. Read it in @@ -315,8 +323,8 @@ jobs: retention-days: 30 verify: - needs: [prepare, rebase] - if: needs.rebase.outputs.produced == 'true' + needs: [prepare, rebase, build, fix] + if: always() && needs.rebase.outputs.produced == 'true' && needs.build.result != 'cancelled' && needs.fix.result != 'cancelled' runs-on: ubuntu-latest timeout-minutes: 45 outputs: @@ -344,10 +352,11 @@ jobs: mkdir "$RUNNER_TEMP/tools" git archive "$GITHUB_SHA" .github/scripts | tar -x -C "$RUNNER_TEMP/tools" --strip-components=1 + # The repaired branch when there is one, otherwise Claude's rebase. - name: Download uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: rebase-${{ inputs.key }} + name: ${{ needs.fix.outputs.produced == 'true' && format('fix-{0}', inputs.key) || format('rebase-{0}', inputs.key) }} path: ${{ runner.temp }}/in - name: Check @@ -355,7 +364,7 @@ jobs: env: OLD: ${{ needs.prepare.outputs.old }} UP: ${{ needs.prepare.outputs.up }} - RESULT: ${{ needs.rebase.outputs.result }} + RESULT: ${{ needs.fix.outputs.produced == 'true' && needs.fix.outputs.result || needs.rebase.outputs.result }} run: | set -euo pipefail git fetch --no-tags "$UPSTREAM_URL" "$UPSTREAM_BRANCH" @@ -387,13 +396,31 @@ jobs: build: needs: [prepare, rebase] if: needs.rebase.outputs.produced == 'true' + uses: ./.github/workflows/rebase-build.yml + with: + old: ${{ needs.prepare.outputs.old }} + up: ${{ needs.prepare.outputs.up }} + result: ${{ needs.rebase.outputs.result }} + bundle: rebase-${{ inputs.key }} + logs: build-${{ inputs.key }} + upstream_url: ${{ inputs.upstream_url }} + upstream_branch: ${{ inputs.upstream_branch }} + + # The repair round. The same read-only footing as the rebase job: Claude + # gets the failed build logs and its own branch back, and nothing it says + # decides anything. A branch that comes out of here is verified and built + # again from scratch before publish looks at it. + fix: + needs: [prepare, rebase, build] + if: always() && needs.rebase.outputs.produced == 'true' && needs.build.result == 'failure' runs-on: ubuntu-latest - timeout-minutes: 120 - strategy: - fail-fast: false - matrix: - arch: [x86_64, arm64] - name: build (${{ matrix.arch }}) + timeout-minutes: 330 + permissions: + contents: read + id-token: write + outputs: + produced: ${{ steps.collect.outputs.produced }} + result: ${{ steps.collect.outputs.result }} steps: - name: Harden runner uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 @@ -412,16 +439,28 @@ jobs: run: | set -euo pipefail git fetch --no-tags --filter=blob:none origin "$GITHUB_SHA" - mkdir "$RUNNER_TEMP/tools" - git archive "$GITHUB_SHA" .github/scripts | tar -x -C "$RUNNER_TEMP/tools" --strip-components=1 + mkdir .rebase-tools + git archive "$GITHUB_SHA" .github/scripts .automation | tar -x -C .rebase-tools --strip-components=1 - - name: Download + - name: Download Claude's rebase uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: rebase-${{ inputs.key }} - path: ${{ runner.temp }}/in + path: ${{ runner.temp }}/in/rebase + + # A build that died before it started leaves no log; Claude can still + # build the branch itself. + - name: Download the build logs + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: build-${{ inputs.key }}-* + path: ${{ runner.temp }}/in/build + merge-multiple: true - - name: Check out the result + # The logs go where Claude can read them; .rebase-scratch/ is ignored + # by the kernel's .gitignore, so they cannot end up in a commit. + - name: Restore the branch env: UP: ${{ needs.prepare.outputs.up }} RESULT: ${{ needs.rebase.outputs.result }} @@ -429,8 +468,17 @@ jobs: set -euo pipefail git fetch --no-tags "$UPSTREAM_URL" "$UPSTREAM_BRANCH" git merge-base --is-ancestor "$UP" FETCH_HEAD - git fetch "$RUNNER_TEMP/in/rebase.bundle" rebase-result:rebase-result - git checkout --detach "$RESULT" + git branch upstream-target "$UP" + git fetch "$RUNNER_TEMP/in/rebase/rebase.bundle" rebase-result:rebase-result + [ "$(git rev-parse rebase-result)" = "$RESULT" ] || { echo "bundle does not hold $RESULT"; exit 1; } + git checkout -q rebase-result + mkdir -p .rebase-scratch/build-logs + for f in "$RUNNER_TEMP"/in/build/*.log; do + [ ! -e "$f" ] || cp "$f" .rebase-scratch/build-logs/ + done + cp "$RUNNER_TEMP/in/rebase/report.md" "$RUNNER_TEMP/rebase-report.md" + git config --global user.name "edera-upstream-rebase" + git config --global user.email "noreply@edera.dev" - name: Install toolchains run: | @@ -440,13 +488,106 @@ jobs: build-essential bc bison flex libelf-dev libssl-dev python3 \ gcc-aarch64-linux-gnu - - name: Build + # The rebase job has already checked that these are set. + - name: Normalize identifiers + id: ids + env: + RULE: ${{ vars.REBASE_FEDERATION_RULE_ID }} + ORG: ${{ vars.ANTHROPIC_ORGANIZATION_ID }} + SVC: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }} + WS: ${{ vars.ANTHROPIC_WORKSPACE_ID }} + run: | + set -euo pipefail + strip() { printf '%s' "$1" | tr -d '[:space:]'; } + { + echo "rule=$(strip "$RULE")" + echo "org=$(strip "$ORG")" + echo "svc=$(strip "$SVC")" + echo "ws=$(strip "$WS")" + } >> "$GITHUB_OUTPUT" + + - name: Repair + id: claude + continue-on-error: true + uses: anthropics/claude-code-action@6fed3ca145920b639991cb756090506e1bcaf515 # v1.0.245 + with: + github_token: ${{ github.token }} + anthropic_federation_rule_id: '${{ steps.ids.outputs.rule }}' + anthropic_organization_id: '${{ steps.ids.outputs.org }}' + anthropic_service_account_id: '${{ steps.ids.outputs.svc }}' + anthropic_workspace_id: '${{ steps.ids.outputs.ws }}' + settings: '{"env": {"BASH_DEFAULT_TIMEOUT_MS": "5400000", "BASH_MAX_TIMEOUT_MS": "5400000"}}' + prompt: | + Your rebase of the downstream kernel series failed the workflow's + independent build. Repair it by following the "Repairing a failed + build" section of .rebase-tools/skills/upstream-rebase/SKILL.md + exactly. Read the whole skill first; the rest of it still applies. + + DOWNSTREAM=${{ env.DOWNSTREAM }} + OLD_TIP=${{ needs.prepare.outputs.old }} + UPSTREAM=${{ needs.prepare.outputs.up }} (local branch upstream-target) + RESULT=rebase-result (your rebase, checked out, at ${{ needs.rebase.outputs.result }}) + BUILD_LOGS=.rebase-scratch/build-logs (one log per arch the workflow built) + REPORT=${{ runner.temp }}/rebase-report.md (your report from the rebase) + + You cannot push and must not try. The workflow verifies and + builds your branch independently again afterwards. + claude_args: | + --model claude-opus-5-5 + --max-turns 400 + --allowedTools "Read,Grep,Glob,Edit,Write,Bash(git:*),Bash(make:*),Bash(bash .rebase-tools/scripts/verify-upstream-rebase.sh:*),Bash(bash .rebase-tools/scripts/kernel-build.sh:*),Bash(scripts/config:*),Bash(mkdir -p .rebase-scratch:*),Bash(diff:*),Bash(comm:*),Bash(sort:*),Bash(grep:*),Bash(sed:*),Bash(awk:*),Bash(cat:*),Bash(head:*),Bash(tail:*),Bash(wc:*),Bash(ls:*),Bash(cut:*),Bash(tee:*)" + + # Only a branch that still sits on the upstream tip and differs from + # the one that failed is worth building again. The report is always + # kept: when nothing was repaired, it says why. + - name: Collect the result + id: collect + if: always() env: - ARCH: ${{ matrix.arch }} - run: bash "$RUNNER_TEMP/tools/scripts/kernel-build.sh" "$ARCH" "$RUNNER_TEMP/obj" + UP: ${{ needs.prepare.outputs.up }} + BEFORE: ${{ needs.rebase.outputs.result }} + OUTCOME: ${{ steps.claude.outcome }} + run: | + set -euo pipefail + out=$RUNNER_TEMP/out + mkdir -p "$out" + cp "$RUNNER_TEMP/rebase-report.md" "$out/report.md" + if git rev-parse --verify -q refs/heads/rebase-result >/dev/null \ + && git merge-base --is-ancestor "$UP" rebase-result \ + && [ "$(git rev-parse rebase-result)" != "$UP" ] \ + && [ "$(git rev-parse rebase-result)" != "$BEFORE" ]; then + git bundle create "$out/rebase.bundle" refs/heads/rebase-result "^$UP" + echo "produced=true" >> "$GITHUB_OUTPUT" + echo "result=$(git rev-parse rebase-result)" >> "$GITHUB_OUTPUT" + else + echo "produced=false" >> "$GITHUB_OUTPUT" + echo "::warning::The repair round left no new branch (repair step: $OUTCOME)." + fi + + - name: Upload + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: fix-${{ inputs.key }} + path: ${{ runner.temp }}/out/ + if-no-files-found: error + retention-days: 30 + + rebuild: + needs: [prepare, fix] + if: always() && needs.fix.outputs.produced == 'true' + uses: ./.github/workflows/rebase-build.yml + with: + old: ${{ needs.prepare.outputs.old }} + up: ${{ needs.prepare.outputs.up }} + result: ${{ needs.fix.outputs.result }} + bundle: fix-${{ inputs.key }} + logs: rebuild-${{ inputs.key }} + upstream_url: ${{ inputs.upstream_url }} + upstream_branch: ${{ inputs.upstream_branch }} publish: - needs: [prepare, rebase, verify, build] + needs: [prepare, rebase, build, fix, verify, rebuild] if: always() && needs.prepare.outputs.run == 'true' && needs.rebase.result != 'cancelled' runs-on: ubuntu-latest timeout-minutes: 45 @@ -483,6 +624,16 @@ jobs: name: rebase-${{ inputs.key }} path: ${{ runner.temp }}/in/rebase + # The repaired branch if there is one, and the repair round's report + # whether or not it repaired anything. + - name: Download the repair + if: needs.fix.result != 'skipped' + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: fix-${{ inputs.key }} + path: ${{ runner.temp }}/in/fix + # Absent when Claude produced no branch and the checker never ran. - name: Download the checker report if: needs.verify.result == 'success' @@ -508,10 +659,12 @@ jobs: JOB_TOKEN: ${{ github.token }} PRODUCED: ${{ needs.rebase.outputs.produced }} VERIFY: ${{ needs.verify.outputs.status }} - BUILD: ${{ needs.build.result }} + BUILD: ${{ needs.fix.outputs.produced == 'true' && needs.rebuild.result || needs.build.result }} + REPAIR: ${{ needs.fix.result }} + REPAIRED: ${{ needs.fix.outputs.produced }} OLD: ${{ needs.prepare.outputs.old }} UP: ${{ needs.prepare.outputs.up }} - RESULT: ${{ needs.rebase.outputs.result }} + RESULT: ${{ needs.fix.outputs.produced == 'true' && needs.fix.outputs.result || needs.rebase.outputs.result }} PREFIX: ${{ needs.prepare.outputs.prefix }} CANDIDATE: ${{ needs.prepare.outputs.candidate }} KEY: ${{ inputs.key }} @@ -520,6 +673,9 @@ jobs: set -euo pipefail in=$RUNNER_TEMP/in report=$in/rebase/report.md + bundle=$in/rebase/rebase.bundle + [ ! -s "$in/fix/report.md" ] || report=$in/fix/report.md + [ "$REPAIRED" != "true" ] || bundle=$in/fix/rebase.bundle verify=$in/verify/verify.md summary() { cat >> "$GITHUB_STEP_SUMMARY"; } title="Nightly upstream rebase of $DOWNSTREAM failed" @@ -550,7 +706,7 @@ jobs: git fetch --no-tags "$UPSTREAM_URL" "$UPSTREAM_BRANCH" git merge-base --is-ancestor "$UP" FETCH_HEAD - git fetch "$in/rebase/rebase.bundle" rebase-result:rebase-result + git fetch "$bundle" rebase-result:rebase-result [ "$(git rev-parse rebase-result)" = "$RESULT" ] # Workflows triggered by a pull request, including the land workflow, @@ -648,6 +804,13 @@ jobs: echo "## Independent checks" echo echo "Builds: **${BUILD}** (x86_64 vmlinux on the Edera host config and arm64 vmlinux, both with the Edera features built in; per-build results in the run)." + if [ "$REPAIRED" = "true" ]; then + echo + echo "The first build of Claude's rebase failed. Claude repaired the branch from the build logs, and the builds and checker above are of the repaired branch; its report's **Build fixes** section says what changed." + elif [ "$REPAIR" != "skipped" ]; then + echo + echo "The build failed and Claude's repair round (${REPAIR}) left no new branch; this is the unrepaired rebase." + fi echo if [ -s "$verify" ]; then cat "$verify"; else echo "The checker did not run."; fi echo From d9580fdf08727a512052c21427bd205dad1e3bd1 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Oct 2026 14:41:13 -0700 Subject: [PATCH 3/3] automation: add the reusable rebase build workflow The previous commit moved the build job into rebase-build.yml and calls it twice, but the file itself never made it into the commit: the kernel's .gitignore ignores dot-directories, so it has to be added with -f. Without it, upstream-rebase.yml calls a workflow that does not exist. --- .github/workflows/rebase-build.yml | 117 +++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 .github/workflows/rebase-build.yml diff --git a/.github/workflows/rebase-build.yml b/.github/workflows/rebase-build.yml new file mode 100644 index 00000000000000..2b07628fb92109 --- /dev/null +++ b/.github/workflows/rebase-build.yml @@ -0,0 +1,117 @@ +name: Rebase build + +# The independent build of a rebase result, called by upstream-rebase.yml: +# once for Claude's rebase and, if that fails and Claude repairs the branch, +# once more for the repaired branch. +# +# It builds x86_64 and arm64 vmlinux with kernel-build.sh, taken from the +# commit the calling run started from, never from the tree under test. Each +# build's log is kept as an artifact, so a repair round can read what broke. + +on: + workflow_call: + inputs: + old: + description: The downstream tip the rebase started from + type: string + required: true + up: + description: The upstream commit the result sits on + type: string + required: true + result: + description: The commit to build + type: string + required: true + bundle: + description: The artifact holding rebase.bundle with the result + type: string + required: true + logs: + description: Prefix for the build log artifacts; the arch is appended + type: string + required: true + upstream_url: + type: string + required: true + upstream_branch: + type: string + required: true + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + arch: [x86_64, arm64] + name: build (${{ matrix.arch }}) + steps: + - name: Harden runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + ref: ${{ inputs.old }} + fetch-depth: 0 + filter: blob:none + persist-credentials: false + + - name: Trusted tools + run: | + set -euo pipefail + git fetch --no-tags --filter=blob:none origin "$GITHUB_SHA" + mkdir "$RUNNER_TEMP/tools" + git archive "$GITHUB_SHA" .github/scripts | tar -x -C "$RUNNER_TEMP/tools" --strip-components=1 + + - name: Download + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ inputs.bundle }} + path: ${{ runner.temp }}/in + + - name: Check out the result + env: + UPSTREAM_URL: ${{ inputs.upstream_url }} + UPSTREAM_BRANCH: ${{ inputs.upstream_branch }} + UP: ${{ inputs.up }} + RESULT: ${{ inputs.result }} + run: | + set -euo pipefail + git fetch --no-tags "$UPSTREAM_URL" "$UPSTREAM_BRANCH" + git merge-base --is-ancestor "$UP" FETCH_HEAD + git fetch "$RUNNER_TEMP/in/rebase.bundle" rebase-result:rebase-result + [ "$(git rev-parse rebase-result)" = "$RESULT" ] || { echo "bundle does not hold $RESULT"; exit 1; } + git checkout --detach "$RESULT" + + - name: Install toolchains + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential bc bison flex libelf-dev libssl-dev python3 \ + gcc-aarch64-linux-gnu + + - name: Build + env: + ARCH: ${{ matrix.arch }} + run: | + set -euo pipefail + bash "$RUNNER_TEMP/tools/scripts/kernel-build.sh" "$ARCH" "$RUNNER_TEMP/obj" 2>&1 | + tee "$RUNNER_TEMP/build-$ARCH.log" + + - name: Upload the log + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ inputs.logs }}-${{ matrix.arch }} + path: ${{ runner.temp }}/build-${{ matrix.arch }}.log + if-no-files-found: ignore + retention-days: 30