Today's incident: a template's adapter.py imported a symbol
(RuntimeCapabilities) from molecule_runtime that the published runtime
didn't yet export. The image built fine, the existing "smoke test"
inspected the entrypoint string and passed, and a broken :latest
shipped to GHCR. Every claude-code + hermes provision then hung in
"provisioning" status until the 10-min sweep marked them failed.
The old smoke test was named correctly but didn't actually exercise
anything — `docker inspect` doesn't catch ImportError. This change
splits the build/push step into three:
1. Build with `load: true, push: false` so the image lands on the
runner's local docker.
2. Smoke test runs `docker run ... python -c "import adapter"` against
the loaded image. This catches the version-skew class of bug
(adapter.py imports a symbol the installed runtime doesn't export),
plus syntax errors, missing files, and anything else that breaks
import-time.
3. Push :latest + :sha-* only if the smoke test passes. The push step
reuses the cached build, so it's fast.
Net cost: ~5s per publish (the docker run). Net benefit: broken images
can no longer poison :latest.
All 8 caller templates (claude-code, gemini-cli, hermes, langgraph,
crewai, autogen, deepagents, openclaw) inherit the gate automatically
since this is the reusable workflow they all call.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All 8 template repos are public → GHA-hosted minutes are free, so
there's no cost incentive to stay on the self-hosted Mac mini. The
only reason we started there was to avoid GHA rate limits (memory
feedback_selfhosted_runner); that concern doesn't apply here because:
- Linux/amd64 builds go native on ubuntu-latest (no QEMU emulation
from arm64 → amd64), so builds run ~2-3x faster.
- docker/login-action@v3 + GITHUB_TOKEN handles GHCR auth cleanly,
no Keychain gymnastics needed.
- No queue wait when the Mac mini is busy publishing canvas/platform
or running e2e.
Concretely this change:
- runs-on: [self-hosted, macos, arm64] → ubuntu-latest
- Drops the hand-rolled `auths` config step (macOS Keychain
workaround) in favour of `docker/login-action@v3`.
- Drops `docker/setup-qemu-action` (unnecessary for a linux/amd64
target on an amd64 runner).
- Uses setup-buildx@v3 to match the login-action major version.
Self-hosted Mac mini remains the runner for private-repo workflows
(follow-up PRs will migrate other public-repo workflows in
molecule-core).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each Molecule-AI/molecule-ai-workspace-template-* repo currently has
no way to publish its Docker image. Tenants build locally via
workspace/rebuild-runtime-images.sh after a manual clone — which
means "merge template PR" and "template live on tenants" are two
separate manual steps per tenant.
This workflow is the `publish` half of that pipeline. Called from
each template repo via `uses: Molecule-AI/molecule-ci/.github/
workflows/publish-template-image.yml@main`, it:
- Derives runtime name from the caller repo (strip
`molecule-ai-workspace-template-` prefix) so per-repo wrappers
stay one-line.
- Builds linux/amd64 (self-hosted macOS arm64 runner + QEMU) and
pushes to `ghcr.io/molecule-ai/workspace-template-<runtime>:latest`
plus `:sha-<7>` for per-commit pinning.
- Uses the Keychain-avoiding GHCR auth pattern from canvas' publish
workflow — osxkeychain write fails under the locked launchd keychain
on the Mac mini runner; writing auths map directly works.
- Smoke-tests the pushed image by pulling and inspecting entrypoint.
Follow-up (not in this PR):
- Each template repo gets a ~10-line caller workflow.
- Monorepo provisioner.RuntimeImages map switches from bare
`workspace-template:<runtime>` (local-only) to
`ghcr.io/molecule-ai/workspace-template-<runtime>:latest`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The grep-based secrets check matched literal credential patterns in
documentation (e.g., "sk-ant-..." in CLAUDE.md examples), causing
false-positive CI failures.
Replace with a Python script that:
- Skips .molecule-ci/ directory entirely
- Uses context-aware matching (requires quotes or assignment context)
- Filters out documentation examples with "..." or <example> markers
- Handles all three reusable workflows (plugin, workspace-template, org-template)
- Remove redundant nested checkout of molecule-ci in workflow_call jobs
- Add timeout-minutes to prevent hung jobs (plugin: 10m, workspace: 15m)
- Add pip cache using requirements.txt
- Add missing SKILL.md heading check in validate-plugin
- Add legacy import and runtime dependency warnings in workspace validation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds standard credential gitignore (.env / *.pem / .secrets/ / .auth_token).
Per-CEO directive 2026-04-16: every plugin and template repo should
gitignore credentials so self-hosters can't accidentally commit real
tokens to public repos.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Heredocs in GitHub Actions YAML were being echoed as script text
instead of executed. Moving validation logic to scripts/ and running
via 'python3 .molecule-ci/scripts/validate-*.py' after checking out
the molecule-ci repo at .molecule-ci/ path.