molecule-ai-workspace-runtime/molecule_runtime/scripts/molecule-gh-token-refresh.sh
rabbitblood f1329fe230 feat: ship GitHub credential-helper inline in runtime (fixes #1933 class)
Lifts the per-template wiring (Dockerfile COPY + entrypoint.sh git config
+ nohup daemon launch) into the Python runtime. Templates that depend
on molecule-ai-workspace-runtime get the behavior automatically — they
no longer need to maintain their own copy of the helper scripts or
remember to write the right git config in their entrypoint.

Background:
- GitHub App installation tokens (ghs_…) expire ~60min after issue
- claude-code-default template shipped without wiring → 39 workspaces
  lost their tokens, three PMs' A2A queues filled with retry-status
  messages, manual fleet restart required (cycle 62-66 incident)

This commit:
- Adds molecule_runtime/scripts/{molecule-git-token-helper.sh,
  molecule-gh-token-refresh.sh} as package data (copies from canonical
  workspace/scripts/ in molecule-monorepo)
- Adds molecule_runtime/credential_helper.py with
  install_credential_helper() that:
    1. Extracts bundled scripts to ~/.molecule-runtime/scripts/
    2. Configures git credential.helper for github.com
    3. Creates ~/.molecule-token-cache/ mode 0700
    4. Spawns refresh daemon under respawn loop (PID file dedup)
    5. Runs initial gh auth login --with-token
- Hooks call site early in main.py (step 0.1, before config load)
- Fails-soft: each step independently fault-tolerant; missing git/gh
  binary doesn't block runtime startup

Bumped to 0.1.10. Templates can drop their entrypoint.sh credential
helper setup once they update the runtime pin (separate PRs per template).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:41:32 -07:00

58 lines
2.0 KiB
Bash

#!/bin/bash
# molecule-gh-token-refresh.sh — background daemon that keeps GitHub
# credentials fresh inside Molecule AI workspace containers.
#
# Started by entrypoint.sh under a respawn wrapper. Every
# REFRESH_INTERVAL_SEC + jitter (default 45 min ± 2 min) it calls the
# credential helper's _refresh_gh action.
#
# # Jitter
# A 0..120s random offset prevents 39 containers from synchronizing
# their refresh requests against /workspaces/:id/github-installation-token.
#
# # Security
# - This daemon NEVER prints token values. Failures log the helper's
# exit code only, not its stderr, so token bytes can't leak via the
# docker log pipeline.
# - The helper script is responsible for chmod 600 on cache files.
#
set -uo pipefail
HELPER_SCRIPT="${TOKEN_HELPER_SCRIPT:-/app/scripts/molecule-git-token-helper.sh}"
REFRESH_INTERVAL_SEC="${TOKEN_REFRESH_INTERVAL_SEC:-2700}" # 45 min
JITTER_MAX_SEC="${TOKEN_REFRESH_JITTER_SEC:-120}"
INITIAL_DELAY_SEC="${TOKEN_REFRESH_INITIAL_DELAY_SEC:-60}"
log() {
echo "[molecule-gh-token-refresh] $(date -u '+%Y-%m-%dT%H:%M:%SZ') $*" >&2
}
jittered_sleep() {
local base="$1"
local jitter=$((RANDOM % (JITTER_MAX_SEC + 1)))
sleep $((base + jitter))
}
log "starting (interval=${REFRESH_INTERVAL_SEC}s ± ${JITTER_MAX_SEC}s, initial_delay=${INITIAL_DELAY_SEC}s)"
sleep "${INITIAL_DELAY_SEC}"
# Initial refresh — prime the cache + gh auth immediately after boot.
# Discard helper output to /dev/null so token can't leak via docker logs.
log "initial token refresh"
if bash "${HELPER_SCRIPT}" _refresh_gh >/dev/null 2>&1; then
log "initial refresh succeeded"
else
log "initial refresh failed (rc=$?) — will retry in ~${REFRESH_INTERVAL_SEC}s"
fi
# Steady-state loop.
while true; do
jittered_sleep "${REFRESH_INTERVAL_SEC}"
log "periodic token refresh"
if bash "${HELPER_SCRIPT}" _refresh_gh >/dev/null 2>&1; then
log "refresh succeeded"
else
log "refresh failed (rc=$?) — will retry in ~${REFRESH_INTERVAL_SEC}s"
fi
done