ship: fix 7 review findings from Ganymede + Rhea
- auto-deploy.sh: fail hard on checkout error (was silent || true), show syntax check errors (was 2>/dev/null), add flock concurrency guard, quote rsync excludes, fix agent-state path, add telegram/ rsync target, add smoke test failure comment - prune-branches.sh: only delete merged branches (is-ancestor check), show delete errors (was 2>/dev/null) - deploy.sh: show syntax check errors, add telegram/ rsync target - evaluate-trigger.sh: remove stale ^diagnostics/ pattern - AGENT-SOP.md: add stderr suppression rule, config.py constants rule Pentagon-Agent: Ship <1A6F9A42-AC52-4027-B8C5-3CB5FA3F7C28> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0d718f0786
commit
acc5a9e7bb
5 changed files with 29 additions and 7 deletions
|
|
@ -68,9 +68,11 @@ Check auto-deploy status: `journalctl -u teleo-auto-deploy -n 20`
|
||||||
## Shell and Python Safety
|
## Shell and Python Safety
|
||||||
|
|
||||||
- Run `bash -n script.sh` after modifying any shell script.
|
- Run `bash -n script.sh` after modifying any shell script.
|
||||||
|
- Never suppress stderr on critical git commands (`2>/dev/null || true`). Log errors, fail hard.
|
||||||
- Never interpolate shell variables into Python strings via `'$var'`.
|
- Never interpolate shell variables into Python strings via `'$var'`.
|
||||||
Pass values via `os.environ` or `sys.argv`.
|
Pass values via `os.environ` or `sys.argv`.
|
||||||
- Never write credentials to `.git/config`. Use per-command `git -c http.extraHeader`.
|
- Never write credentials to `.git/config`. Use per-command `git -c http.extraHeader`.
|
||||||
|
- Tunable constants live in `ops/pipeline-v2/lib/config.py`. Don't hardcode numbers in module files.
|
||||||
|
|
||||||
## Schema Changes
|
## Schema Changes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,13 @@
|
||||||
# Exits silently when nothing has changed.
|
# Exits silently when nothing has changed.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
LOCK_FILE="/tmp/teleo-auto-deploy.lock"
|
||||||
|
exec 9>"$LOCK_FILE"
|
||||||
|
if ! flock -n 9; then
|
||||||
|
logger -t "auto-deploy" "Another deploy is already running. Skipping."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
DEPLOY_CHECKOUT="/opt/teleo-eval/workspaces/deploy"
|
DEPLOY_CHECKOUT="/opt/teleo-eval/workspaces/deploy"
|
||||||
PIPELINE_DIR="/opt/teleo-eval/pipeline"
|
PIPELINE_DIR="/opt/teleo-eval/pipeline"
|
||||||
DIAGNOSTICS_DIR="/opt/teleo-eval/diagnostics"
|
DIAGNOSTICS_DIR="/opt/teleo-eval/diagnostics"
|
||||||
|
|
@ -33,7 +40,10 @@ fi
|
||||||
|
|
||||||
log "New commits: ${OLD_SHA:0:8} -> ${NEW_SHA:0:8}"
|
log "New commits: ${OLD_SHA:0:8} -> ${NEW_SHA:0:8}"
|
||||||
|
|
||||||
git checkout main --quiet 2>/dev/null || true
|
if ! git checkout main --quiet 2>&1; then
|
||||||
|
log "ERROR: git checkout main failed — dirty tree or corrupted index"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
if ! git pull --ff-only --quiet 2>&1; then
|
if ! git pull --ff-only --quiet 2>&1; then
|
||||||
log "ERROR: git pull --ff-only failed. Manual intervention needed."
|
log "ERROR: git pull --ff-only failed. Manual intervention needed."
|
||||||
exit 1
|
exit 1
|
||||||
|
|
@ -43,7 +53,7 @@ fi
|
||||||
ERRORS=0
|
ERRORS=0
|
||||||
for f in ops/pipeline-v2/lib/*.py ops/pipeline-v2/*.py ops/diagnostics/*.py; do
|
for f in ops/pipeline-v2/lib/*.py ops/pipeline-v2/*.py ops/diagnostics/*.py; do
|
||||||
[ -f "$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
if ! python3 -c "import ast, sys; ast.parse(open(sys.argv[1]).read())" "$f" 2>/dev/null; then
|
if ! python3 -c "import ast, sys; ast.parse(open(sys.argv[1]).read())" "$f" 2>&1; then
|
||||||
log "SYNTAX ERROR: $f"
|
log "SYNTAX ERROR: $f"
|
||||||
ERRORS=$((ERRORS + 1))
|
ERRORS=$((ERRORS + 1))
|
||||||
fi
|
fi
|
||||||
|
|
@ -55,7 +65,7 @@ fi
|
||||||
log "Syntax check passed"
|
log "Syntax check passed"
|
||||||
|
|
||||||
# Sync to working directories (mirrors deploy.sh logic)
|
# Sync to working directories (mirrors deploy.sh logic)
|
||||||
RSYNC_FLAGS="-az --exclude=__pycache__ --exclude=*.pyc --exclude=*.bak*"
|
RSYNC_FLAGS="-az --exclude='__pycache__' --exclude='*.pyc' --exclude='*.bak*'"
|
||||||
|
|
||||||
rsync $RSYNC_FLAGS ops/pipeline-v2/lib/ "$PIPELINE_DIR/lib/"
|
rsync $RSYNC_FLAGS ops/pipeline-v2/lib/ "$PIPELINE_DIR/lib/"
|
||||||
|
|
||||||
|
|
@ -63,6 +73,7 @@ for f in teleo-pipeline.py reweave.py batch-extract-50.sh; do
|
||||||
[ -f "ops/pipeline-v2/$f" ] && rsync $RSYNC_FLAGS "ops/pipeline-v2/$f" "$PIPELINE_DIR/$f"
|
[ -f "ops/pipeline-v2/$f" ] && rsync $RSYNC_FLAGS "ops/pipeline-v2/$f" "$PIPELINE_DIR/$f"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
rsync $RSYNC_FLAGS ops/pipeline-v2/telegram/ "$PIPELINE_DIR/telegram/"
|
||||||
rsync $RSYNC_FLAGS ops/diagnostics/ "$DIAGNOSTICS_DIR/"
|
rsync $RSYNC_FLAGS ops/diagnostics/ "$DIAGNOSTICS_DIR/"
|
||||||
rsync $RSYNC_FLAGS ops/agent-state/ "$AGENT_STATE_DIR/"
|
rsync $RSYNC_FLAGS ops/agent-state/ "$AGENT_STATE_DIR/"
|
||||||
[ -f ops/research-session.sh ] && rsync $RSYNC_FLAGS ops/research-session.sh /opt/teleo-eval/research-session.sh
|
[ -f ops/research-session.sh ] && rsync $RSYNC_FLAGS ops/research-session.sh /opt/teleo-eval/research-session.sh
|
||||||
|
|
@ -117,7 +128,8 @@ if [ -n "$RESTART" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$FAIL" -gt 0 ]; then
|
if [ "$FAIL" -gt 0 ]; then
|
||||||
log "WARNING: Smoke test failures. NOT updating stamp. Will retry next cycle."
|
# Code is already synced — push a fix, don't wait for next cycle
|
||||||
|
log "WARNING: Smoke test failures. NOT updating stamp. Will retry next cycle. Push a fix."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ echo "=== Pre-deploy syntax check ==="
|
||||||
ERRORS=0
|
ERRORS=0
|
||||||
for f in "$REPO_ROOT/ops/pipeline-v2/lib/"*.py "$REPO_ROOT/ops/pipeline-v2/"*.py "$REPO_ROOT/ops/diagnostics/"*.py; do
|
for f in "$REPO_ROOT/ops/pipeline-v2/lib/"*.py "$REPO_ROOT/ops/pipeline-v2/"*.py "$REPO_ROOT/ops/diagnostics/"*.py; do
|
||||||
[ -f "$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
if ! python3 -c "import ast, sys; ast.parse(open(sys.argv[1]).read())" "$f" 2>/dev/null; then
|
if ! python3 -c "import ast, sys; ast.parse(open(sys.argv[1]).read())" "$f" 2>&1; then
|
||||||
echo "SYNTAX ERROR: $f"
|
echo "SYNTAX ERROR: $f"
|
||||||
ERRORS=$((ERRORS + 1))
|
ERRORS=$((ERRORS + 1))
|
||||||
fi
|
fi
|
||||||
|
|
@ -76,6 +76,10 @@ echo "=== Diagnostics ==="
|
||||||
rsync $RSYNC_FLAGS "$REPO_ROOT/ops/diagnostics/" "$VPS_HOST:$VPS_DIAGNOSTICS/"
|
rsync $RSYNC_FLAGS "$REPO_ROOT/ops/diagnostics/" "$VPS_HOST:$VPS_DIAGNOSTICS/"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Telegram bot ==="
|
||||||
|
rsync $RSYNC_FLAGS "$REPO_ROOT/ops/pipeline-v2/telegram/" "$VPS_HOST:$VPS_PIPELINE/telegram/"
|
||||||
|
echo ""
|
||||||
|
|
||||||
echo "=== Agent state ==="
|
echo "=== Agent state ==="
|
||||||
rsync $RSYNC_FLAGS "$REPO_ROOT/ops/agent-state/" "$VPS_HOST:$VPS_AGENT_STATE/"
|
rsync $RSYNC_FLAGS "$REPO_ROOT/ops/agent-state/" "$VPS_HOST:$VPS_AGENT_STATE/"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ detect_code_pr() {
|
||||||
|
|
||||||
files=$(gh pr view "$pr_number" --json files --jq '.files[].path' 2>/dev/null || echo "")
|
files=$(gh pr view "$pr_number" --json files --jq '.files[].path' 2>/dev/null || echo "")
|
||||||
|
|
||||||
if echo "$files" | grep -qE "^ops/|^diagnostics/|\.py$|\.sh$|\.js$|\.html$|\.css$|\.json$"; then
|
if echo "$files" | grep -qE "^ops/|\.py$|\.sh$|\.js$|\.html$|\.css$|\.json$"; then
|
||||||
echo "true"
|
echo "true"
|
||||||
else
|
else
|
||||||
echo "false"
|
echo "false"
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,13 @@ while IFS= read -r branch; do
|
||||||
COUNT=$((COUNT + 1))
|
COUNT=$((COUNT + 1))
|
||||||
|
|
||||||
if [[ "$last_date" < "$CUTOFF" ]]; then
|
if [[ "$last_date" < "$CUTOFF" ]]; then
|
||||||
|
if ! git merge-base --is-ancestor "$branch" "$REMOTE/main" 2>/dev/null; then
|
||||||
|
echo " SKIP (unmerged): $short ($last_date)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
if $EXECUTE; then
|
if $EXECUTE; then
|
||||||
echo " DELETE: $short ($last_date)"
|
echo " DELETE: $short ($last_date)"
|
||||||
git push "$REMOTE" --delete "$short" 2>/dev/null && DELETE_COUNT=$((DELETE_COUNT + 1)) || echo " FAILED: $short"
|
git push "$REMOTE" --delete "$short" 2>&1 && DELETE_COUNT=$((DELETE_COUNT + 1)) || echo " FAILED: $short"
|
||||||
else
|
else
|
||||||
echo " WOULD DELETE: $short ($last_date)"
|
echo " WOULD DELETE: $short ($last_date)"
|
||||||
DELETE_COUNT=$((DELETE_COUNT + 1))
|
DELETE_COUNT=$((DELETE_COUNT + 1))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue