shareAI-lab/learn-claude-code · error · ValueError
Mailbox path escapes directory: {agent!r}
Error message
Mailbox path escapes directory: {agent!r} What it means
The second check in MessageBus._path(): after the name passes the alphabet regex, the resolved .mailboxes/<agent>.jsonl path must still be inside MAILBOX_ROOT. Since the regex already forbids '/' and '.', this branch fires only when the environment shifts under the process — .mailboxes is a symlink, or MAILBOX_ROOT (resolved at import) no longer sits under the current WORKDIR because the workspace symlink changed.
Source
Thrown at s13_agent_teams/code.py:795
def is_valid_agent_name(name: str) -> bool:
return bool(VALID_AGENT_NAME.fullmatch(name))
class MessageBus:
"""Thread-safe file mailboxes with destructive reads."""
def __init__(self):
self._lock = threading.RLock()
self._changed = threading.Condition(self._lock)
def _path(self, agent: str) -> Path:
if not is_valid_agent_name(agent):
raise ValueError(f"Invalid mailbox recipient: {agent!r}")
path = (MAILBOX_DIR / f"{agent}.jsonl").resolve()
if not path.is_relative_to(MAILBOX_ROOT):
raise ValueError(f"Mailbox path escapes directory: {agent!r}")
return path
def _read_unlocked(self, agent: str) -> list[dict]:
inbox = self._path(agent)
if not inbox.exists():
return []
msgs = [json.loads(line) for line in inbox.read_text().splitlines()
if line.strip()]
inbox.unlink()
return msgs
def send(self, from_agent: str, to_agent: str, content: str,
msg_type: str = "message", metadata: dict | None = None):
msg = {"from": from_agent, "to": to_agent,
"content": content, "type": msg_type,
"ts": time.time(), "metadata": metadata or {}}
with self._changed:
MAILBOX_DIR.mkdir(parents=True, exist_ok=True)View on GitHub (pinned to 985456f4ad)
Solutions
- Make .mailboxes a real directory inside the workspace.
- Resolve WORKDIR at startup and keep it stable for the process lifetime.
- In tests, recreate the module after changing workspace paths.
Example fix
# before ln -s /mnt/shared/mail .mailboxes # after rm .mailboxes && mkdir .mailboxes
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def mailbox_root_inside_workdir() -> bool:
return MAILBOX_ROOT.is_relative_to(WORKDIR.resolve()) and not MAILBOX_DIR.is_symlink() Try / catch
try:
bus.send(src, dst, content)
except ValueError as exc:
if 'escapes directory' in str(exc):
logging.exception('mailbox layout unsafe; check %s for symlinks', MAILBOX_DIR)
raise Prevention
- Keep .mailboxes a real directory inside the workspace.
- Resolve WORKDIR at startup; don't rotate workspace symlinks mid-run.
- Add a startup assertion MAILBOX_ROOT.is_relative_to(WORKDIR.resolve()).
When it happens
Trigger: .mailboxes is a symlink to a directory outside WORKDIR; workspace symlink retargeted between import and message send; tests monkeypatching WORKDIR without recomputing MAILBOX_ROOT.
Common situations: Putting mailboxes on shared storage via symlink; macOS /tmp resolution; test fixture global mutation.
Related errors
- Tasks directory escapes workspace
- Mailbox path escapes directory: {agent!r}
- Memory path escapes the store: {filename}
- Worktree path escapes directory: {name!r}
- Tasks directory escapes workspace
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/b7f7b1702a39d6e9.
Report an issue: GitHub.