docs/superpowers/plans/2026-08-15-pr-label-controlled-tap-asan.md
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make ci:asan select an ASAN-instrumented standard TAP build in the existing CI-builds handoff, without adding label triggers or changing individual fan-out workflows.
Architecture: Add a small, independently tested shell resolver on the GH-Actions branch. The reusable ci-builds.yml invokes it before its build matrix, then passes its normal or asan result to the existing standard TAP build. The ASAN cache key is distinct, while artifact handoff names remain unchanged so downstream consumers receive the selected artifact transparently.
Tech Stack: GitHub Actions reusable workflows, GitHub CLI REST API, Bash, jq, Actions cache/artifacts.
GH-Actions branch; v3.0 continues to call that reusable workflow.ci:asan.labeled or unlabeled trigger types; an empty commit starts a fresh label-selected run.ci:asan, select normal mode.CI-unit-tests-asan-coverage unchanged.ubuntu24-tap-genai-gcov handoff in this change. MySQLX and the other nonstandard variants remain follow-up work._asan suffix only to the Actions cache key, not artifact names..github/scripts/resolve-tap-build-mode.bash — receives trusted status and the serialized triggering GitHub context; prints normal or asan to stdout and writes its reason to stderr..github/scripts/tests/test-resolve-tap-build-mode.bash — hermetic Bash tests for normal, labeled, untrusted, non-PR, and label-query-failure cases..github/workflows/ci-builds.yml — checks out the trusted control script, exposes its result as a job output, passes it into the build matrix, isolates ASAN caches, and injects sanitizer flags only in the standard TAP matrix leg.doc/GH-Actions/README.md — documents the opt-in label, the explicit empty-commit rerun behavior, and the scope boundary.Files:
.github/scripts/resolve-tap-build-mode.bash.github/scripts/tests/test-resolve-tap-build-mode.bashInterfaces:
Consumes: TRUSTED (true or false), optional TRIGGER_JSON, GITHUB_REPOSITORY, and optional GH_BIN (defaults to gh).
Produces: exactly normal or asan on stdout; diagnostic reason on stderr; nonzero only when a trusted PR-backed label lookup cannot be completed.
Uses: gh api "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}" --jq '.labels[].name' to retrieve current PR labels.
Step 1: Write the resolver tests first
Create a Bash test file that makes a temporary executable gh stub and invokes the resolver through its public environment interface. Include these assertions:
assert_mode() {
local expected="$1"; shift
local actual
actual=$("$@")
test "$actual" = "$expected" || {
echo "expected $expected, got $actual" >&2
exit 1
}
}
pr_trigger='{"event":{"workflow_run":{"pull_requests":[{"number":42}]}}}'
non_pr_trigger='{"event":{"workflow_run":{"pull_requests":[]}}}'
assert_mode normal env TRUSTED=false TRIGGER_JSON="$pr_trigger" \
GITHUB_REPOSITORY=sysown/proxysql GH_BIN="$stub" "$resolver"
assert_mode normal env TRUSTED=true GITHUB_REPOSITORY=sysown/proxysql \
GH_BIN="$stub" "$resolver"
assert_mode normal env TRUSTED=true TRIGGER_JSON="$non_pr_trigger" \
GITHUB_REPOSITORY=sysown/proxysql GH_BIN="$stub" "$resolver"
assert_mode normal env TRUSTED=true TRIGGER_JSON="$pr_trigger" GH_LABELS='bug' \
GITHUB_REPOSITORY=sysown/proxysql GH_BIN="$stub" "$resolver"
assert_mode normal env TRUSTED=true TRIGGER_JSON="$pr_trigger" GH_LABELS='ci:asan-extra' \
GITHUB_REPOSITORY=sysown/proxysql GH_BIN="$stub" "$resolver"
assert_mode asan env TRUSTED=true TRIGGER_JSON="$pr_trigger" GH_LABELS='bug ci:asan' \
GITHUB_REPOSITORY=sysown/proxysql GH_BIN="$stub" "$resolver"
Make the stub emit each space-separated GH_LABELS entry on its own line (for example, tr ' ' '\n' <<<"${GH_LABELS:-}"), matching gh api --jq '.labels[].name'; it exits nonzero when GH_STUB_FAIL=1. Assert that the failure case exits nonzero; this is the guard against a false-green normal build.
Run:
bash .github/scripts/tests/test-resolve-tap-build-mode.bash
Expected: failure because .github/scripts/resolve-tap-build-mode.bash does not yet exist.
Implement the following control flow in .github/scripts/resolve-tap-build-mode.bash:
#!/usr/bin/env bash
set -euo pipefail
emit_normal() {
echo "CI TAP build mode: normal ($1)" >&2
printf '%s\n' normal
}
if [[ "${TRUSTED:-false}" != true ]]; then
emit_normal 'untrusted caller'; exit 0
fi
if [[ -z "${TRIGGER_JSON:-}" ]]; then
emit_normal 'direct dispatch'; exit 0
fi
pr_number=$(jq -r '.event.workflow_run.pull_requests[0].number // empty' <<<"${TRIGGER_JSON}") \
|| { echo 'invalid trigger JSON' >&2; exit 1; }
if [[ -z "$pr_number" ]]; then
emit_normal 'non-PR trigger'; exit 0
fi
labels=$("${GH_BIN:-gh}" api "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}" \
--jq '.labels[].name') \
|| { echo "unable to read labels for PR #${pr_number}" >&2; exit 1; }
if grep -Fxq 'ci:asan' <<<"${labels}"; then
echo "CI TAP build mode: asan (PR #${pr_number} has ci:asan)" >&2
printf '%s\n' asan
else
emit_normal "PR #${pr_number} has no ci:asan"
fi
Mark the resolver and test executable.
Run:
bash .github/scripts/tests/test-resolve-tap-build-mode.bash
Expected: all seven cases pass: untrusted, direct dispatch, non-PR, ordinary PR, near-matching label, labeled PR, and API failure.
git add .github/scripts/resolve-tap-build-mode.bash \
.github/scripts/tests/test-resolve-tap-build-mode.bash
git commit -m "ci: resolve TAP ASAN mode from PR label"
Files:
.github/workflows/ci-builds.yml.github/scripts/tests/test-resolve-tap-build-mode.bashInterfaces:
Consumes: resolve-tap-mode.outputs.mode, constrained to normal or asan by Task 1.
Produces: the existing ci-builds-handoff-${SHA}-ubuntu24-tap-genai-gcov-{src,test} artifacts, containing the normal or ASAN binary for that run.
Preserves: all fan-out HANDOFF_VARIANT: ubuntu24-tap-genai-gcov references and CI-unit-tests-asan-coverage.
Step 1: Add the mode-resolution job before builds
Add resolve-tap-mode with one mode job output. Check out only the trusted GH-Actions control script—not PR code—then run it with serialized caller context:
resolve-tap-mode:
runs-on: ubuntu-24.04
outputs:
mode: ${{ steps.resolve.outputs.mode }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
ref: GH-Actions
path: ci-control
sparse-checkout: |
.github/scripts/resolve-tap-build-mode.bash
- id: resolve
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TRUSTED: ${{ inputs.trusted }}
TRIGGER_JSON: ${{ inputs.trigger }}
run: |
mode="$(ci-control/.github/scripts/resolve-tap-build-mode.bash)"
printf 'mode=%s\n' "$mode" >> "$GITHUB_OUTPUT"
Set GITHUB_REPOSITORY through the standard Actions environment and run
chmod +x ci-control/.github/scripts/resolve-tap-build-mode.bash immediately
before invoking it.
Run:
bash .github/scripts/tests/test-resolve-tap-build-mode.bash
Expected: all resolver cases remain green. Inspect the workflow diff to confirm the control checkout is pinned to GH-Actions, so a PR cannot alter label-selection behavior.
Add needs: resolve-tap-mode to builds. Expose its output in the job environment and append a suffix only for sanitizer cache entries:
env:
TAP_BUILD_MODE: ${{ needs.resolve-tap-mode.outputs.mode }}
BLDCACHE: ${{ inputs.trigger && fromJson(inputs.trigger).event.workflow_run.head_sha || github.sha }}_${{ matrix.dist }}${{ matrix.type }}${{ needs.resolve-tap-mode.outputs.mode == 'asan' && '_asan' || '' }}
MATRIX: '(${{ matrix.dist }},${{ matrix.type }},${{ needs.resolve-tap-mode.outputs.mode }})'
Keep the ci-builds-handoff-* artifact name expressions unchanged. This prevents a cached normal binary from satisfying an ASAN build for the same SHA while retaining fan-out compatibility.
Replace the current matrix.type =~ "-asan" condition in the Build step with an explicit selected-mode guard before the GenAI and GCOV injections:
if [[ "${TAP_BUILD_MODE}" = 'asan' && "${{ matrix.dist }}${{ matrix.type }}" = 'ubuntu24-tap-genai-gcov' ]]; then
sed -i "/command/i \\ - WITHASAN=1" docker-compose.yml
sed -i "/command/i \\ - TEST_WITHASAN=1" docker-compose.yml
fi
Emit the selected mode and the matrix variant at the top of the Build step. Do not add flags to ubuntu22-tap-mysqlx, ubuntu22-tap, or non-TAP matrix entries in this implementation.
Run:
bash .github/scripts/tests/test-resolve-tap-build-mode.bash
docker run --rm -v "$PWD:/repo:ro" -w /repo rhysd/actionlint:1.7.7 \
.github/workflows/ci-builds.yml
Expected: resolver tests pass and actionlint reports no workflow syntax or expression errors.
git add .github/workflows/ci-builds.yml
git commit -m "ci: select TAP ASAN build from PR label"
Files:
doc/GH-Actions/README.mdInterfaces:
Documents: the exact ci:asan label, the empty-commit rerun rule, normal behavior for dispatch/push, and the standard-variant-only scope.
Depends on: Tasks 1 and 2.
Step 1: Add a concise “Opt-in TAP ASAN” section
Document these exact rules:
- Add `ci:asan` to an internal PR, then push an empty commit to request an
ASAN standard-TAP integration run.
- Removing or adding the label alone does not start CI.
- Without the label, push builds, and manual dispatches use the normal build.
- The label replaces the normal `ubuntu24-tap-genai-gcov` handoff for that
commit; it does not run a second fan-out.
- Unit ASAN coverage and MySQLX behavior are unchanged.
On an internal PR without ci:asan, push a commit and verify:
CI TAP build mode: normal
The build must publish the existing ubuntu24-tap-genai-gcov handoffs, and a representative existing consumer such as CI-mysql84-g1 must restore them successfully.
Add ci:asan, push an empty commit, and verify:
CI TAP build mode: asan
WITHASAN=1
TEST_WITHASAN=1
Confirm the artifact names remain ci-builds-handoff-<sha>-ubuntu24-tap-genai-gcov-{src,test} and the same representative fan-out workflow restores them.
Temporarily make the resolver’s gh api call fail in a branch-only test run. Confirm a trusted PR-backed ci-builds run fails in resolve-tap-mode, before the build matrix starts. Confirm CI-unit-tests-asan-coverage still runs under its existing trigger regardless of ci:asan.
git add doc/GH-Actions/README.md
git commit -m "docs(ci): explain label-selected TAP ASAN runs"