From 67136dc33e521289375696d41f5e7bb3d11d2606 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Fri, 28 Aug 2026 21:07:13 +0200 Subject: [PATCH 1/2] port: fix the credential mount, the /reborn pull, and cargo re-fetching Three fixes from the port agent's first infrastructure report. **A. The credential file is written, so it cannot be a read-only mount.** `credential.helper=store` rewrites its file after a successful auth: temp file, then rename over the target. Renaming onto a bind-mount point gives EBUSY, which surfaces as `fatal: unable to write credential store: Device or resource busy`. The push succeeds anyway, and that is the real hazard -- a `fatal:` line that is routinely wrong teaches the reader to ignore the one that is real. It also fired intermittently, so it read as flakiness rather than as a mount. Fixed by mirroring the pattern already used for .claude.json: mount it as `.git-credentials.host:ro` and have the entrypoint copy it to a writable ~/.git-credentials at 600. Mounting rw would also silence it, but then the container can clobber the host's real credential file; copying cannot. **B. `git -C /reborn pull` can never work, and should not.** /reborn is a live read-only mount of the RE agent's working tree -- it updates itself, and pulling would move another agent's checkout. The prompt now says so, and adds the consequence the agent found the hard way: because the mount is live, HANDOFF can move mid-iteration, so anything copied out of it (BLOCKED.md especially) may already be stale and must be re-checked rather than trusted. **C.** CARGO_HOME moves to a named volume; it was on the container overlay, so the pinned decoder source was re-fetched from the network on every fresh start. Also adds SYLPH_PORT_REPO, so this launcher can be run from a worktree without repointing the agent's checkout -- which is how these edits were made, the agent being mid-iteration on auto/p0-exporter in the shared tree. Co-Authored-By: Claude Opus 5 --- docker/entrypoint.sh | 8 ++++++++ docker/sylph-port | 20 ++++++++++++++++++-- docs/loop-task.md | 12 ++++++++++-- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 2ee5700c..73969dc5 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -32,6 +32,14 @@ fi 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 diff --git a/docker/sylph-port b/docker/sylph-port index e0c94aab..28af6b5b 100755 --- a/docker/sylph-port +++ b/docker/sylph-port @@ -11,6 +11,7 @@ # # Env: # SYLPH_PORT_CPUS / SYLPH_PORT_MEM_GB override the cap (default 3 / 4) +# SYLPH_PORT_REPO repo to mount at /work (default: this script's parent) # SYLPH_REBORN path to the Syplheed-Reborn checkout (read-only mount) # SYLPH_DISC extracted disc root # SYLPH_GIT_CREDENTIALS file with `https://:@host` for push-work @@ -30,7 +31,9 @@ set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO="$(cd "$HERE/.." && pwd)" +# The repo to mount at /work. Overridable so this script can be run from a +# worktree -- a human editing on `main` must not repoint the agent's checkout. +REPO="${SYLPH_PORT_REPO:-$(cd "$HERE/.." && pwd)}" IMAGE="${SYLPH_PORT_IMAGE:-sylpheed-port:latest}" NAME="${SYLPH_PORT_NAME:-sylpheed-port}" @@ -55,6 +58,9 @@ docker_args() { --pids-limit 2048 -v "$REPO:/work" -v "sylpheed-port-target:/sylph-home/port/target-container" + # CARGO_HOME on a volume, not the container overlay: without it the pinned + # decoder source is re-fetched from the network on every fresh container. + -v "sylpheed-port-cargo:/sylph-home/port/.cargo" -v "${SYLPH_CLAUDE_HOME:-$HOME/.claude}:/sylph-home/port/.claude" -v "${SYLPH_CLAUDE_JSON:-$HOME/.claude.json}:/sylph-home/port/.claude.host.json:ro" -e "PROJECT_DIR=/work" @@ -86,9 +92,19 @@ docker_args() { -e "GIT_COMMITTER_EMAIL=port-agent@localhost" ) + # Mounted as `.host` and copied to a writable file by the entrypoint, exactly + # like .claude.json. `credential.helper=store` REWRITES its file after a + # successful auth -- it writes a temp file and renames over the target, and + # renaming onto a bind-mount point gives EBUSY, which surfaces as + # `fatal: unable to write credential store: Device or resource busy`. + # + # The push still succeeds, which is the actual danger: a `fatal:` line that is + # routinely wrong teaches the reader to ignore the one that is real. Mounting + # rw would also silence it, but then the container can clobber the host's + # credential file; copying cannot. local gitcred="${SYLPH_GIT_CREDENTIALS:-$HOME/.sylph-git-credentials}" if [ -f "$gitcred" ]; then - _out+=(-v "$gitcred:/sylph-home/port/.git-credentials:ro") + _out+=(-v "$gitcred:/sylph-home/port/.git-credentials.host:ro") else echo "==> NOTE: no git credentials at $gitcred — the agent cannot push," >&2 echo " so its work dies with the container." >&2 diff --git a/docs/loop-task.md b/docs/loop-task.md index 5e5471cf..395115a5 100644 --- a/docs/loop-task.md +++ b/docs/loop-task.md @@ -16,8 +16,16 @@ believed later. If you need an answer the disc has not given you, write it in 1. `docs/MISSION.md` — milestones, gates, scope. 2. `/reborn/docs/port/HANDOFF.md` — **the contract.** What is decoded, what was - measured off the running game, and what is known undecodable. `git -C /reborn - pull` first; the RE agent publishes continuously. + measured off the running game, and what is known undecodable. + + **It is a live read-only mount of the RE agent's working tree**, so it updates + itself and there is nothing to pull — `git -C /reborn pull` cannot work (the + mount is read-only) and should not: it would move another agent's checkout. + `git -C /reborn log -1` shows where they are. + + Because it is live, **it can move under you mid-iteration.** Anything you + copied out of it earlier — `docs/BLOCKED.md` especially — may already be + stale. Re-check it against HANDOFF before trusting it. 3. `docs/FORMAT.md` — the open format. It is versioned and it is yours to revise, but a change is a deliberate act with a version bump. 4. `docs/BLOCKED.md` — what you are waiting on, so you do not re-discover it. From 11eac797a0f38c50465eae5ca8a54d6b28fda79f Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Fri, 28 Aug 2026 21:15:24 +0200 Subject: [PATCH 2/2] port: push-work must not configure the repository it runs in It set `git config --local credential.helper "store --file=$HOME/..."`, which PERSISTS in the repository. The repo is a bind mount the host also uses, so the host's git inherited `store --file=/sylph-home/port/.git-credentials` -- a path that exists only inside the container -- and every host push then failed with `unable to get credential storage lock: No such file or directory`. Same root as the EBUSY credential error, from the other side: a tool that configures a shared repository to suit itself breaks every other user of that repository. Now applied with `-c` to the single push invocation. Cleared the leaked setting from both checkouts by hand. Syplheed-Reborn had it too, from the RE container's copy of this script. Co-Authored-By: Claude Opus 5 --- docker/bin/push-work | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docker/bin/push-work b/docker/bin/push-work index 74ac8a28..5a1d13b3 100755 --- a/docker/bin/push-work +++ b/docker/bin/push-work @@ -49,8 +49,17 @@ if [ ! -s "$HOME/.git-credentials" ]; then exit 1 fi -# `store` reads the file we mounted; nothing is written back (it is read-only). -git config --local credential.helper "store --file=$HOME/.git-credentials" +# Applied to THIS COMMAND ONLY, via `-c`, never `git config --local`. +# +# Writing it to --local config persists it in the repository, and this repo is a +# bind mount the host also uses -- so the host's git inherited +# `store --file=/sylph-home/port/.git-credentials`, a path that exists only +# inside the container, and every host push then failed with +# `unable to get credential storage lock: No such file or directory`. +# +# A tool that configures a shared repository to suit itself breaks every other +# user of that repository. Keep it to the invocation. +CRED_HELPER="store --file=$HOME/.git-credentials" ahead=$(git rev-list --count "origin/$branch..$branch" 2>/dev/null || git rev-list --count HEAD) echo "push-work: $branch — $ahead commit(s) to publish" @@ -63,5 +72,5 @@ fi # --force-with-lease is deliberately NOT offered. If this is rejected as # non-fast-forward, someone else moved the branch: fetch and merge, do not # overwrite. -git push --set-upstream origin "$branch" +git -c "credential.helper=$CRED_HELPER" push --set-upstream origin "$branch" echo "push-work: pushed $branch"