From 02915456ae24aac71f59f8a276a62fde8d9b556f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Apr 2026 11:37:23 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20bug:=20credential=20helper=20race=20on?= =?UTF-8?q?=20every=20cold=20boot=20=E2=80=94=20configure=5Fgit=5Fcreds()?= =?UTF-8?q?=20silently=20falls=20back=20to=20wrong=20username=20when=20For?= =?UTF-8?q?gejo=20is=20not=20yet=20ready=20(#741)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- docker-compose.yml | 18 ++++++++++++---- docker/agents/entrypoint.sh | 13 ++++++++---- lib/git-creds.sh | 41 +++++++++++++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 23d1c3e..3b4ad13 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,8 +49,10 @@ services: - ARCHITECT_INTERVAL=${ARCHITECT_INTERVAL:-21600} - PLANNER_INTERVAL=${PLANNER_INTERVAL:-43200} depends_on: - - forgejo - - woodpecker + forgejo: + condition: service_healthy + woodpecker: + condition: service_started networks: - disinto-net @@ -101,8 +103,10 @@ services: - POLL_INTERVAL=${POLL_INTERVAL:-300} - AGENT_ROLES=dev depends_on: - - forgejo - - woodpecker + forgejo: + condition: service_healthy + woodpecker: + condition: service_started networks: - disinto-net @@ -171,6 +175,12 @@ services: - FORGEJO__security__INSTALL_LOCK=true - FORGEJO__service__DISABLE_REGISTRATION=true - FORGEJO__webhook__ALLOWED_HOST_LIST=private + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:3000/api/v1/version"] + interval: 5s + timeout: 3s + retries: 30 + start_period: 30s ports: - "3000:3000" networks: diff --git a/docker/agents/entrypoint.sh b/docker/agents/entrypoint.sh index 66b0b1b..d63c40a 100644 --- a/docker/agents/entrypoint.sh +++ b/docker/agents/entrypoint.sh @@ -49,7 +49,7 @@ source "${DISINTO_BAKED}/lib/git-creds.sh" # Wrapper that calls the shared configure_git_creds with agent-specific paths, # then repairs any legacy baked-credential URLs in existing clones. _setup_git_creds() { - configure_git_creds "/home/agent" "gosu agent" + _GIT_CREDS_LOG_FN=log configure_git_creds "/home/agent" "gosu agent" if [ -n "${FORGE_PASS:-}" ] && [ -n "${FORGE_URL:-}" ]; then log "Git credential helper configured (password auth)" fi @@ -61,16 +61,21 @@ _setup_git_creds() { # Configure git author identity for commits made by this container. # Derives identity from the resolved bot user (BOT_USER) to ensure commits # are visibly attributable to the correct bot in the forge timeline. +# BOT_USER is normally set by configure_git_creds() (#741); this function +# only falls back to its own API call if BOT_USER was not already resolved. configure_git_identity() { - # Resolve BOT_USER from FORGE_TOKEN if not already set + # Resolve BOT_USER from FORGE_TOKEN if not already set (configure_git_creds + # exports BOT_USER on success, so this is a fallback for edge cases only). if [ -z "${BOT_USER:-}" ] && [ -n "${FORGE_TOKEN:-}" ]; then BOT_USER=$(curl -sf --max-time 10 \ -H "Authorization: token ${FORGE_TOKEN}" \ "${FORGE_URL:-http://localhost:3000}/api/v1/user" 2>/dev/null | jq -r '.login // empty') || true fi - # Default to dev-bot if resolution fails - BOT_USER="${BOT_USER:-dev-bot}" + if [ -z "${BOT_USER:-}" ]; then + log "WARNING: Could not resolve bot username for git identity — commits will use fallback" + BOT_USER="agent" + fi # Configure git identity for all repositories gosu agent git config --global user.name "${BOT_USER}" diff --git a/lib/git-creds.sh b/lib/git-creds.sh index 6b328cd..7ba8524 100644 --- a/lib/git-creds.sh +++ b/lib/git-creds.sh @@ -35,13 +35,35 @@ configure_git_creds() { forge_host=$(printf '%s' "$FORGE_URL" | sed 's|https\?://||; s|/.*||') forge_proto=$(printf '%s' "$FORGE_URL" | sed 's|://.*||') - # Determine the bot username from FORGE_TOKEN identity (or default to dev-bot) + local log_fn="${_GIT_CREDS_LOG_FN:-echo}" + + # Determine the bot username from FORGE_TOKEN identity with retry/backoff. + # Never fall back to a hardcoded default — a wrong username paired with the + # real password produces a cryptic 401 that's much harder to diagnose than + # a missing credential helper (#741). local bot_user="" if [ -n "${FORGE_TOKEN:-}" ]; then - bot_user=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ - "${FORGE_URL}/api/v1/user" 2>/dev/null | jq -r '.login // empty') || bot_user="" + local attempt + for attempt in 1 2 3 4 5; do + bot_user=$(curl -sf --max-time 5 -H "Authorization: token ${FORGE_TOKEN}" \ + "${FORGE_URL}/api/v1/user" 2>/dev/null | jq -r '.login // empty') || bot_user="" + if [ -n "$bot_user" ]; then + break + fi + $log_fn "WARNING: Forgejo not reachable (attempt ${attempt}/5) — retrying in ${attempt}s" + sleep "$attempt" + done fi - bot_user="${bot_user:-dev-bot}" + + if [ -z "$bot_user" ]; then + $log_fn "ERROR: Could not determine bot username from FORGE_TOKEN after 5 attempts — credential helper NOT configured" + $log_fn "ERROR: git push will fail until this is resolved. Restart the container after Forgejo is healthy." + return 1 + fi + + # Export BOT_USER so downstream functions (e.g. configure_git_identity) can + # reuse the resolved value without a redundant API call. + export BOT_USER="$bot_user" local helper_path="${home_dir}/.git-credentials-helper" @@ -77,6 +99,17 @@ CREDEOF else git config --global --add safe.directory '*' fi + + # Verify the credential helper actually authenticates (#741). + # A helper that was written with a valid username but a mismatched password + # would silently 401 on every push — catch it now. + if ! curl -sf --max-time 5 -u "${bot_user}:${FORGE_PASS}" \ + "${FORGE_URL}/api/v1/user" >/dev/null 2>&1; then + $log_fn "ERROR: credential helper verification failed — ${bot_user}:FORGE_PASS rejected by Forgejo" + rm -f "$helper_path" + return 1 + fi + $log_fn "Git credential helper verified: ${bot_user}@${forge_host}" } # repair_baked_cred_urls [--as RUN_AS_CMD] DIR [DIR ...]