Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .agents/skills/nemoclaw-maintainer-cut-release-tag/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,17 @@ The release is one signed annotated semver tag on an already-merged `origin/main
When a release admin creates or moves `lkg` to a commit carrying a `vX.Y.Z` tag, the `Release / LKG Brev Image` workflow dispatches the `Release Production Image` workflow in `brevdev/nemoclaw-image` on its `main` branch.
The dispatch passes the immutable semver tag instead of the mutable `lkg` tag.
The source workflow requires the `NEMOCLAW_IMAGE_DISPATCH_TOKEN` Actions secret with Actions read/write access to `brevdev/nemoclaw-image`; a missing secret fails before the API request, and the workflow summary never includes its value.
The trigger summary records the selected release tag, full commit SHA, target workflow, and dispatch result.
The trigger summary records the selected release tag, full commit SHA, target workflow, dispatch result, downstream run ID, and a direct link to the downstream run.
After `lkg` promotion, find and wait for the source trigger run using the promoted commit:

```bash
gh run list --repo NVIDIA/NemoClaw --workflow release-lkg-brev-image.yaml --commit <lkg-commit> --event push --limit 1 --json databaseId,status,conclusion,url
gh run watch <source-run-id> --repo NVIDIA/NemoClaw --exit-status
gh run view <source-run-id> --repo NVIDIA/NemoClaw --log
```

Extract the exact `https://github.com/brevdev/nemoclaw-image/actions/runs/<run-id>` URL printed by the source run, give that link to the maintainer immediately, and tell them to follow it to terminal success.
Treat dispatch acceptance as an intermediate state, not proof of production image promotion: the downstream run must succeed and its summary must show successful runtime E2E validation and promotion of the `nemoclaw-brev-cpu` image family.
A rejected dispatch fails the trigger run but does not move or roll back `lkg`.
Deleting `lkg` does not dispatch an image build.
The downstream scheduled reconciliation remains available if the event-driven dispatch fails or is delayed.
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release-lkg-brev-image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
dispatch-production-image:
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.event.deleted == false }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out LKG target
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand Down
44 changes: 32 additions & 12 deletions scripts/release-lkg-brev-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ SUMMARY_PATH="${GITHUB_STEP_SUMMARY:-/dev/null}"
lkg_commit="unresolved"
release_tag="none"
dispatch_result="not attempted"
downstream_run_id="unavailable"
downstream_run_url=""

write_summary() {
{
Expand All @@ -21,6 +23,13 @@ write_summary() {
echo "- Release tag: \`$release_tag\`"
echo "- Target: \`$TARGET_REPOSITORY/.github/workflows/$TARGET_WORKFLOW@$TARGET_REF\`"
echo "- Dispatch result: \`$dispatch_result\`"
if [[ -n "$downstream_run_url" ]]; then
echo "- Downstream run: [$downstream_run_id]($downstream_run_url)"
echo
echo "Follow the downstream run to terminal success and verify production image promotion."
else
echo "- Downstream run: \`$downstream_run_id\`"
fi
} >>"$SUMMARY_PATH"
}

Expand Down Expand Up @@ -70,21 +79,32 @@ if ! command -v gh >/dev/null 2>&1; then
fail "GitHub CLI is required to dispatch $TARGET_REPOSITORY"
fi

payload="$(printf '{"ref":"%s","inputs":{"nemoclaw_ref":"%s"}}' "$TARGET_REF" "$release_tag")"
payload="$(printf '{"ref":"%s","inputs":{"nemoclaw_ref":"%s"},"return_run_details":true}' "$TARGET_REF" "$release_tag")"
endpoint="repos/$TARGET_REPOSITORY/actions/workflows/$TARGET_WORKFLOW/dispatches"

if ! printf '%s\n' "$payload" \
| env -u GH_DEBUG GH_TOKEN="$NEMOCLAW_IMAGE_DISPATCH_TOKEN" gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"$endpoint" \
--input - \
>/dev/null; then
if ! dispatch_details="$(
printf '%s\n' "$payload" \
| env -u GH_DEBUG GH_TOKEN="$NEMOCLAW_IMAGE_DISPATCH_TOKEN" gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2026-03-10" \
"$endpoint" \
--input - \
--jq '[.workflow_run_id, .html_url] | @tsv'
)"; then
dispatch_result="rejected"
fail "GitHub rejected the production image dispatch to $TARGET_REPOSITORY/$TARGET_WORKFLOW"
fi

dispatch_result="accepted (HTTP 204)"
printf 'release-lkg-brev-image: dispatched %s for %s (%s)\n' \
"$TARGET_WORKFLOW" "$release_tag" "$lkg_commit"
IFS=$'\t' read -r downstream_run_id downstream_run_url <<<"$dispatch_details"
expected_run_url="https://github.com/$TARGET_REPOSITORY/actions/runs/$downstream_run_id"
if [[ ! "$downstream_run_id" =~ ^[0-9]+$ || "$downstream_run_url" != "$expected_run_url" ]]; then
downstream_run_id="unavailable"
downstream_run_url=""
dispatch_result="rejected (invalid run details)"
fail "GitHub accepted the dispatch but did not return valid downstream run details"
fi

dispatch_result="accepted (HTTP 200)"
printf 'release-lkg-brev-image: dispatched %s for %s (%s): %s\n' \
"$TARGET_WORKFLOW" "$release_tag" "$lkg_commit" "$downstream_run_url"
57 changes: 55 additions & 2 deletions test/release-lkg-brev-image.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Workflow = {
{
if?: string;
steps?: WorkflowStep[];
"timeout-minutes"?: number;
}
>;
on?: {
Expand Down Expand Up @@ -95,6 +96,11 @@ if [[ "\${GH_EXIT_CODE:-0}" != "0" ]]; then
echo "HTTP 403: dispatch denied" >&2
exit "$GH_EXIT_CODE"
fi
if [[ -n "\${GH_OUTPUT+x}" ]]; then
printf '%s\n' "$GH_OUTPUT"
else
printf '123456789\thttps://github.com/brevdev/nemoclaw-image/actions/runs/123456789\n'
fi
`,
"utf8",
);
Expand Down Expand Up @@ -156,22 +162,34 @@ describe("LKG production image dispatch", () => {
"-H",
"Accept: application/vnd.github+json",
"-H",
"X-GitHub-Api-Version: 2022-11-28",
"X-GitHub-Api-Version: 2026-03-10",
"repos/brevdev/nemoclaw-image/actions/workflows/build-scheduled.yml/dispatches",
"--input",
"-",
"--jq",
"[.workflow_run_id, .html_url] | @tsv",
]);
expect(JSON.parse(fs.readFileSync(fixture.inputPath, "utf8"))).toEqual({
ref: "main",
inputs: { nemoclaw_ref: "v0.0.10" },
return_run_details: true,
});
const summary = fs.readFileSync(fixture.summaryPath, "utf8");
expect(summary).toContain(`LKG commit: \`${fixture.commit}\``);
expect(summary).toContain("Release tag: `v0.0.10`");
expect(summary).toContain(
"Target: `brevdev/nemoclaw-image/.github/workflows/build-scheduled.yml@main`",
);
expect(summary).toContain("Dispatch result: `accepted (HTTP 204)`");
expect(summary).toContain("Dispatch result: `accepted (HTTP 200)`");
expect(summary).toContain(
"Downstream run: [123456789](https://github.com/brevdev/nemoclaw-image/actions/runs/123456789)",
);
expect(summary).toContain(
"Follow the downstream run to terminal success and verify production image promotion.",
);
expect(result.stdout).toContain(
"https://github.com/brevdev/nemoclaw-image/actions/runs/123456789",
);
expect(`${result.stdout}${result.stderr}${summary}`).not.toContain("test-dispatch-token");
});

Expand Down Expand Up @@ -233,6 +251,40 @@ describe("LKG production image dispatch", () => {
expect(fs.readFileSync(fixture.summaryPath, "utf8")).toContain("Dispatch result: `rejected`");
});

it("fails when GitHub omits valid downstream run details (#6772)", () => {
const fixture = createFixture();
tag(fixture, "v0.0.1");

const result = runDispatch(fixture, { GH_OUTPUT: "null\tnull" });

expect(result.status).not.toBe(0);
expect(result.stderr).toContain(
"GitHub accepted the dispatch but did not return valid downstream run details",
);
const summary = fs.readFileSync(fixture.summaryPath, "utf8");
expect(summary).toContain("Dispatch result: `rejected (invalid run details)`");
expect(summary).toContain("Downstream run: `unavailable`");
expect(summary).not.toContain("actions/runs/");
});

Comment thread
coderabbitai[bot] marked this conversation as resolved.
it("rejects a downstream run URL that does not match its numeric ID (#6772)", () => {
const fixture = createFixture();
tag(fixture, "v0.0.1");

const result = runDispatch(fixture, {
GH_OUTPUT: "123456789\thttps://github.com/brevdev/nemoclaw-image/actions/runs/987654321",
});

expect(result.status).not.toBe(0);
expect(result.stderr).toContain(
"GitHub accepted the dispatch but did not return valid downstream run details",
);
const summary = fs.readFileSync(fixture.summaryPath, "utf8");
expect(summary).toContain("Dispatch result: `rejected (invalid run details)`");
expect(summary).toContain("Downstream run: `unavailable`");
expect(summary).not.toContain("actions/runs/");
});

// source-shape-contract: security -- The secret-bearing LKG trigger must stay canonical, deletion-safe, read-only, and immutable
it("keeps LKG dispatch inside the trusted secret boundary (#6772)", () => {
const workflow = readYaml<Workflow>(".github/workflows/release-lkg-brev-image.yaml");
Expand All @@ -245,6 +297,7 @@ describe("LKG production image dispatch", () => {
expect(job.if).toBe(
"${{ github.repository == 'NVIDIA/NemoClaw' && github.event.deleted == false }}",
);
expect(job["timeout-minutes"]).toBe(5);
expect(checkout?.uses).toMatch(/^actions\/checkout@[0-9a-f]{40}$/u);
expect(checkout?.with).toEqual({
ref: "${{ github.sha }}",
Expand Down