Files
Sylpheed/docker/entrypoint.sh
MechaCat02 58c6d3874c port: fix the container exiting instead of starting the agent
`loose` exited 126 with "File name too long": the entrypoint ended in
`exec "$@"`, so the whole loop-task.md prompt was handed to exec as a command
and the markdown was tried as a filename.

The launcher was modelled on the RE container's but the entrypoint was written
fresh and simpler, and it left out the half that matters -- the block that turns
a `/loop ...` argument into a Claude Code invocation. Ported it over, with the
two gates that make an unattended run possible: seeding ~/.claude.json so the
first-run wizard does not stop on the theme picker, and claude-autonomous, which
answers the Bypass Permissions disclaimer that has no config key to skip it.

Also mounts the host's Claude credentials, which the container had no way to
reach, and passes SYLPH_AUTONOMOUS=1 from `loose`.

Verified: container stays up, display :97 ready, /reborn mounted read-only,
Remote Control registered, Claude Code started in /work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 20:31:32 +02:00

65 lines
3.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Bring up the headless display, then hand over.
#
# Xvfb and openbox are started as children of PID 1 (tini), NOT of the agent's
# shell, so they outlive any single command. The RE container learned this the
# hard way: a display owned by a shell gets reaped when that shell exits, which
# reads as "Xvfb dies on its own every few minutes".
set -euo pipefail
: "${DISPLAY:=:97}"
: "${SCREEN_GEOMETRY:=1280x720x24}"
if ! xdpyinfo -display "$DISPLAY" >/dev/null 2>&1; then
Xvfb "$DISPLAY" -screen 0 "$SCREEN_GEOMETRY" -nolisten tcp &
for _ in $(seq 50); do
xdpyinfo -display "$DISPLAY" >/dev/null 2>&1 && break
sleep 0.1
done
openbox >/dev/null 2>&1 &
fi
echo "[entrypoint] display $DISPLAY ready ($SCREEN_GEOMETRY)"
if [ -d /reborn ]; then
echo "[entrypoint] /reborn mounted read-only — HANDOFF.md is the contract"
fi
# Seed ~/.claude.json from the host's read-only copy, then stamp onboarding as
# complete. Claude Code re-runs its first-run wizard whenever
# lastOnboardingVersion differs from the installed version, so a container with a
# newer Claude than the host stops on the theme picker -- no error, no log line,
# and an unattended agent sits there forever.
if [ -f "$HOME/.claude.host.json" ] && [ ! -s "$HOME/.claude.json" ]; then
cp "$HOME/.claude.host.json" "$HOME/.claude.json" 2>/dev/null || true
fi
CLAUDE_VER=$(claude --version 2>/dev/null | grep -oE '^[0-9][0-9.]*' || echo 0.0.0)
python3 /usr/local/bin/seed-claude-config.py "$HOME/.claude.json" "$CLAUDE_VER" \
"$PWD" "${PROJECT_DIR:-/work}" "$HOME" || true
chmod 600 "$HOME/.claude.json" 2>/dev/null || true
# ── Claude Code ──────────────────────────────────────────────────────────────
# Without this the loop prompt is handed to `exec` as a command, and the whole
# markdown file is tried as a filename: exit 126, "File name too long".
if [ "${SYLPH_AUTONOMOUS:-0}" = "1" ]; then
# Drop the image's default CMD first, or `claude` is handed the literal string
# "bash" as its prompt and answers a question nobody asked.
if [ "$#" -eq 1 ] && [ "$1" = "bash" ]; then
set --
fi
# Remote Control registers the session with the account so the agent can be
# reached from claude.ai -- the point of a detached run being that nobody is
# sitting in front of it. The name is passed EXPLICITLY: the flag's value is
# optional, so a bare --remote-control swallows the /loop prompt after it.
if [ "${SYLPH_REMOTE:-1}" != "0" ]; then
set -- --remote-control "${SYLPH_REMOTE_NAME:-sylpheed-port}" "$@"
echo "[entrypoint] Remote Control as '${SYLPH_REMOTE_NAME:-sylpheed-port}'"
fi
# claude-autonomous wraps `claude --dangerously-skip-permissions` in a pty and
# answers the one-time first-run gates. The Bypass Permissions disclaimer has
# no config key that skips it, so unattended it hangs forever.
set -- claude-autonomous "$@"
echo "[entrypoint] starting Claude Code in $(pwd)"
fi
exec "$@"