Skip to content

ci: mirror the GitHub-native release workflow from mono#9079 - #1124

Merged
haasonsaas merged 1 commit into
mainfrom
chore/mirror-release-workflow
Sep 14, 2026
Merged

haasonsaas merged 1 commit into
mainfrom
chore/mirror-release-workflow

Conversation

@haasonsaas

@haasonsaas haasonsaas commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Issue

Source PR: https://github.com/evalops/mono/pull/9079 (evalops/mono#9079) — products/maestro is the source of truth; this copies its release workflow to the mirror.

What changed

.github/workflows/release.yml replaced with the mono copy (GitHub-native release on owned runners, receipt metadata via scripts/create-release-metadata.mjs).

Why

maestro-ci on main failed linux-check: scripts/update-lifecycle-contract.test.mjs:50 expects the workflow to reference create-release-metadata.mjs; the mirror workflow predated mono#9079.

Verification

  • node --test scripts/update-lifecycle-contract.test.mjs → 3 pass, 0 fail
  • actionlint on the workflow → clean

🤖 Generated with Claude Code

https://claude.ai/code/session_014mGdZdLHUANa66S3RGuHMr


Devin Review

The mirror's release.yml predated dx-corp/mono#9079 (release through GitHub on
owned runners); scripts/update-lifecycle-contract.test.mjs expects the
workflow to publish receipt metadata via create-release-metadata.mjs, which
the stale copy lacked, so maestro-ci's linux-check failed on main.

Source: dx-corp/mono#9079 (products/maestro).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mGdZdLHUANa66S3RGuHMr
@haasonsaas
haasonsaas requested review from a team as code owners September 14, 2026 04:09
@haasonsaas
haasonsaas enabled auto-merge (squash) September 14, 2026 04:09
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 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-09-14T04:12:48.663482Z e2e74f9 PR opened
ℹ️ 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.

@haasonsaas
haasonsaas merged commit 73190e8 into main Sep 14, 2026
11 of 13 checks passed
@haasonsaas
haasonsaas deleted the chore/mirror-release-workflow branch September 14, 2026 04:11

@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: e2e74f9c0d

ℹ️ 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 on lines +129 to +130
- id: private-cargo
uses: ./.github/actions/configure-private-cargo-git

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore the missing local Cargo action

The binaries job now invokes ./.github/actions/configure-private-cargo-git, but a repo-wide git ls-tree -r d2ab24d -- .github/actions confirms that no action manifest exists at this path. Every tag or manual release will therefore stop during local-action resolution before building any binary; mirror the action into this repository or remove the private-only dependency.

Useful? React with 👍 / 👎.

Comment on lines +37 to +38
plan:
runs-on: ${{ vars.INTERNAL_CONFIRMATION_RUNNER || 'evalops-internal-arc' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict release dispatches to admitted source

When workflow_dispatch selects a non-main ref, this unguarded job proceeds and the later checkouts execute that ref; the deleted prepare job was the only code that restricted the repository/ref and verified protected-main or protected-release ancestry. Because subsequent jobs expose release secrets, OIDC, and contents: write, an unprotected branch can now provide the scripts that are built and signed. Restore the protected-source admission before any release job runs.

AGENTS.md reference: AGENTS.md:L7-L9

Useful? React with 👍 / 👎.

Comment on lines 371 to +375
- id: pack
run: |
PACK_JSON=$(npm pack --json)
echo "$PACK_JSON"
node -e 'const fs=require("node:fs"); const p=JSON.parse(fs.readFileSync("package.json","utf8")); p.name="@evalops/maestro"; fs.writeFileSync("package.json", JSON.stringify(p, null, "\t")+"\n")'
ALIAS_PACK_JSON=$(npm pack --json)
echo "$ALIAS_PACK_JSON"
git checkout -- package.json
{
echo "tarball=$(echo "$PACK_JSON" | jq -r '.[0].filename')"
echo "integrity=$(echo "$PACK_JSON" | jq -r '.[0].integrity')"
echo "alias_tarball=$(echo "$ALIAS_PACK_JSON" | jq -r '.[0].filename')"
echo "alias_integrity=$(echo "$ALIAS_PACK_JSON" | jq -r '.[0].integrity')"
} >> "$GITHUB_OUTPUT"
echo "tarball=$(echo "$PACK_JSON" | jq -r '.[0].filename')" >> "$GITHUB_OUTPUT"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Publish the packed package to npm

The replacement workflow only runs npm pack; there is no npm publish anywhere afterward. This breaks the existing public release flow because .github/workflows/tag-release.yml explicitly dispatches release.yml when the version is absent from npm, so even a successful run would merely attach a tarball to a GitHub release while leaving the package unpublished and eligible for repeated dispatches. Restore the registry publisher and its post-publish verification.

AGENTS.md reference: AGENTS.md:L13-L14

Useful? React with 👍 / 👎.

exit 1
fi
sleep 2
identity="^https://github.com/evalops/mono/\\.github/workflows/maestro-release.yml@"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Verify against this workflow's certificate identity

When this active workflow runs in evalops/maestro, keyless cosign signatures are issued for the public evalops/maestro/.github/workflows/release.yml workflow, but this regex accepts only the private evalops/mono/.github/workflows/maestro-release.yml identity. Consequently the immediately following verify-blob call rejects the signatures just created and the GitHub release step is unreachable; use the executing workflow's identity or disable this mirrored file as an entrypoint.

Useful? React with 👍 / 👎.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 7 potential issues.

Devin Review

Comment on lines +469 to +475
identity="^https://github.com/evalops/mono/\\.github/workflows/maestro-release.yml@"
verify() {
cosign verify-blob \
--bundle "$2" \
--certificate-identity-regexp "${identity}" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
"$1"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Wrong identity blocks every release

Every run signs with this workflow's OIDC identity, but identity accepts only the private mono workflow. Verification rejects every bundle, so no release completes.

Learn more

A keyless cosign bundle records the GitHub workflow that requested its OIDC certificate. This file runs as evalops/maestro/.github/workflows/release.yml, while the regular expression accepts only evalops/mono/.github/workflows/maestro-release.yml. The immediately preceding cosign sign-blob calls therefore produce certificates that the following verification cannot accept. The workflow exits before softprops/action-gh-release on every trigger.

Example: A v0.10.87 tag starts this workflow. Cosign signs with an identity beginning https://github.com/evalops/maestro/.github/workflows/release.yml@, but verification requires the evalops/mono prefix and fails.

Recommended fix: Match the identity of the workflow that actually executes this file. If both repositories legitimately execute equivalent workflows, explicitly allow both exact repository/workflow identities without broadening the expression beyond those workflows.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +290 to +291
- name: Conformance against the exact native release artifact
run: node scripts/run-runtime-conformance.mjs --binary release-binaries/maestro-linux-x64

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Downloaded binary cannot run

download-artifact removes maestro-linux-x64's executable mode, then conformance launches it directly. The publish job fails before packaging or release creation.

Learn more

GitHub artifact transfer does not preserve Unix file modes. The binary is executable in the matrix job, but the downloaded copy is a regular non-executable file. startRuntime passes that path directly to spawn, so the process cannot start.

Example: The build uploads maestro-linux-x64 as mode 0755. The publish job downloads it as mode 0644, and conformance receives EACCES instead of a readiness URL.

Recommended fix: Restore executable permission on downloaded binaries before any direct execution. Keeping the change before conformance also lets materialize-native-package.mjs package the same tested bytes later.

Suggested change
- name: Conformance against the exact native release artifact
run: node scripts/run-runtime-conformance.mjs --binary release-binaries/maestro-linux-x64
- name: Conformance against the exact native release artifact
run: |
chmod +x release-binaries/maestro-linux-x64
node scripts/run-runtime-conformance.mjs --binary release-binaries/maestro-linux-x64
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +259 to +265
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: maestro-verified-native
path: release-binaries/*
name: maestro-${{ matrix.platform }}
path: |
dist/release/maestro-${{ matrix.platform }}
dist/release/smoked-${{ matrix.platform }}.txt
dist/release/rustc-${{ matrix.platform }}.txt

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 macOS artifacts always fail verification

A Darwin matrix leg uploads no signing or notarization markers. The coverage verifier requires both, so configured macOS releases never publish.

Learn more

The plan conditionally adds Darwin builds when a macOS runner variable is configured. Each matrix leg uploads only maestro-<platform>, smoked-<platform>.txt, and rustc-<platform>.txt. The publish job supplies the same platform list to the coverage verifier. For every Darwin platform, that verifier also requires signed-<platform>.json and notarized-<platform>.json, but no new step creates or uploads either file.

Example: Setting RELEASE_MACOS_ARM64_RUNNER=self-hosted-mac adds darwin-arm64. Its build succeeds, but publish reports darwin-arm64: no macOS Developer ID signature marker and exits.

Recommended fix: Add Developer ID signing and Apple notarization to each Darwin leg, generate the marker formats accepted by verify-release-smoke-coverage.mjs, and upload both markers. Keep Darwin platforms disabled until those steps and credentials are available.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

package-manager-cache: false
registry-url: https://registry.npmjs.org
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Manual release uses unrelated source

A manual dispatch checks out its selected branch while REQUESTED names the release tag. Binaries and metadata can therefore disagree with the released tag.

Learn more

A manual workflow dispatch has two independent values: the selected Git ref becomes github.sha, while the version input becomes the release tag. The checkout uses the former, and all builds plus SOURCE_SHA use that checkout. The package-version check does not prove that the checked-out commit belongs to the requested tag. The release action can then attach those branch-built assets to v<version>.

Example: Main and v0.10.86 both still declare version 0.10.86, but main contains one extra commit. Dispatching version 0.10.86 from main builds the extra commit, records its SHA, and uploads those bytes under the existing v0.10.86 release.

Recommended fix: Normalize the requested version first, fetch and resolve the corresponding immutable tag, and checkout that resolved commit for manual runs. Use the resolved commit for all source metadata, and verify the tag still resolves to it before publishing.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +3 to 9
# Not a GitHub Actions entrypoint. The live private tarball release is
# evalops/mono .github/workflows/maestro-release.yml.

on:
repository_dispatch:
types: [maestro-signed-release]
push:
tags: ["v*.*.*"]
workflow_dispatch:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Entrypoint ownership is unclear

The header denies entrypoint status, while on.push.tags and workflow_dispatch execute this workflow directly. Clarify whether the public mirror publishes releases.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +485 to 489
- uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda
with:
tag_name: ${{ needs.prepare.outputs.release_tag }}
name: Maestro ${{ needs.prepare.outputs.release_version }}
tag_name: v${{ steps.version.outputs.value }}
name: Maestro ${{ steps.version.outputs.value }}
generate_release_notes: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Preview releases lose prerelease status

Alpha and beta versions reach action-gh-release without prerelease. GitHub presents them as normal releases despite their signed preview channel.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 376 to +377
- name: Smoke packed package without JS runtime
env:
MAESTRO_REQUIRE_PACKAGED_TUI: "1"
NPM_CONFIG_FETCH_RETRIES: "1"
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: "2000"
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: "1000"
NPM_CONFIG_FETCH_TIMEOUT: "30000"
NPM_CONFIG_REGISTRY: https://registry.npmjs.org
PACKED_TARBALL: ${{ steps.pack.outputs.tarball }}
run: node scripts/smoke-packed-cli.js "$PACKED_TARBALL"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
retention-days: 14
name: npm-tarball-${{ needs.prepare.outputs.release_tag }}
overwrite: true
path: |
${{ steps.pack.outputs.tarball }}
${{ steps.pack.outputs.alias_tarball }}
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
PACKAGE_NAME: ${{ needs.prepare.outputs.package_name }}
ALIAS_PACKAGE_NAME: "@evalops/maestro"
PACKED_INTEGRITY: ${{ steps.pack.outputs.integrity }}
ALIAS_PACKED_INTEGRITY: ${{ steps.pack.outputs.alias_integrity }}
RELEASE_VERSION: ${{ needs.prepare.outputs.release_version }}
NPM_TAG: ${{ needs.prepare.outputs.npm_tag }}
TARBALL: ${{ steps.pack.outputs.tarball }}
ALIAS_TARBALL: ${{ steps.pack.outputs.alias_tarball }}
NPM_CONFIG_FETCH_RETRIES: "1"
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: "2000"
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: "1000"
NPM_CONFIG_FETCH_TIMEOUT: "30000"
NPM_CONFIG_REGISTRY: https://registry.npmjs.org
run: node scripts/smoke-release-native-only.mjs bin/maestro

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Packed install gate is bypassed

The smoke runs the checkout's bin/maestro, not an installation of the generated tarball. Packaging defects can pass this required release gate.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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