ci: add docker-smoke workflow — build + import smoke on Dockerfile PRs (#348)#350
Conversation
…rfile Closes the structural gap surfaced by PR #346: CI has no opinion on Docker today. The test matrix uses setup-python, not the image's Python. docker-publish.yml only fires at tag push, so a Dockerfile change reaches main unexercised. This workflow fixes that. Fires on pull_request when any of these change: - Dockerfile - pyproject.toml - uv.lock - .dockerignore - .github/workflows/docker-smoke.yml itself Does: - Build the image via Buildx, with GHA cache (cache-from/to type=gha) - Load it into the runner (no registry push) - Three import smokes inside the built image: * import mcp_awareness * from mcp_awareness import server * command -v on all six console-script entry points - Report image size and tags for visibility Does NOT: - Push to any registry (docker-publish.yml keeps that responsibility, still tag-triggered) - Run the full test suite inside the image (test matrix's job) - Multi-arch build (linux/amd64 only for PR smoke) Closes #348. Pairs with #349 (Python matrix extension, P3) — that one is additive, not a substitute: matrix validates Python versions under setup-python, this validates the actual shipped image. Full coverage needs both. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round-2 fix on PR #350's own self-validation. The image's ENTRYPOINT is ./docker-entrypoint.sh which runs migrations requiring AWARENESS_DATABASE_URL. The original workflow did: docker run --rm mcp-awareness:pr-smoke python -c "..." Docker treats "python -c '...'" as CMD (args to ENTRYPOINT). ENTRYPOINT runs first, tries to do migrations, fails on missing env var, and the container exits 1 before our import ever runs. That's exactly the kind of "green CI" bug this workflow was meant to catch — ironically caught itself on the first run. Fix: pass --entrypoint python (or --entrypoint bash) on each docker run invocation so we go straight to the interpreter, bypassing the migration entrypoint. Tiny comment block above the steps documents why. Bonus: added a fourth smoke step that verifies docker-entrypoint.sh IS executable and has a sane shebang — now that we're bypassing it for imports, we still want to catch regressions that break the real runtime entrypoint. Refs #348.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
QA Active — reviewing the docker-smoke CI workflow (the follow-up proposed from #346's QA). |
cmeans
left a comment
There was a problem hiding this comment.
QA Review — Round 1
Verdict: QA Failed.
The workflow itself is excellent and is proven functional — it self-validated on this PR in the most convincing way possible:
- Run 1 (
2026-04-21T14:37:16Z, initial commit9b555938): FAILED. The workflow caught its own ENTRYPOINT bug —./docker-entrypoint.shtried to run migrations, couldn't, and exited before thepython -cimport ever executed. This is exactly the class of "green CI missing a real failure" that #348 was filed to close. - Run 2 (
2026-04-21T14:40:15Z, fix commitf089a602): SUCCESS after adding--entrypoint python/--entrypoint bashoverrides plus a fourth smoke that positively checks the entrypoint script's executable bit + shebang.
A workflow whose first action is to catch its own regression is as strong a signal of working CI as you can ask for. Zero findings against the workflow's substance, security hygiene (permissions: contents: read, no contributor-controlled inputs in any run: body — matches the #333 discipline), or trigger set.
Finding
Substantive — PR body and CHANGELOG both say "three import smokes"; workflow has four. The round-2 fix commit (f089a602) added a fourth smoke — the docker-entrypoint.sh executable + shebang check — but the PR body and CHANGELOG weren't updated to match. Four files reference a tally that's off by one:
| Location | Current text | Should reflect |
|---|---|---|
| PR body, "What it does" §2 (line 12) | "Three import smokes inside the built image:" + 3 bullets | Four + 4th bullet (entrypoint script executable + shebang check) |
| PR body, manual test 4 (line 92) | "~3–5 min (buildx + three docker run --rm invocations)" |
four |
| PR body, step 7 expected diff (line 102) | ".github/workflows/docker-smoke.yml (+88), …" |
actual diff is +102 (14 lines larger — the round-2 fix added 14 lines of workflow body) |
| PR body, acceptance map (line 107) | "three distinct smokes catch base-image / install / import-time / entry-point failures" | four (add entrypoint-script-regression) |
CHANGELOG.md ### Added entry |
"runs three import smokes inside it: …" + 3 bullets | four + 4th bullet |
None of this affects behavior — the workflow is doing what it advertises, just one more thing than it says. But a reviewer counting steps against the claim will spot the mismatch. Fix in the PR body and CHANGELOG to restore tally accuracy.
Steps verified
| Step | Result |
|---|---|
| 1. YAML parse | OK ✓ |
2. paths: trigger set |
['Dockerfile', 'pyproject.toml', 'uv.lock', '.dockerignore', '.github/workflows/docker-smoke.yml'] ✓ |
3. No contributor-controlled inputs in run: bodies |
(none — good) ✓ |
| 4. Post-merge: fires on touching PR | Deferred, but this PR already empirically validated the workflow end-to-end (self-trigger via paths filter on the workflow file itself) |
| 5. Post-merge: skips on non-touching PR | Deferred (post-merge empirical) |
| 6. Post-merge: failure injection | Deferred (post-merge empirical) — but the initial run's real failure (ENTRYPOINT bug) is the functional equivalent: the workflow caught a real regression. |
| 7. Diff stat | Actual +105 / 0 (workflow +102, CHANGELOG +3) vs expected "+88 / +3" — see finding (this is the round-2 fix adding ~14 lines). |
Issue #348 scope closure
- ✓ PR-triggered job builds
docker build .on PRs touching Dockerfile / pyproject.toml / uv.lock — plus.dockerignoreand the workflow itself, both sensible additions - ✓ Build + import smoke fails fast — empirically demonstrated on this PR's own first run
- ✓
docker-publish.ymlunchanged - ◐ On an unrelated PR, job correctly skips — deferred to post-merge empirical (not blockable from inside this PR)
- ◐ Manually verified by reopening a 3.13-slim test PR — post-merge, though the ENTRYPOINT-bug catch on this PR's first run is an equivalent demonstration
Round 2
Narrow fix in the PR body + CHANGELOG (update tally + add the fourth bullet). No workflow changes needed. Round 2 will re-check the text.
|
Audit: applying QA Failed as the final act on |
…rypoint check) Round-2 fix commit f089a60 added a fourth smoke (docker-entrypoint.sh executable + shebang check) to work around the ENTRYPOINT override needed for the import smokes. Round-1 CHANGELOG said "three import smokes"; corrected to four with the entrypoint-script verification explicitly named. Also flags the --entrypoint override rationale. QA caught this on round 2 of PR #350. No behavior change — pure doc-tally alignment. Refs #348.
Dev Response — Round 2 (doc-tally fix)Thanks — and thanks for the generous framing on the self-catch. Finding verified: five locations referenced "three" smokes after round-2 had added a fourth. All five fixed:
Also added a new "Round 1 self-validation — the workflow caught its own bug" section at the top of the PR body so a reviewer sees the self-catch story before the fuller description. No workflow code changes — the substance is untouched on Outstanding acceptance itemsBoth remaining ☐-items in the acceptance map are structurally deferrable to post-merge (they require the workflow to already be on
Head SHA for re-verification
|
|
QA Active — round 2, re-checking tally alignment on |
cmeans
left a comment
There was a problem hiding this comment.
QA Review — Round 2
Verdict: Ready for QA Signoff.
All 5 round-1 drift spots cleanly updated on 2b3ef15c, plus Dev added round-2/3 narrative paragraphs (lines 7, 9 of the PR body) documenting both the ENTRYPOINT-override fix and the tally correction — good provenance for future readers. CI green.
Round-1 finding closure
| Location | Round 1 | Round 2 |
|---|---|---|
| PR body "What it does" §2 | "Three import smokes" + 3 bullets | "Four smokes" ✓ |
| PR body manual test 4 | "three docker run --rm invocations" |
"four" ✓ |
| PR body step 7 Expected diff | "+88" | "+102 / CHANGELOG +4, -1" ✓ (with explanatory note: "Pre-round-2 numbers were +88/+3; round-2 added the --entrypoint overrides + the fourth smoke + updated the CHANGELOG to match.") |
| PR body acceptance map | "three distinct smokes" | "four distinct smokes catch base-image / install / import-time / entry-point-resolution / entrypoint-script-regression failures" ✓ |
CHANGELOG.md ### Added |
"three import smokes" + 3 bullets | "four smokes" with the 4th (entrypoint-script executable + shebang) explicitly named ✓ |
One residual I missed in round 1 (disclosed, not blocking)
PR body line 53 (the "Scope of changes" bullet) still reads:
.github/workflows/docker-smoke.yml— new, 88 lines (AGPL preamble + workflow-purpose comments + trigger + one job with six steps)
Actual file is 102 lines with 8 job steps (checkout + buildx + build + 4 smokes + report). This wasn't on my round-1 list and Dev hit everything I flagged. Noting it here rather than spinning another round — the user-visible text in CHANGELOG + the acceptance map is all correct; this is a lone PR-body scope bullet whose staleness doesn't ship anywhere. If the maintainer wants to tidy it before QA-approving, the fix is trivial; if not, the PR body is merge-history anyway.
Steps re-verified
| Step | Result |
|---|---|
| 1. YAML parse | OK ✓ |
2. paths: trigger set |
5-element list, correct ✓ |
3. No contributor-controlled inputs in run: bodies |
(none — good) ✓ |
| 4, 5, 6. Post-merge empirical | Deferred — this PR's own run-1-failure / run-2-success cycle is the equivalent demonstration |
| 7. Diff stat | +102 / 0 in .github/workflows/docker-smoke.yml, +4 / -1 in CHANGELOG.md — matches the updated step-7 expectation ✓ |
CI rollup on 2b3ef15c: lint, typecheck, test (3.10/3.11/3.12), docker-smoke, CodeQL (actions+python), codecov/patch, license/cla — all SUCCESS.
Maintainer to apply QA Approved.
|
Audit: applying Ready for QA Signoff as the final act on |
…334) (#351) Closes [#334](#334). Also closes CodeQL alerts #1, #2, #3 (three flags of `actions/missing-workflow-permissions` in `ci.yml`). ## Summary Two workflow-hardening fixes bundled because they're the same theme (least-privilege + contributor-controlled-input discipline) and both surfaced from the same security review: ### 1. `pr-labels.yml` — cascade the `#333` env-routing pattern (closes #334) Three job steps in `pr-labels.yml` (`on-push`, `on-unlabel`, `on-label`) previously inlined contributor-visible fields as `${{ ... }}` expressions inside `run:` bodies: ```yaml # Before run: | PR=${{ github.event.pull_request.number }} REPO=${{ github.repository }} HEAD_SHA=${{ github.event.pull_request.head.sha }} # ... shell script references PR / REPO / HEAD_SHA ``` Now they route through step-level `env:` and are referenced as shell variables: ```yaml # After env: PR: ${{ github.event.pull_request.number }} REPO: ${{ github.repository }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: | # ... shell script references "$PR" / "$REPO" / "$HEAD_SHA" ``` **Not a currently-exploitable bug.** The `on: pull_request:` trigger means fork PRs get a read-only `GITHUB_TOKEN` — the `gh pr edit --add-label` / `--remove-label` calls would be rejected from a fork PR regardless of what `PR`/`REPO`/`HEAD_SHA` contained. And all three values are typed (numeric PR, repo name validated by GHA, hex SHA) — none come from user-authored text like titles or bodies. **Why change it anyway**, per #334's rationale: - **Trigger-drift risk.** If `pr-labels.yml` ever switches to `pull_request_target` (to allow label automation on fork PRs), the same injection class that #333 closed on `pr-labels-ci.yml` reappears — and now the hardening would already be in place. - **Parameterization-drift risk.** A future maintainer adding a contributor-authored string field (label name, PR title fragment, branch name) to a `run:` block won't be prompted to route via `env:` first because the file already establishes the inline `${{ ... }}` style as "fine here." - **Cascade consistency.** `pr-labels-ci.yml` uses env-routing since #333; having the sibling workflow use a different style is a readability cost for anyone auditing the repo. ### 2. `ci.yml` — add workflow-level `permissions: contents: read` (closes CodeQL #1/#2/#3) `ci.yml` had no `permissions:` block at workflow or job level, so all three jobs (`lint`, `typecheck`, `test`) inherited whatever repo-level default `GITHUB_TOKEN` scope is configured. CodeQL flagged this three times (one per job). Fix: declare `permissions: contents: read` at the workflow level. Every job inherits read-only content access, which is sufficient for lint / typecheck / pytest / codecov. No job actually needs write access to anything. ## Audit sweep results While touching workflow files, checked all six for missing `permissions:`: | Workflow | Had `permissions:`? | This PR's action | |----------|---------------------|------------------| | `ci.yml` | No (CodeQL flagged 3x) | Added `contents: read` at workflow level | | `docker-publish.yml` | Yes, line 23 | No change | | `docker-smoke.yml` | Yes, line 40 (from #350) | No change | | `pr-labels-ci.yml` | Yes, line 35 (from #333) | No change | | `pr-labels.yml` | Yes, line 26 | No change to permissions block; env-routing changes only | | `qa-gate.yml` | Yes, line 24 | No change | `ci.yml` was the last gap. Sweep is complete. ## Scope - `.github/workflows/ci.yml` — `+7 lines` (permissions block with inline rationale comment) - `.github/workflows/pr-labels.yml` — `+10, -11` (three `run:` bodies lose two shell-assignment lines each; three `env:` blocks gain two-three entries each; explanatory comment added in the `on-unlabel` case) - `CHANGELOG.md` — `+4 lines` (new `### Security` subsection under `[Unreleased]`) No source, no tests, no migrations. ## References - Closes [#334](#334) - Closes CodeQL alerts #1, #2, #3 (`actions/missing-workflow-permissions` on `ci.yml:27/41/53`) - Cascade source: PR [#333](#333) (same pattern for `pr-labels-ci.yml`, which closed #332) - Related CodeQL alerts not addressed by this PR: #5/#6/#7/#8 (OAuth clear-text logging in `oauth.py` and `oauth_proxy.py`) — separate audit PR, coming next. #4 (socket bind in tests) — dismiss via UI. ## QA ### Prerequisites None. Pure workflow-YAML changes. ### Automated checks - `lint`, `typecheck`, `test (3.10/3.11/3.12)` — none touch YAML, should remain green. - `CodeQL (actions)` — will re-scan `ci.yml` and `pr-labels.yml` on this PR. Expected outcome: alerts #1/#2/#3 flip to "fixed" on merge; no new alerts introduced. - `docker-smoke` — not triggered (no changes under `Dockerfile` / `pyproject.toml` / `uv.lock` / `.dockerignore`). ### Manual tests 1. - [x] **Both workflow files parse.** ``` python3 -c "import yaml; [yaml.safe_load(open(f)) for f in ['.github/workflows/ci.yml', '.github/workflows/pr-labels.yml']]; print('OK')" ``` Expected: `OK`. 2. - [x] **`ci.yml` now has `permissions: contents: read`.** ``` grep -A1 '^permissions:' .github/workflows/ci.yml ``` Expected: `permissions:` header followed by ` contents: read`. 3. - [x] **No contributor-controlled inputs in `pr-labels.yml` `run:` bodies.** ``` awk '/^[[:space:]]+run: \|/,/^[[:space:]]+- name:|^[[:space:]]{2,6}[a-z-]+:$/' .github/workflows/pr-labels.yml | grep -nE '\$\{\{ *github\.(event|repository|head_ref)' || echo "(none — good)" ``` Expected: `(none — good)`. All `github.event.*` / `github.repository` references are now in `env:` blocks (and in job-level `if:` conditionals, which is safe context). 4. - [x] **All six workflows now have `permissions:`.** ``` for f in .github/workflows/*.yml; do if ! grep -q '^permissions:\|^ permissions:\|^ permissions:' "$f"; then echo "$f: MISSING permissions" fi done echo "(if no 'MISSING' lines above, sweep is complete)" ``` Expected: no `MISSING` lines. 5. - [x] **Label automation still functions on this PR.** When I push, `pr-labels.yml`'s `on-push` should reset labels to `Awaiting CI` and strip any stale QA labels. When `Dev Active` is removed, `on-unlabel` should promote to `Ready for QA` after CI passes. Empirically validated if the label transitions on this PR itself behave identically to recent merged PRs (self-test). 6. - [x] **`permissions: contents: read` doesn't break anything.** Lint / typecheck / pytest / codecov upload only need read access to `GITHUB_TOKEN` — none of them push labels, create comments, or mutate repo state. If any of the existing CI checks start failing on this PR with "resource not accessible" errors, that's a signal the permissions block is too tight (unlikely, but the empirical test is: does this PR's CI go green?). 7. - [x] **Diff review.** ``` git diff --stat origin/main ``` Expected: `.github/workflows/ci.yml` (+7), `.github/workflows/pr-labels.yml` (+10, -11), `CHANGELOG.md` (+4). Nothing else. ### Acceptance - ✅ `#334` — symmetric env-routing cascade landed in `pr-labels.yml` - ✅ CodeQL `#1`, `#2`, `#3` — `ci.yml` now has explicit `permissions:` - ☐ CodeQL re-scan confirmation — post-merge, the three alerts flip from Open → Fixed automatically on the next `Analyze (actions)` run against `main` Post-merge, also worth a look at CodeQL's /security/code-scanning dashboard to confirm Open count drops from 8 → 5 (just the four OAuth-logging + the one test-file socket-bind remaining). Co-authored-by: cmeans-claude-dev[bot] <3223881+cmeans-claude-dev[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes [#349](#349). ## Summary One-line change in `.github/workflows/ci.yml`: \`\`\`diff matrix: - python-version: [\"3.10\", \"3.11\", \"3.12\"] + python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"] \`\`\` `pyproject.toml` declares `requires-python = \">=3.10\"` — the package metadata *allows* 3.13 and 3.14, but CI never exercised them. That's a source of silent breakage for any self-hoster running the package under a newer Python than we tested. ### What stays unchanged - **lint / typecheck / codecov jobs** still pinned to Python 3.12 — one representative version for static analysis is sufficient, and these jobs don't gain anything from fanning out across the whole matrix. - **`pyproject.toml`** metadata — `requires-python = \">=3.10\"` already allowed both. No lower-bound or classifier changes. - **`docker-publish.yml`, `docker-smoke.yml`** — untouched. The Dockerfile pins to `python:3.12-slim`; bumping that is a separate decision (see #346 / #348 / the earlier thread). ### Why this is additive, not redundant, with #348 This PR validates Python versions under `actions/setup-python`; [#348](#348) (merged as PR #350) validates the shipped Docker image. Both are needed for full CI coverage: - The **matrix** catches Python-version-specific code breakage (stdlib removals, syntax/semantics changes, transitive-dep wheel availability for newer Pythons). - The **docker-smoke** catches image-specific breakage (`FROM python:X.Y-slim` changes, `pip install` failures inside the slim image, entry-point registration). Neither substitutes for the other. ## Risks and unknowns This is the \"may surface hidden work\" case I flagged when we ordered the P3s. Two outcomes for the new matrix entries: - **Happy path**: 3.13 and 3.14 both pass. Merge and move on. - **Unhappy path**: 3.13 or 3.14 fails — likely because a transitive dependency (testcontainers, psycopg, lingua-language-detector, mcp, pgvector, pydantic, etc.) doesn't ship wheels for that Python yet, or a stdlib removal breaks a dep. Each failure is a separate triage: bump a dep to a version that supports the newer Python, or exclude the entry with a note, or declare the `>=3.10` floor more strictly as an upper bound in `pyproject.toml`. CI on this PR is the load-bearing test. If it's green, #349 is closed. If it's red, we'll have a triage list to work through. ## Scope - `.github/workflows/ci.yml` — `+1, -1` (matrix list) - `CHANGELOG.md` — `+3` (new `### Added` entry under `[Unreleased]`) No source, no tests, no compose-file changes, no migrations. ## References - Closes [#349](#349) - Pairs with #348 (`docker-smoke`, merged via PR #350) — additive, both needed ## QA ### Prerequisites None. Pure CI-workflow change. ### Automated checks - `test (3.10/3.11/3.12)` — existing entries; expected to remain green. - **`test (3.13)` and `test (3.14)`** — new entries; expected to exercise the full `pytest tests/` suite against these versions. If any fail, that's the actual finding this PR surfaces. - `lint`, `typecheck`, `codecov/patch` — pinned to 3.12; unchanged. - `docker-smoke` — **not triggered** (no `Dockerfile` / `pyproject.toml` / `uv.lock` / `.dockerignore` / workflow-file-matching-its-trigger change). - `CodeQL (actions)` — will re-scan `ci.yml`; no new taint-flow sites introduced. ### Manual tests 1. - [x] **YAML parse.** \`\`\` python3 -c \"import yaml; yaml.safe_load(open('.github/workflows/ci.yml')); print('OK')\" \`\`\` Expected: `OK`. 2. - [x] **Matrix lists 5 entries in order.** \`\`\` grep -A1 '^ matrix:' .github/workflows/ci.yml | tail -1 \`\`\` Expected: `python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]`. 3. - [x] **CI runs 5 test jobs on this PR.** Verify in the Actions tab that `test (3.10)`, `test (3.11)`, `test (3.12)`, `test (3.13)`, `test (3.14)` all appear in the check list for this PR. 4. - [x] **All five test jobs pass.** The actual acceptance test — if 3.13 or 3.14 fails, this PR needs follow-up work before it can merge: - `test (3.13)` fails → read the log; common culprits: `testcontainers` / `lingua-language-detector` / `mcp` without a 3.13 wheel - `test (3.14)` fails → same, newer target; 3.14 wheel coverage is generally thinner than 3.13 Each failure is a separate triage decision (bump the dep, exclude the matrix entry with a note, or tighten `requires-python` upper bound). 5. - [x] **Lint/typecheck unchanged.** `lint` and `typecheck` jobs still use `python-version: \"3.12\"` — they should continue to run in their existing time envelope. Verify by checking that neither job's duration in this PR's CI is materially different from the last green `main` CI run. 6. - [x] **No other files changed.** `git diff --stat origin/main` shows exactly `.github/workflows/ci.yml` (+1, -1) and `CHANGELOG.md` (+3). Nothing else. ### Acceptance - ☐ All 5 test matrix entries pass — **load-bearing**, pending CI - ✅ `pyproject.toml` `requires-python = \">=3.10\"` now aligned with CI matrix coverage - ✅ Lint/typecheck/codecov jobs remain at a single representative version (3.12) - ✅ No overlap with or replacement of #348's docker-smoke — complementary coverage Co-authored-by: cmeans-claude-dev[bot] <3223881+cmeans-claude-dev[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follows up on Dependabot [#346](#346) (closed unmerged pending CI matrix coverage). ## Summary One-line change in `Dockerfile`: \`\`\`diff -FROM python:3.12-slim +FROM python:3.13-slim \`\`\` Plus a `CHANGELOG.md` `### Changed` entry. ### Why 3.13 and not 3.14 - **3.13** — GA October 2024, ~5 years of upstream support (EOL October 2029), well-represented in wheel coverage across our deps. Safe bump. - **3.14** — only GA'd October 2026. Our own suite passes (see #354's matrix), but transitive tooling outside our direct deps (container probes, operator scripts, downstream self-hoster environments) lags. Deferring until 3.14-slim has broader ecosystem saturation — revisit in a few months. ### Coverage this relies on - **#354 / #349** (merged `99860de`) — CI test matrix now exercises 3.13, so the Python version the image ships is the Python version the test suite ran on. - **#348 / #350** — `docker-smoke.yml` workflow fires on any `Dockerfile` change, building the image and running import + console-script smokes inside it. That will validate this PR end-to-end. ### What stays unchanged - `pyproject.toml` — `requires-python = ">=3.10"`; no metadata change. - `ci.yml` — `lint` / `typecheck` / `codecov` jobs still pinned to 3.12; `test` matrix still covers 3.10–3.14. - `docker-compose.yaml`, `docker-publish.yml` — untouched. - `uv.lock` — untouched; all deps already support 3.13. ## Risks and unknowns **Happy path:** `docker-smoke` green → merge. Image is marginally larger on first pull (new base layer) but subsequent pulls share layers as usual. **Unhappy path:** `docker-smoke` catches something that the `actions/setup-python` matrix didn't. Likeliest culprit would be a transitive-dep wheel that resolves differently in the slim image's glibc environment than it does on Ubuntu `ubuntu-latest` runners. Triage per-finding. ## Scope - `Dockerfile` — `+1, -1` (FROM line) - `CHANGELOG.md` — `+3` (new `### Changed` entry under `[Unreleased]`) No source, no tests, no compose changes, no migrations. ## References - Follows up on closed Dependabot [#346](#346) - Relies on merged [#354](#354) (3.13 matrix entry) and merged [#350](#350) (docker-smoke workflow) ## QA ### Prerequisites None. The `docker-smoke.yml` workflow runs automatically on any PR touching `Dockerfile`. ### Automated checks - **`docker-smoke` — load-bearing.** Builds the image and runs: - `python -c 'import mcp_awareness'` - `python -c 'from mcp_awareness import server'` - `command -v` on all six console scripts (`mcp-awareness`, `mcp-awareness-migrate`, `mcp-awareness-user`, `mcp-awareness-token`, `mcp-awareness-secret`, `mcp-awareness-register-schema`) - positive check that `docker-entrypoint.sh` has a valid shebang and executable bit - `test (3.10 / 3.11 / 3.12 / 3.13 / 3.14)` — unchanged by this PR but re-runs on CI; expected green. - `lint`, `typecheck`, `codecov/patch` — still pinned to 3.12; unchanged. - `CodeQL (actions)` — no workflow files touched; re-scans diff only. ### Manual tests (via MCP tools) This PR changes the shipped container only. There are no new MCP tools or behavior changes to exercise — the awareness tool surface is identical on 3.13-slim as it was on 3.12-slim. Listing the checks here for reviewer audit: 1. - [x] **`docker-smoke` all four sub-checks pass** on the PR run. Verify in the Actions tab that each smoke step shows green. 2. - [x] **Container boots cleanly** against a throwaway Postgres (CI environment or local). Optional belt-and-suspenders sanity check: \`\`\` docker build -t awareness-test . docker run --rm awareness-test python -c 'from mcp_awareness import server; print("ok")' \`\`\` Expected: `ok` printed, exit 0. 3. - [x] **Image base is Python 3.13.** Verify the built image reports the expected Python version: \`\`\` docker run --rm awareness-test python --version \`\`\` Expected: `Python 3.13.x`. 4. - [x] **No stray file changes.** `git diff --stat origin/main` shows exactly `Dockerfile` (+1, -1) and `CHANGELOG.md` (+3). Nothing else. ### Acceptance - ✅ `docker-smoke` green — **load-bearing** - ✅ Base image aligned with CI-covered Python version (3.13 now in matrix per #354) - ✅ `requires-python` floor unchanged; no consumer-facing metadata shift - ✅ Single-concern diff; no test, source, or compose changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: cmeans-claude-dev[bot] <3223881+cmeans-claude-dev[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Patch release stamping six PRs merged to `main` since v0.18.1 on 2026-04-20. ## Summary Two-file diff: - `pyproject.toml` — `version` bump `0.18.1` → `0.18.2` - `CHANGELOG.md` — `[Unreleased]` renamed to `[0.18.2] - 2026-04-21`; new empty `[Unreleased]` section seeded; comparison-link footer updated ## Why patch - No new MCP tools, no changed tool signatures, no resource changes. - No breaking config, no migration, no data-format change. - `requires-python = ">=3.10"` floor unchanged in `pyproject.toml`. - Dockerfile base bump (3.12 → 3.13) is runtime-transparent to image consumers; CI matrix widening (3.13, 3.14) is pure infra. - OAuth log-redaction is security-hardening with no behavior change on the happy path. - `docker-compose` host-port parameterization is backward-compatible — default behavior unchanged. Textbook patch bump for a 0.x project. ## Included PRs | PR | Title | Kind | |---|---|---| | [#351](#351) | ci: cascade env-routing to `pr-labels.yml` + workflow permissions | Security | | [#352](#352) | fix(oauth): redact URLs in log output (CodeQL #5-#9) | Security | | [#350](#350) | ci: add `docker-smoke` workflow — build + import smoke on Dockerfile PRs | Added | | [#353](#353) | chore(compose): parameterize host port in `docker-compose.yaml` | Changed | | [#354](#354) | ci: extend Python test matrix to include 3.13 and 3.14 | Added | | [#355](#355) | chore(docker): bump base image from `python:3.12-slim` to `3.13-slim` | Changed | All six merged via their own QA-Approved cycles — nothing in this release bypasses the standard pipeline. ## What's unchanged - `docker-compose.yaml` — uses `:latest`, no version bump needed - `README.md` — tool count (32) and text-mode content unchanged; no update needed - `uv.lock` — no dep changes in any of the six PRs ## QA Lightweight per project convention — all substantive code was tested in its own PR. Review-only checks: 1. - [x] **`pyproject.toml` version** is `0.18.2`. Verify line 3: `version = "0.18.2"`. 2. - [x] **CHANGELOG** — `[0.18.2] - 2026-04-21` heading exists; the six rolled-up entries sit beneath it in their original order (Changed → Added → Changed → Security → Security → Added); empty `[Unreleased]` seeded above. 3. - [x] **Comparison links** — `[0.18.2]: …v0.18.1...v0.18.2` added; `[Unreleased]` now points at `v0.18.2...HEAD`. 4. - [ ] **Scope** — `git diff --stat origin/main` shows exactly `CHANGELOG.md` (+4, -1) and `pyproject.toml` (+1, -1). Nothing else. 5. - [x] **No accidental content drift in rolled-up entries** — diff between this branch's `[0.18.2]` section and what was in `[Unreleased]` on `main` before this PR should be zero beyond the heading/anchor move. ### Acceptance - ✅ CI green - ☐ Merge + tag (Dev authorization, executed post-merge) ## Merge + tag (Dev post-merge action) After merge, Dev runs: \`\`\` git checkout main && git pull --ff-only origin main git tag -a v0.18.2 -m "v0.18.2 — CI matrix widening (3.13/3.14), Dockerfile to python:3.13-slim, docker-smoke workflow, compose host-port parameterization, OAuth log redaction, workflow permission hardening" git push origin v0.18.2 \`\`\` The tag triggers \`docker-publish.yml\` to build and publish the \`:v0.18.2\` + \`:latest\` images. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: cmeans-claude-dev[bot] <3223881+cmeans-claude-dev[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #348. Follow-up from PR #346 QA finding.
Round 1 self-validation — the workflow caught its own bug
On the first CI run against this PR (commit
9b55593),docker-smokefailed — the image'sENTRYPOINT ["./docker-entrypoint.sh"]runs migrations that requireAWARENESS_DATABASE_URL, anddocker run mcp-awareness:pr-smoke python -c "..."passes the python invocation as CMD, not replacing the entrypoint. ENTRYPOINT ran first, failed fast on the missing env var, exited 1 before any import ever executed. This is precisely the class of "green CI missing a real failure" that #348 was filed to close — and the workflow caught it against itself on run 1.Round-2 fix (
f089a60): eachdocker runnow uses--entrypoint pythonor--entrypoint bashto skip the migration entrypoint for import validation. Added a fourth smoke step that positively verifiesdocker-entrypoint.shitself is still executable with a valid shebang — so regressions in the real runtime entrypoint still surface, they just don't block the import-smoke flow.Round-3 (
2b3ef15): CHANGELOG tally updated from "three" to "four" to match the added step.Summary
Adds
.github/workflows/docker-smoke.yml— a new GitHub Actions workflow that builds the production Docker image and runs import smokes inside it, triggered on every PR that could affect the build.Trigger:
pull_requestwithpaths:filter onDockerfile,pyproject.toml,uv.lock,.dockerignore, and the workflow file itself. Unrelated PRs skip the job entirely (no wasted minutes).What it does:
docker buildvia Buildx with GitHub Actions cache (cache-from: type=gha, cache-to: type=gha,mode=max) — so repeat builds on unchanged base layers are fast.import mcp_awareness(via--entrypoint python— image's real ENTRYPOINT runs migrations requiringAWARENESS_DATABASE_URL, bypassed here)from mcp_awareness import server(same--entrypoint pythonoverride)command -von all six console-script entry points (mcp-awareness,mcp-awareness-migrate,mcp-awareness-user,mcp-awareness-token,mcp-awareness-secret,mcp-awareness-register-schema) via--entrypoint bashdocker-entrypoint.shis executable with a valid#!/bin/bashor#!/bin/shshebang — catches regressions in the runtime entrypoint even though the import smokes bypass itWhat it does NOT do:
docker/login-action, zeropush: true. Registry publishes remain indocker-publish.yml(tag-triggered). This workflow is purely validation.linux/amd64only for PR smoke. Full multi-arch stays indocker-publish.yml.Why this matters
The next time Dependabot (or anyone) proposes a Dockerfile change — base-image bump, apt-package change, layer reorg — the "green CI" on the PR will actually mean something. Without this workflow, PR #346's
python:3.12-slim → 3.14-slimwould have sailed through a fully-green CI suite that didn't touch the image at all.What this catches (and what it doesn't)
Catches:
pip installbreaks inside the slim image (wheel missing for a runtime dep; C extension needs a compiler the slim image doesn't ship)import mcp_awarenessfails at runtime (Python-version-specific stdlib removal, missing system library)[project.scripts]wiring)Doesn't catch:
arm64(onlyamd64is smoked here)docker-publish.yml's surface)Scope
.github/workflows/docker-smoke.yml— new, 102 lines (AGPL preamble + workflow-purpose comments + trigger + one job with 8 steps: checkout + buildx + build + four smokes + report)CHANGELOG.md—[Unreleased] → ### AddedentryNo source, no tests, no migrations, no other workflow changes.
References
actions/setup-python; this workflow validates the shipped image. Full coverage needs both.QA
Prerequisites
None. Pure workflow-file addition.
Automated checks
This PR doesn't touch source, tests,
pyproject.toml, orDockerfile, so:test (3.10/3.11/3.12)should pass unchanged.lint/typecheckdon't apply to YAML workflow files; should pass unchanged.CodeQL (actions)will scan the new workflow for script-injection and other GitHub Actions vulnerabilities — should pass (no contributor-controlled inputs used; no${{ github.event.* }}text in anyrun:body).docker-smokewill NOT run on this PR — thepaths:trigger requiresDockerfile/pyproject.toml/uv.lock/.dockerignore/docker-smoke.ymlto change, and onlydocker-smoke.ymlitself qualifies here (the workflow won't actually run until after it's merged, at which point any future PR touching those paths will pick it up). See manual test 4 below for how to empirically verify this post-merge.Manual tests
OK.paths:trigger covers the right set.['Dockerfile', 'pyproject.toml', 'uv.lock', '.dockerignore', '.github/workflows/docker-smoke.yml']. The dict key isTruein PyYAML 6.x becauseon:is interpreted as YAML 1.1 boolean — harmless parser quirk.run:bodies. This is the class of bug that bug: pr-labels-ci.yml is pre-hardening (shell-injection risk) and lacks the PR-#28 comment escape #332 closed forpr-labels-ci.yml; same discipline should apply to every new workflow.(none — good). Workflow uses no contributor-controlled context fields.Dockerfile,pyproject.toml,uv.lock, or.dockerignoreshould trigger theDocker Smoke / docker-smokecheck. Easiest empirical path: the next Dependabot PR against those files (Dependabot is configured to watchpip+docker+docker-composeper chore: expand .github/dependabot.yml to 4 ecosystems with grouped weekly updates #343; a pending proposal will arrive within a week). On that next PR, confirm:Docker Smoke / docker-smokecheck appears in the PR's check listimport mcp_awareness: ok,import mcp_awareness.server: ok,all entry points resolved, image-size line)docker run --rminvocations)Docker Smoke / docker-smokecheck should NOT appear in the checks list — the workflow'spaths:filter keeps it silent when nothing Docker-relevant changes.DockerfiletoFROM python:3.99-slim(a tag that doesn't exist). Confirmdocker-smokefails on theBuild imagestep. Close the PR without merging. This is the real acceptance test for ci: add docker build + import smoke on PRs touching Dockerfile / pyproject.toml / uv.lock #348..github/workflows/docker-smoke.yml(+102),CHANGELOG.md(+4, -1). Nothing else. (Pre-round-2 numbers were +88/+3; round-2 added the--entrypointoverrides + the fourth smoke + updated the CHANGELOG to match.)Acceptance criteria map (from #348)
docker build .on PRs touching Dockerfile/pyproject.toml/uv.lock —paths:filter covers those plus.dockerignore(for completeness) and the workflow itself (so bugs in the workflow fire the workflow to validate the fix)docker-publish.ymlunchanged — this PR adds a file, doesn't touch any existing workflowmain)