---
id: S03
parent: M006
milestone: M006
provides:
  - git_commit_sha in pipeline_metadata dict
  - Commit row in version metadata panel
requires:
  []
affects:
  - S06
key_files:
  - docker/Dockerfile.api
  - docker-compose.yml
  - backend/config.py
  - backend/pipeline/stages.py
  - frontend/src/pages/TechniquePage.tsx
key_decisions:
  - 4-tier SHA resolution: .git-commit file → git rev-parse → env/config → unknown
  - Hide unknown SHA from UI — only render Commit row when a real hash was captured
patterns_established:
  - Docker build-arg → file → runtime reader pattern for baking build-time metadata into containers
observability_surfaces:
  - none
drill_down_paths:
  - .gsd/milestones/M006/slices/S03/tasks/T01-SUMMARY.md
  - .gsd/milestones/M006/slices/S03/tasks/T02-SUMMARY.md
duration: ""
verification_result: passed
completed_at: 2026-03-30T11:26:49.606Z
blocker_discovered: false
---

# S03: Git Commit SHA in Pipeline Version Metadata

**Pipeline now captures the git commit SHA at Docker build time and displays it in the technique page version metadata panel.**

## What Happened

Two tasks delivered the full vertical slice — backend SHA capture and frontend display.

T01 added a `GIT_COMMIT_SHA` build arg to `Dockerfile.api` that writes the SHA to `/app/.git-commit` at build time. Both `chrysopedia-api` and `chrysopedia-worker` compose services pass this arg. A new `_get_git_commit_sha()` helper in `stages.py` resolves the SHA through a 4-tier fallback chain: (1) read `/app/.git-commit`, (2) run `git rev-parse --short HEAD`, (3) read `GIT_COMMIT_SHA` env var / config setting, (4) return `"unknown"`. The `_capture_pipeline_metadata()` function now includes `git_commit_sha` in its return dict.

T02 added a conditional "Commit" row to the version metadata panel in `TechniquePage.tsx`. It reuses the existing `version-metadata__hash-value` monospace styling and displays only the first 7 characters. When the SHA is `"unknown"` (i.e., the image wasn't built with the arg), the row is hidden entirely.

Verified: frontend builds with zero errors, `_capture_pipeline_metadata()` returns `git_commit_sha` key inside the running container. Currently returns `"unknown"` because the running containers pre-date the Dockerfile change — a `docker compose build` with `GIT_COMMIT_SHA=$(git rev-parse --short HEAD)` will bake in a real SHA.

## Verification

1. `ssh ub01 'cd /vmPool/r/repos/xpltdco/chrysopedia/frontend && npm run build 2>&1 | tail -5'` — exit 0, clean build in 696ms
2. `docker exec chrysopedia-api python -c "from pipeline.stages import _capture_pipeline_metadata; ..."` — exit 0, git_commit_sha key present with "unknown" fallback
3. Grep confirmed all 5 files contain the expected code changes on ub01

## Requirements Advanced

None.

## Requirements Validated

None.

## New Requirements Surfaced

None.

## Requirements Invalidated or Re-scoped

None.

## Deviations

T02 added a filter to hide "unknown" SHA from the UI — not in the original plan but a sensible UX decision (showing literal "unknown" is meaningless to users).

## Known Limitations

Running containers still use the old image without .git-commit baked in. A `docker compose build --build-arg GIT_COMMIT_SHA=$(git rev-parse --short HEAD)` is needed to see real SHAs. This is expected — the build/deploy step is outside slice scope.

## Follow-ups

Consider adding GIT_COMMIT_SHA to the CI/CD build command or a Makefile target so rebuilds automatically capture the SHA.

## Files Created/Modified

- `docker/Dockerfile.api` — Added GIT_COMMIT_SHA build arg and RUN echo to write SHA to /app/.git-commit
- `docker-compose.yml` — Added GIT_COMMIT_SHA build arg to both chrysopedia-api and chrysopedia-worker services
- `backend/config.py` — Added git_commit_sha field to Settings class with 'unknown' default
- `backend/pipeline/stages.py` — Added _get_git_commit_sha() helper with 4-tier fallback; added git_commit_sha to _capture_pipeline_metadata()
- `frontend/src/pages/TechniquePage.tsx` — Added conditional Commit row to version metadata panel with 7-char abbreviated SHA
