From 9f5a6f9942af3cb008466eaa5983d9e36d349e8f Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 27 Mar 2026 14:29:22 +0000 Subject: [PATCH] fix: agents container: dev-poll fails because factory is mounted read-only (#781) Add DISINTO_LOG_DIR to lib/env.sh: points to $HOME/data/logs inside the container (writable volume) and $FACTORY_ROOT on the host (existing behavior). Update all agent scripts to write logs, CI fix tracker, metrics, and vault locks to DISINTO_LOG_DIR instead of FACTORY_ROOT. This keeps the factory mount read-only while ensuring all writable state lands on the persistent data volume. Co-Authored-By: Claude Opus 4.6 (1M context) --- action/action-agent.sh | 2 +- action/action-poll.sh | 2 +- dev/dev-agent.sh | 2 +- dev/dev-poll.sh | 4 ++-- lib/env.sh | 6 +++++- review/review-pr.sh | 2 +- site/collect-engagement.sh | 2 +- site/collect-metrics.sh | 2 +- supervisor/supervisor-poll.sh | 6 +++--- vault/vault-fire.sh | 4 ++-- vault/vault-poll.sh | 4 ++-- vault/vault-reject.sh | 4 ++-- 12 files changed, 22 insertions(+), 18 deletions(-) diff --git a/action/action-agent.sh b/action/action-agent.sh index e6e55ff..4e11d98 100755 --- a/action/action-agent.sh +++ b/action/action-agent.sh @@ -36,7 +36,7 @@ source "$(dirname "$0")/../lib/formula-session.sh" source "$(dirname "$0")/../dev/phase-handler.sh" SESSION_NAME="action-${PROJECT_NAME}-${ISSUE}" LOCKFILE="/tmp/action-agent-${ISSUE}.lock" -LOGFILE="${FACTORY_ROOT}/action/action-poll-${PROJECT_NAME:-default}.log" +LOGFILE="${DISINTO_LOG_DIR}/action/action-poll-${PROJECT_NAME:-default}.log" IDLE_TIMEOUT="${ACTION_IDLE_TIMEOUT:-14400}" # 4h default MAX_LIFETIME="${ACTION_MAX_LIFETIME:-28800}" # 8h default wall-clock cap SESSION_START_EPOCH=$(date +%s) diff --git a/action/action-poll.sh b/action/action-poll.sh index ba0c4ec..8d67c47 100755 --- a/action/action-poll.sh +++ b/action/action-poll.sh @@ -19,7 +19,7 @@ FORGE_TOKEN="${FORGE_ACTION_TOKEN:-${FORGE_TOKEN}}" source "$(dirname "$0")/../lib/guard.sh" check_active action -LOGFILE="${FACTORY_ROOT}/action/action-poll-${PROJECT_NAME:-default}.log" +LOGFILE="${DISINTO_LOG_DIR}/action/action-poll-${PROJECT_NAME:-default}.log" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" log() { diff --git a/dev/dev-agent.sh b/dev/dev-agent.sh index a717f95..14792c5 100755 --- a/dev/dev-agent.sh +++ b/dev/dev-agent.sh @@ -60,7 +60,7 @@ status() { printf '[%s] dev-agent #%s: %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$ISSUE" "$*" > "$STATUSFILE" log "$*" } -LOGFILE="${FACTORY_ROOT}/dev/dev-agent.log" +LOGFILE="${DISINTO_LOG_DIR}/dev/dev-agent.log" PREFLIGHT_RESULT="/tmp/dev-agent-preflight.json" BRANCH="fix/issue-${ISSUE}" WORKTREE="/tmp/${PROJECT_NAME}-worktree-${ISSUE}" diff --git a/dev/dev-poll.sh b/dev/dev-poll.sh index e348894..bddd05f 100755 --- a/dev/dev-poll.sh +++ b/dev/dev-poll.sh @@ -32,7 +32,7 @@ UNDERSPECIFIED_LABEL_ID=$(forge_api GET "/labels" 2>/dev/null \ UNDERSPECIFIED_LABEL_ID="${UNDERSPECIFIED_LABEL_ID:-1300816}" # Track CI fix attempts per PR to avoid infinite respawn loops -CI_FIX_TRACKER="${FACTORY_ROOT}/dev/ci-fixes-${PROJECT_NAME:-default}.json" +CI_FIX_TRACKER="${DISINTO_LOG_DIR}/dev/ci-fixes-${PROJECT_NAME:-default}.json" CI_FIX_LOCK="${CI_FIX_TRACKER}.lock" ci_fix_count() { local pr="$1" @@ -399,7 +399,7 @@ Instructions: API="${FORGE_API}" LOCKFILE="/tmp/dev-agent-${PROJECT_NAME:-default}.lock" -LOGFILE="${FACTORY_ROOT}/dev/dev-agent-${PROJECT_NAME:-default}.log" +LOGFILE="${DISINTO_LOG_DIR}/dev/dev-agent-${PROJECT_NAME:-default}.log" PREFLIGHT_RESULT="/tmp/dev-agent-preflight.json" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" diff --git a/lib/env.sh b/lib/env.sh index ca8d40e..48734a5 100755 --- a/lib/env.sh +++ b/lib/env.sh @@ -12,8 +12,12 @@ FACTORY_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # maps land on the persistent volume instead of /tmp (which is ephemeral). if [ "${DISINTO_CONTAINER:-}" = "1" ]; then DISINTO_DATA_DIR="${HOME}/data" - mkdir -p "${DISINTO_DATA_DIR}" + DISINTO_LOG_DIR="${DISINTO_DATA_DIR}/logs" + mkdir -p "${DISINTO_DATA_DIR}" "${DISINTO_LOG_DIR}"/{dev,action,review,supervisor,vault,site,metrics} +else + DISINTO_LOG_DIR="${FACTORY_ROOT}" fi +export DISINTO_LOG_DIR # Load secrets: prefer .env.enc (SOPS-encrypted), fall back to plaintext .env. # Inside the container, compose already injects env vars via env_file + environment diff --git a/review/review-pr.sh b/review/review-pr.sh index 2a83573..fd41404 100755 --- a/review/review-pr.sh +++ b/review/review-pr.sh @@ -11,7 +11,7 @@ git -C "$FACTORY_ROOT" pull --ff-only origin main 2>/dev/null || true PR_NUMBER="${1:?Usage: review-pr.sh [--force]}" FORCE="${2:-}" API="${FORGE_API}" -LOGFILE="${FACTORY_ROOT}/review/review.log" +LOGFILE="${DISINTO_LOG_DIR}/review/review.log" SESSION="review-${PROJECT_NAME}-${PR_NUMBER}" PHASE_FILE="/tmp/review-session-${PROJECT_NAME}-${PR_NUMBER}.phase" OUTPUT_FILE="/tmp/${PROJECT_NAME}-review-output-${PR_NUMBER}.json" diff --git a/site/collect-engagement.sh b/site/collect-engagement.sh index 6430197..37aa98d 100644 --- a/site/collect-engagement.sh +++ b/site/collect-engagement.sh @@ -21,7 +21,7 @@ FACTORY_ROOT="$(dirname "$SCRIPT_DIR")" # shellcheck source=../lib/env.sh source "$FACTORY_ROOT/lib/env.sh" -LOGFILE="${FACTORY_ROOT}/site/collect-engagement.log" +LOGFILE="${DISINTO_LOG_DIR}/site/collect-engagement.log" log() { printf '[%s] collect-engagement: %s\n' \ "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" >> "$LOGFILE" diff --git a/site/collect-metrics.sh b/site/collect-metrics.sh index c9437f8..a52bbcc 100644 --- a/site/collect-metrics.sh +++ b/site/collect-metrics.sh @@ -21,7 +21,7 @@ source "$FACTORY_ROOT/lib/env.sh" # shellcheck source=../lib/ci-helpers.sh source "$FACTORY_ROOT/lib/ci-helpers.sh" 2>/dev/null || true -LOGFILE="${FACTORY_ROOT}/site/collect-metrics.log" +LOGFILE="${DISINTO_LOG_DIR}/site/collect-metrics.log" log() { printf '[%s] collect-metrics: %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" >> "$LOGFILE" } diff --git a/supervisor/supervisor-poll.sh b/supervisor/supervisor-poll.sh index 81494d3..5e68f4a 100755 --- a/supervisor/supervisor-poll.sh +++ b/supervisor/supervisor-poll.sh @@ -16,13 +16,13 @@ set -euo pipefail source "$(dirname "$0")/../lib/env.sh" source "$(dirname "$0")/../lib/ci-helpers.sh" -LOGFILE="${FACTORY_ROOT}/supervisor/supervisor.log" +LOGFILE="${DISINTO_LOG_DIR}/supervisor/supervisor.log" STATUSFILE="/tmp/supervisor-status" LOCKFILE="/tmp/supervisor-poll.lock" PROMPT_FILE="${FACTORY_ROOT}/supervisor/PROMPT.md" PROJECTS_DIR="${FACTORY_ROOT}/projects" -METRICS_FILE="${FACTORY_ROOT}/metrics/supervisor-metrics.jsonl" +METRICS_FILE="${DISINTO_LOG_DIR}/metrics/supervisor-metrics.jsonl" emit_metric() { printf '%s\n' "$1" >> "$METRICS_FILE" @@ -428,7 +428,7 @@ check_project() { AGE_MIN=$(( (NOW_EPOCH - UPDATED_EPOCH) / 60 )) if [ "$AGE_MIN" -gt 60 ]; then p3 "${proj_name}: PR #${pr}: CI passed, no review for ${AGE_MIN}min" - bash "${FACTORY_ROOT}/review/review-pr.sh" "$pr" >> "${FACTORY_ROOT}/review/review.log" 2>&1 & + bash "${FACTORY_ROOT}/review/review-pr.sh" "$pr" >> "${DISINTO_LOG_DIR}/review/review.log" 2>&1 & fixed "${proj_name}: Auto-triggered review for PR #${pr}" fi fi diff --git a/vault/vault-fire.sh b/vault/vault-fire.sh index 229825b..ad57022 100755 --- a/vault/vault-fire.sh +++ b/vault/vault-fire.sh @@ -18,8 +18,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" source "${SCRIPT_DIR}/vault-env.sh" OPS_VAULT_DIR="${OPS_REPO_ROOT}/vault" -LOCKS_DIR="${FACTORY_ROOT}/vault/.locks" -LOGFILE="${FACTORY_ROOT}/vault/vault.log" +LOCKS_DIR="${DISINTO_LOG_DIR}/vault/.locks" +LOGFILE="${DISINTO_LOG_DIR}/vault/vault.log" RESOURCES_FILE="${OPS_REPO_ROOT}/RESOURCES.md" log() { diff --git a/vault/vault-poll.sh b/vault/vault-poll.sh index ace8984..a32b31f 100755 --- a/vault/vault-poll.sh +++ b/vault/vault-poll.sh @@ -23,12 +23,12 @@ source "${SCRIPT_DIR}/../lib/env.sh" # Use vault-bot's own Forgejo identity (#747) FORGE_TOKEN="${FORGE_VAULT_TOKEN:-${FORGE_TOKEN}}" -LOGFILE="${FACTORY_ROOT}/vault/vault.log" +LOGFILE="${DISINTO_LOG_DIR}/vault/vault.log" STATUSFILE="/tmp/vault-status" LOCKFILE="/tmp/vault-poll.lock" VAULT_SCRIPT_DIR="${FACTORY_ROOT}/vault" OPS_VAULT_DIR="${OPS_REPO_ROOT}/vault" -LOCKS_DIR="${VAULT_SCRIPT_DIR}/.locks" +LOCKS_DIR="${DISINTO_LOG_DIR}/vault/.locks" TIMEOUT_HOURS=48 diff --git a/vault/vault-reject.sh b/vault/vault-reject.sh index 7339604..54fa127 100755 --- a/vault/vault-reject.sh +++ b/vault/vault-reject.sh @@ -9,8 +9,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" source "${SCRIPT_DIR}/vault-env.sh" OPS_VAULT_DIR="${OPS_REPO_ROOT}/vault" -LOGFILE="${FACTORY_ROOT}/vault/vault.log" -LOCKS_DIR="${FACTORY_ROOT}/vault/.locks" +LOGFILE="${DISINTO_LOG_DIR}/vault/vault.log" +LOCKS_DIR="${DISINTO_LOG_DIR}/vault/.locks" log() { printf '[%s] vault-reject: %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" >> "$LOGFILE"