Report read-only paths after a session; known issues document

- run (on exit) and doctor scan the OpenCode log for 'Read-only file system' / EROFS, map
  the agent's XDG mirror back to the home directory and print a ready OCSB_RW_HOME line;
  writable, hidden and protected project paths are skipped
- docs/known-issues.md: open gaps and planned work (Docker, blocklist egress, DNS, shared
  network namespace, resource limits, undo, planted repos, credentials, agents working
  around guards); linked from both READMEs
This commit is contained in:
2026-10-30 05:52:37 -07:00
parent a516ecf744
commit 55d217e3cf
5 changed files with 116 additions and 3 deletions
+51 -1
View File
@@ -170,6 +170,54 @@ ocsb_git_audit() {
return 1
}
# Paths the session could not write to (tools log "Read-only file system" / EROFS): mapped
# back from the agent's XDG mirror to the user's home and reported with a ready config line.
# Paths already writable, deliberately hidden, or inside the project (protected) are skipped.
# ocsb_erofs_report [byte offset into the OpenCode log]
ocsb_erofs_report() {
local log=${OCSB_STATE_DIR}/data/opencode/log/opencode.log from=${1:-0} s=${OCSB_STATE_DIR}
local p rel n out=() seen=" " skip
[[ -f ${log} ]] || return 0
(($(stat -c %s "${log}") >= from)) || from=0
while IFS= read -r p; do
case ${p} in
"${s}/data/"*) rel=.local/share/${p#"${s}/data/"} ;;
"${s}/state/"*) rel=.local/state/${p#"${s}/state/"} ;;
"${s}/config/"*) rel=.config/${p#"${s}/config/"} ;;
"${s}/cache/"*) rel=.cache/${p#"${s}/cache/"} ;;
"${OCSB_PROJECT_DIR}"/* | "${OCSB_WORKTREES_DIR}"/* | "${s}"/*) continue ;;
"${HOME}"/*) rel=${p#"${HOME}/"} ;;
*) rel=/${p#/} ;;
esac
# The directory to open: ~/.local/share/<tool>, ~/.local/bin, ~/.config/<tool>, ~/.<tool>
case ${rel} in
/*) rel=$(cut -d/ -f1-3 <<<"${rel}") ;;
.local/share/* | .local/state/*) rel=$(cut -d/ -f1-3 <<<"${rel}") ;;
.local/* | .config/*) rel=$(cut -d/ -f1-2 <<<"${rel}") ;;
*) rel=${rel%%/*} ;;
esac
rel=${rel%/.tmp*}
skip=0
for n in "${OCSB_RW_HOME[@]}" "${OCSB_HIDE_HOME[@]}"; do
[[ ${rel} == "${n}" || ${rel} == "${n}"/* ]] && skip=1
done
[[ ${skip} == 1 || ${seen} == *" ${rel} "* ]] && continue
seen+="${rel} "
out+=("${rel}")
done < <(tail -c +"$((from + 1))" "${log}" | grep -E 'Read-only file system|EROFS' |
sed -E 's/ (directory|cwd|path\.cwd)=[^ ]*//g' | grep -oE '/[A-Za-z0-9._@+/-]+' | grep -E "^(${HOME}|/opt|/usr/local|/var)/" | sort -u)
((${#out[@]} == 0)) && return 0
ocsb_warn "the session hit read-only paths (a tool or MCP server may have failed):"
for rel in "${out[@]}"; do
if [[ ${rel} == /* ]]; then
printf ' %s → if needed: OCSB_RW+=(%s)\n' "${rel}" "${rel}" >&2
else
printf ' ~/%s → OCSB_RW_HOME+=(%s)\n' "${rel}" "${rel}" >&2
fi
done
echo " add the line to .opencode-sandbox/sandbox.conf (or local/sandbox.conf just for you) and restart" >&2
}
# bubblewrap arguments for the sandbox (variant: read-only host, writable project)
ocsb_bwrap_args() {
local p f uid
@@ -310,7 +358,9 @@ ocsb_run() {
fi
ocsb_bwrap_args
# On any exit (Ctrl-C too): stop the bus proxy, look for what the session planted
trap 'kill "${OCSB_DBUS_PROXY_PID:-}" 2>/dev/null; ocsb_git_audit || true' EXIT
# (global: the trap runs after this function has returned)
OCSB_LOG_START=$(stat -c %s "${OCSB_STATE_DIR}/data/opencode/log/opencode.log" 2>/dev/null || echo 0)
trap 'kill "${OCSB_DBUS_PROXY_PID:-}" 2>/dev/null; ocsb_git_audit || true; ocsb_erofs_report "${OCSB_LOG_START}"' EXIT
cmd=(bwrap "${OCSB_BWRAP[@]}" -- "${cmd[@]}")
# No permission prompts in the sandbox: permission rules from all configs form one
# list, the last match wins, and OPENCODE_CONFIG_CONTENT is the last document.