Some checks failed
The last structural fix for the collision class that has bitten three times. Both containers now clone the repository into their OWN named volume instead of bind-mounting a human's working tree, so an agent's local git config cannot capture a human's commits, a credential helper cannot leak a container-only path onto the host, and a `git add -A` cannot sweep another party's in-flight files. Cloned once at startup and never auto-pulled: pulling under a running agent moves files out from under whatever it is mid-edit, which is the same bug again. Accepted knowingly: Claude Code keys per-project memory off the working directory, so moving off the host path starts that memory empty. The corpus in docs/ is the memory that matters and it travels with the clone. Other changes: * docker/agent -> docker/decoder; the launcher is sylph-decoder. Roles, not "the agent", now that there is more than one. * /reborn is gone -- one repository now, so the port reads HANDOFF from its own checkout rather than through a live read-only mount of someone else's tree. * Canary mounts separately at /canary; it stays a fork tracking upstream. * A shared `sylpheed-exchange` volume at /exchange, with tools/ on PATH so `share` is available in both. * The decoder's credential file gets the .host-copy treatment the port already had -- `credential.helper=store` rewrites by rename-over-target, which is EBUSY on a bind mount and reports a fatal that is not one. * Budget split deliberately: decoder 5 cpu / 6 GB, port 3 / 4, leaving room for the planned Referee. "Half the host" was right when there was one agent. Prompts move to docs/agents/ and are rewritten around the protocol: the oracle is the running game, dynamic RE stays with the decoder, each iteration must attempt to refute one claim of the other, and neither may verify its way out of its own role.
93 lines
4.6 KiB
Bash
Executable File
93 lines
4.6 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
|
|
# Same reason as .claude.json above: `credential.helper=store` rewrites this
|
|
# file by rename-over-target, which fails with EBUSY on a bind mount. Copy it to
|
|
# a writable path; nothing is ever written back to the host's file.
|
|
if [ -f "$HOME/.git-credentials.host" ]; then
|
|
cp "$HOME/.git-credentials.host" "$HOME/.git-credentials" 2>/dev/null || true
|
|
chmod 600 "$HOME/.git-credentials" 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
|
|
|
|
# ── The repository, cloned into THIS AGENT'S OWN volume ─────────────────────
|
|
# Not a bind mount of a human's working tree. That arrangement bit this project
|
|
# three times: an agent's `git config --local` captured a human's commits, a
|
|
# credential helper leaked a container-only path onto the host, and a `git add
|
|
# -A` swept an agent's in-flight files into somebody else's commit. Separate
|
|
# checkouts make all three impossible rather than discouraged.
|
|
#
|
|
# Cloned ONCE. Never auto-pulled: pulling under a running agent moves files out
|
|
# from under whatever it is mid-edit, which is the same class of bug again.
|
|
if [ ! -d /work/.git ]; then
|
|
echo "[entrypoint] cloning ${SYLPH_REPO_URL:-https://git.mc02.dev/fabi/Sylpheed.git} into /work"
|
|
git clone --quiet "${SYLPH_REPO_URL:-https://git.mc02.dev/fabi/Sylpheed.git}" /work || {
|
|
echo "[entrypoint] clone FAILED -- the agent has no repository" >&2; }
|
|
else
|
|
echo "[entrypoint] /work is at $(git -C /work rev-parse --short HEAD 2>/dev/null) on $(git -C /work rev-parse --abbrev-ref HEAD 2>/dev/null)"
|
|
fi
|
|
|
|
# The shared exchange, for transient files that must not enter git history.
|
|
mkdir -p /exchange/files 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 "$@"
|