diff --git a/Dockerfile b/Dockerfile index 88f6248..a661c93 100644 --- a/Dockerfile +++ b/Dockerfile @@ -205,12 +205,25 @@ RUN set -eux; \ op --version # Per-agent CLI install. Both are npm packages; the global install puts -# `codex` or `claude` on PATH for the non-root user. +# `codex` or `claude` on PATH for the non-root user. Versions are pinned +# so a rebuild from the same commit produces the same agent CLI behavior. +# Bump these in lockstep with intentional CLI upgrades. +ARG CODEX_CLI_VERSION=0.129.0 +ARG CLAUDE_CLI_VERSION=2.1.132 RUN case "$AGENT" in \ - codex) npm install -g @openai/codex ;; \ - claude) npm install -g @anthropic-ai/claude-code ;; \ + codex) npm install -g "@openai/codex@${CODEX_CLI_VERSION}" ;; \ + claude) npm install -g "@anthropic-ai/claude-code@${CLAUDE_CLI_VERSION}" ;; \ esac && npm cache clean --force +# Record the resolved CLI versions in image metadata so a built image +# advertises which agent CLI it shipped with — visible via +# `docker inspect` and surfaced in the entrypoint startup banner. +LABEL org.opencontainers.image.title="codex-shell-${AGENT}" \ + com.prodromou.codex-shell.codex-cli-version="${CODEX_CLI_VERSION}" \ + com.prodromou.codex-shell.claude-cli-version="${CLAUDE_CLI_VERSION}" +ENV CODEX_CLI_VERSION=${CODEX_CLI_VERSION} \ + CLAUDE_CLI_VERSION=${CLAUDE_CLI_VERSION} + # Non-root user. uid/gid 1000, name = AGENT. Matching the AGENT name to # the user keeps PVC ownership obvious and avoids shell prompts that # lie about which agent is running. diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index eef43ac..7b0f35c 100644 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -101,10 +101,25 @@ esac # The ConfigMap (apk8s repo) is the source of truth for model/MCP config; # in-pod edits get blown away on restart. Stakater Reloader restarts the # pod when the ConfigMap changes. +# +# cp -afL: -a recurses + preserves attributes, -L dereferences the +# symlink farm that ConfigMap mounts use. The previous `cp -fL` skipped +# subdirectories entirely and silently dropped managed config. if [ -d "${AGENT_CONFIG_SOURCE}" ]; then - # cp -L follows symlinks (configmap mounts are symlink farms). - cp -fL "${AGENT_CONFIG_SOURCE}/." "${AGENT_CONFIG_DIR}/" 2>/dev/null || true + if ! cp -afL "${AGENT_CONFIG_SOURCE}/." "${AGENT_CONFIG_DIR}/"; then + echo "FATAL: failed to sync managed config from ${AGENT_CONFIG_SOURCE} to ${AGENT_CONFIG_DIR}" >&2 + exit 1 + fi chmod -R u+w "${AGENT_CONFIG_DIR}" 2>/dev/null || true + + # Smoke check: if the ConfigMap mount has any files, at least one + # must have landed in the destination. Catches silent + # permission/path failures that previously masked stale config. + if [ -n "$(find "${AGENT_CONFIG_SOURCE}" -mindepth 1 -print -quit 2>/dev/null)" ] \ + && [ -z "$(find "${AGENT_CONFIG_DIR}" -mindepth 1 -print -quit 2>/dev/null)" ]; then + echo "FATAL: managed config sync ran but ${AGENT_CONFIG_DIR} is empty" >&2 + exit 1 + fi fi # Pull nprodromou/agent-config for the canonical Nate-org instructions