This is an automated email from the ASF dual-hosted git repository.
epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-mcp.git
The following commit(s) were added to refs/heads/main by this push:
new b862e15 fix(ci): repair release workflows that silently skipped or
never ran (#174)
b862e15 is described below
commit b862e156db9b4d89d81261a108907694357ceab7
Author: Aditya Parikh <[email protected]>
AuthorDate: Fri Sep 11 10:06:34 2026 -0400
fix(ci): repair release workflows that silently skipped or never ran (#174)
Verified findings from a CodeRabbit review, rebased onto current main.
Jobs that never run:
- release-publish.yml: publish-mcp-registry gates on
needs.validate-release.outputs.proceed but does not list validate-release
in needs:. A needs.<job> reference to a job absent from needs: resolves to
empty rather than erroring, so the condition is always falsy and MCP
Registry publishing never happens.
- atr-release.yml / atr-release-test.yml: finish-release has no needs: at
all, only `if: always() && inputs.skip_vote`, so it announces the release
in parallel with the jobs that produce it.
Steps that fail outright:
- atr-release.yml uses actions/upload-artifact@v3, retired by GitHub. Pinned
to the SHA already used elsewhere in this repo.
- The MCP Publisher download 404s today: the asset is
mcp-publisher_linux_amd64.tar.gz, not mcp-publisher-linux-amd64.tar.gz.
Because `curl -L` has no --fail and is piped straight into tar, the error
page is streamed to tar instead of failing the step. Pinned to v1.8.0 and
verified against the release's published checksum.
Other correctness and hardening:
- apache/tooling-actions upload-to-atr / release-on-atr were referenced at
@main; pinned to a commit SHA. ASF's Actions allow-list matches by exact
SHA, and a mutable ref is not reproducible.
- Pass workflow_dispatch inputs through step env instead of interpolating
them into shell text.
- The Docker Hub publish step warned and exited 0 when credentials were
absent, so a release could report success while publishing nothing; it now
fails. Credentials move to JIB_TO_AUTH_* env so the token is not in the
process argument list.
- The build.gradle.kts version sed was unanchored; restrict it to the
top-level `version = ` declaration.
- Drop an unresolved ${VOTE_THREAD_NOTE} from the announcement heredoc; the
vote thread URL is already appended by the block below it.
- The release step rewrote only .packages[0].version in server.json, leaving
the second package on -SNAPSHOT so the registry entry advertised an image
tag that is never published. Rewrite every package and fail if a SNAPSHOT
survives.
Signed-off-by: Aditya Parikh <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
.github/workflows/atr-release-test.yml | 19 +++++--
.github/workflows/atr-release.yml | 26 +++++++---
.github/workflows/release-publish.yml | 92 ++++++++++++++++++++++------------
server.json | 4 +-
4 files changed, 93 insertions(+), 48 deletions(-)
diff --git a/.github/workflows/atr-release-test.yml
b/.github/workflows/atr-release-test.yml
index cfe4882..a37d51c 100644
--- a/.github/workflows/atr-release-test.yml
+++ b/.github/workflows/atr-release-test.yml
@@ -115,8 +115,11 @@ jobs:
fetch-depth: 0 # Full history for proper tagging
- name: Create test tag if not exists
+ env:
+ RELEASE_VERSION: ${{ inputs.release_version }}
+ RELEASE_CANDIDATE: ${{ inputs.release_candidate }}
run: |
- TEST_TAG="v${{ inputs.release_version }}-${{
inputs.release_candidate }}"
+ TEST_TAG="v${RELEASE_VERSION}-${RELEASE_CANDIDATE}"
if ! git rev-parse "${TEST_TAG}" >/dev/null 2>&1; then
echo "Creating test tag: ${TEST_TAG}"
git config user.email "[email protected]"
@@ -221,7 +224,7 @@ jobs:
- name: Upload artifacts to ATR (Real)
if: ${{ !inputs.dry_run }}
- uses: apache/tooling-actions/upload-to-atr@main
+ uses:
apache/tooling-actions/upload-to-atr@81f0a6cf2202b362ee6434b82c271b1849133581 #
main @ 2026-07-27
with:
project: ${{ env.ATR_PROJECT_NAME }}
version: ${{ inputs.release_version }}-${{
inputs.release_candidate }}
@@ -261,7 +264,9 @@ jobs:
name: Vote Instructions (TEST)
runs-on: ubuntu-latest
needs: [ compose-release ]
- if: ${{ !inputs.skip_vote && (success() || inputs.skip_compose) }}
+ # A skipped `needs` job skips its dependents unless the if: uses
always().
+ # Without it the skip_compose=true path could never reach this job.
+ if: ${{ always() && !inputs.skip_vote && (needs.compose-release.result
== 'success' || inputs.skip_compose) }}
steps:
- name: Generate test vote email
@@ -307,6 +312,10 @@ jobs:
finish-release:
name: Finalize Release (TEST)
runs-on: ubuntu-latest
+ # Must wait for the compose and vote jobs even though both may be
skipped
+ # (skip_compose / skip_vote). always() keeps this reachable in that
case,
+ # but without needs: it would otherwise start in parallel with them.
+ needs: [ compose-release, vote-instructions ]
if: ${{ always() && inputs.skip_vote }}
steps:
@@ -343,7 +352,7 @@ jobs:
- name: Resolve vote and announce on ATR (Real)
if: ${{ !inputs.dry_run }}
- uses: apache/tooling-actions/release-on-atr@main
+ uses:
apache/tooling-actions/release-on-atr@81f0a6cf2202b362ee6434b82c271b1849133581
# main @ 2026-07-27
with:
version: ${{ inputs.release_version }}
atr-host: release-test.apache.org
@@ -382,7 +391,7 @@ jobs:
# Test main platform
echo "1. Testing main platform
(https://release-test.apache.org):"
- if curl -s -o /dev/null -w " HTTP Status:
%{http_code}\n" https://release-test.apache.org; then
+ if curl -fsS --max-time 15 -o /dev/null -w " HTTP
Status: %{http_code}\n" https://release-test.apache.org; then
echo " ✅ Platform is reachable"
else
echo " ❌ Platform unreachable"
diff --git a/.github/workflows/atr-release.yml
b/.github/workflows/atr-release.yml
index 9053e1c..9f901f9 100644
--- a/.github/workflows/atr-release.yml
+++ b/.github/workflows/atr-release.yml
@@ -158,9 +158,13 @@ jobs:
fetch-depth: 0 # Full history for proper tagging
- name: Verify release tag
+ env:
+ RELEASE_VERSION: ${{ inputs.release_version }}
+ RELEASE_CANDIDATE: ${{ inputs.release_candidate }}
run: |
- if ! git rev-parse "v${{ inputs.release_version }}-${{
inputs.release_candidate }}" >/dev/null 2>&1; then
- echo "ERROR: Release tag not found: v${{
inputs.release_version }}-${{ inputs.release_candidate }}"
+ RELEASE_TAG="v${RELEASE_VERSION}-${RELEASE_CANDIDATE}"
+ if ! git rev-parse "${RELEASE_TAG}" >/dev/null 2>&1; then
+ echo "ERROR: Release tag not found: ${RELEASE_TAG}"
exit 1
fi
echo "✓ Release tag verified"
@@ -236,7 +240,7 @@ jobs:
# gpg --armor --detach-sign build/distributions/*.jar
- name: Upload artifacts to ATR
- uses: apache/tooling-actions/upload-to-atr@main
+ uses:
apache/tooling-actions/upload-to-atr@81f0a6cf2202b362ee6434b82c271b1849133581 #
main @ 2026-07-27
with:
asf-uid: ${{ secrets.ASF_USERNAME }}
project: ${{ env.ATR_PROJECT_NAME }}
@@ -246,7 +250,7 @@ jobs:
ssh-port: 2222
- name: Upload artifacts for review
- uses: actions/upload-artifact@v3
+ uses:
actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # 6.0.0
with:
name: release-artifacts-${{ inputs.release_version }}-${{
inputs.release_candidate }}
path: build/distributions/
@@ -274,7 +278,9 @@ jobs:
name: Vote Instructions
runs-on: ubuntu-latest
needs: [ compose-release ]
- if: ${{ !inputs.skip_vote && (success() || inputs.skip_compose) }}
+ # A skipped `needs` job skips its dependents unless the if: uses
always().
+ # Without it the skip_compose=true path could never reach this job.
+ if: ${{ always() && !inputs.skip_vote && (needs.compose-release.result
== 'success' || inputs.skip_compose) }}
steps:
- name: Generate vote email template
@@ -329,7 +335,7 @@ jobs:
echo "**Note**: ATR voting automation is not yet
available. Manual process required." >> $GITHUB_STEP_SUMMARY
- name: Upload vote email template
- uses: actions/upload-artifact@v3
+ uses:
actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # 6.0.0
with:
name: vote-email-template
path: vote-email.txt
@@ -338,6 +344,10 @@ jobs:
finish-release:
name: Finalize and Announce Release
runs-on: ubuntu-latest
+ # Must wait for the compose and vote jobs even though both may be
skipped
+ # (skip_compose / skip_vote). always() keeps this reachable in that
case,
+ # but without needs: it would otherwise start in parallel with them.
+ needs: [ compose-release, vote-instructions ]
if: ${{ always() && inputs.skip_vote }} # Manual trigger after vote
passes
steps:
@@ -347,7 +357,7 @@ jobs:
ref: "v${{ inputs.release_version }}-${{
inputs.release_candidate }}"
- name: Resolve vote and announce on ATR
- uses: apache/tooling-actions/release-on-atr@main
+ uses:
apache/tooling-actions/release-on-atr@81f0a6cf2202b362ee6434b82c271b1849133581
# main @ 2026-07-27
with:
version: ${{ inputs.release_version }}
atr-host: release-test.apache.org
@@ -428,7 +438,7 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
echo "#### Distribution:" >> $GITHUB_STEP_SUMMARY
echo "- Apache Mirrors:
https://www.apache.org/dyn/closer.lua/solr/mcp/${{ inputs.release_version }}/"
>> $GITHUB_STEP_SUMMARY
- echo "- Docker Hub: `apache/solr-mcp:${{
inputs.release_version }}`" >> $GITHUB_STEP_SUMMARY
+ echo "- Docker Hub: \`apache/solr-mcp:${{
inputs.release_version }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "#### Next Steps:" >> $GITHUB_STEP_SUMMARY
echo "1. Send announcement to [email protected]" >>
$GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/release-publish.yml
b/.github/workflows/release-publish.yml
index 4c6c6c7..f835274 100644
--- a/.github/workflows/release-publish.yml
+++ b/.github/workflows/release-publish.yml
@@ -191,7 +191,7 @@ jobs:
run: |
# Ensure the Gradle project version matches the GA version (removes
any -SNAPSHOT)
# This keeps image tags and any generated artifacts consistent with
the voted release
- sed -i "s/version = \".*\"/version = \"${RELEASE_VERSION}\"/"
build.gradle.kts
+ sed -i "0,/^version = \".*\"/s//version = \"${RELEASE_VERSION}\"/"
build.gradle.kts
- name: Build project
run: ./gradlew build
@@ -206,34 +206,34 @@ jobs:
echo "Contact INFRA for setup requirements"
- name: Build and publish to Docker Hub (apache/solr-mcp)
+ env:
+ # Jib reads these directly; passing them as -Djib.to.auth.* would put
+ # the token in the process argument list, visible to anything on the
runner.
+ JIB_TO_AUTH_USERNAME: ${{ secrets.DOCKERHUB_APACHE_USERNAME }}
+ JIB_TO_AUTH_PASSWORD: ${{ secrets.DOCKERHUB_APACHE_TOKEN }}
+ RELEASE_VERSION: ${{ inputs.release_version }}
run: |
- # Publish official release to apache/solr-mcp
- # This requires Apache PMC credentials
- if [[ -n "${{ secrets.DOCKERHUB_APACHE_USERNAME }}" ]]; then
- # Build and push with multiple tags
- ./gradlew jib \
- -Djib.to.image=apache/solr-mcp:${{ inputs.release_version }} \
- -Djib.to.auth.username=${{ secrets.DOCKERHUB_APACHE_USERNAME }} \
- -Djib.to.auth.password=${{ secrets.DOCKERHUB_APACHE_TOKEN }} \
- -Djib.to.tags=${{ inputs.release_version }},latest
-
- # Also tag with major and minor versions
- MAJOR_VERSION=$(echo "${{ inputs.release_version }}" | cut -d. -f1)
- MINOR_VERSION=$(echo "${{ inputs.release_version }}" | cut -d.
-f1-2)
-
- ./gradlew jib \
- -Djib.to.image=apache/solr-mcp:${MAJOR_VERSION} \
- -Djib.to.auth.username=${{ secrets.DOCKERHUB_APACHE_USERNAME }} \
- -Djib.to.auth.password=${{ secrets.DOCKERHUB_APACHE_TOKEN }}
-
- ./gradlew jib \
- -Djib.to.image=apache/solr-mcp:${MINOR_VERSION} \
- -Djib.to.auth.username=${{ secrets.DOCKERHUB_APACHE_USERNAME }} \
- -Djib.to.auth.password=${{ secrets.DOCKERHUB_APACHE_TOKEN }}
- else
- echo "WARNING: Apache Docker Hub credentials not configured"
+ set -euo pipefail
+ # Publish official release to apache/solr-mcp. This requires Apache
PMC
+ # credentials; a release that cannot publish must fail loudly rather
than
+ # reporting success while shipping nothing.
+ if [[ -z "${JIB_TO_AUTH_USERNAME}" || -z "${JIB_TO_AUTH_PASSWORD}"
]]; then
+ echo "ERROR: Apache Docker Hub credentials
(DOCKERHUB_APACHE_USERNAME / DOCKERHUB_APACHE_TOKEN) are not configured" >&2
+ exit 1
fi
+ # Build and push with multiple tags
+ ./gradlew jib \
+ -Djib.to.image="apache/solr-mcp:${RELEASE_VERSION}" \
+ -Djib.to.tags="${RELEASE_VERSION},latest"
+
+ # Also tag with major and minor versions
+ MAJOR_VERSION="${RELEASE_VERSION%%.*}"
+ MINOR_VERSION="$(echo "${RELEASE_VERSION}" | cut -d. -f1-2)"
+
+ ./gradlew jib -Djib.to.image="apache/solr-mcp:${MAJOR_VERSION}"
+ ./gradlew jib -Djib.to.image="apache/solr-mcp:${MINOR_VERSION}"
+
- name: Build and publish to GitHub Container Registry
run: |
# Also publish to GitHub Container Registry
@@ -332,7 +332,6 @@ jobs:
## Vote Thread
This release was approved by the Apache Solr PMC.
- ${VOTE_THREAD_NOTE}
EOF
if [[ -n "${{ inputs.vote_thread_url }}" ]]; then
@@ -400,7 +399,7 @@ jobs:
env:
RELEASE_VERSION: ${{ inputs.release_version }}
run: |
- sed -i "s/version = \".*\"/version = \"${RELEASE_VERSION}\"/"
build.gradle.kts
+ sed -i "0,/^version = \".*\"/s//version = \"${RELEASE_VERSION}\"/"
build.gradle.kts
# Inline `docker login` rather than docker/login-action: that third-party
# action is not on the ASF GitHub Actions allow-list, which fails the
whole
@@ -515,7 +514,10 @@ jobs:
runs-on: ubuntu-latest
# Wait for both JVM (publish-docker) and native (publish-native-manifests)
# images so the MCP Registry entry references a fully-published release.
- needs: [publish-docker, publish-native-manifests]
+ # validate-release must be listed here too: the if: below reads its output,
+ # and a needs.<job> reference to a job not in needs: resolves to empty,
+ # which would silently make this job never run.
+ needs: [validate-release, publish-docker, publish-native-manifests]
if: ${{ needs.validate-release.outputs.proceed == 'true' }}
# Permissions required for OIDC-based auth to the MCP Registry and read
access
@@ -543,16 +545,40 @@ jobs:
VERSION="${{ steps.mcp_version.outputs.version }}"
# Update the top-level server version (e.g., 1.2.3)
jq --arg v "$VERSION" '.version = $v' server.json >
server.json.tmp
- # Update package version to match GA release (no -SNAPSHOT
suffix)
- jq --arg v "$VERSION" '.packages[0].version = $v'
server.json.tmp > server.json
+ # Update EVERY package version to match the GA release.
Rewriting
+ # only packages[0] left the other package on -SNAPSHOT, so the
+ # registry entry advertised an image tag that is never
published.
+ jq --arg v "$VERSION" '.packages[].version = $v'
server.json.tmp > server.json
rm server.json.tmp
+ # Fail if any -SNAPSHOT version survived the rewrite
+ if grep -q 'SNAPSHOT' server.json; then
+ echo "ERROR: server.json still contains a SNAPSHOT version
after rewrite" >&2
+ cat server.json >&2
+ exit 1
+ fi
# Show the final server.json for auditing
cat server.json
- # Download the MCP Publisher CLI from its latest GitHub release
+ # Download the MCP Publisher CLI, pinned to an explicit release and
+ # verified against that release's published checksum file. Piping an
+ # unverified `curl -L` straight into tar would silently extract a
+ # GitHub error page on a 404, so download, verify, then extract.
- name: Download MCP Publisher
+ env:
+ MCP_PUBLISHER_VERSION: v1.8.0
run: |
- curl -L
https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher-linux-amd64.tar.gz
| tar xz
+ set -euo pipefail
+
base="https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}"
+ asset="mcp-publisher_linux_amd64.tar.gz"
+ checksums="registry_${MCP_PUBLISHER_VERSION#v}_checksums.txt"
+
+ curl -fsSL -o "${asset}" "${base}/${asset}"
+ curl -fsSL -o "${checksums}" "${base}/${checksums}"
+
+ # Abort if the archive does not match the release's published
digest
+ grep " ${asset}\$" "${checksums}" | sha256sum -c -
+
+ tar xzf "${asset}"
chmod +x mcp-publisher
./mcp-publisher --version
diff --git a/server.json b/server.json
index 5c3f5b4..b3b9e70 100644
--- a/server.json
+++ b/server.json
@@ -6,7 +6,7 @@
"url": "https://github.com/apache/solr-mcp",
"source": "github"
},
- "version": "1.0.0",
+ "version": "1.0.0-SNAPSHOT",
"packages": [
{
"registryType": "docker",
@@ -51,4 +51,4 @@
]
}
]
-}
\ No newline at end of file
+}