Skip to content
14 changes: 14 additions & 0 deletions api/oss/src/core/mounts/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
# The single session-bound mount: the agent's durable working directory.
_SESSION_CWD_NAME = "cwd"

# The name the runner symlinks the agent mount into the cwd as (runner: `AGENT_FILES_LINK_NAME`).
# geesefs degrades that symlink to a 0-byte OBJECT of the same name in the cwd store, so archiving
# must skip it (see `build_archive_work_list`) — its real content comes from the folded agent mount.
_AGENT_FILES_LINK_NAME = "agent-files"

# Default TTL (seconds) for signed mount credentials. Covers the mount lifetime for a
# turn; geesefs holds the creds without refresh, so a turn outliving this hits ExpiredToken.
_CREDENTIALS_TTL_SECONDS = 3600
Expand Down Expand Up @@ -1240,6 +1245,15 @@ async def build_archive_work_list(
)
continue
zip_path = "/".join([*pfx_segments, *rel_segments])
# Skip the cwd mount's `agent-files` fold marker. The runner symlinks the agent mount
# into the cwd as `agent-files/`, but geesefs degrades that symlink to a 0-byte OBJECT
# named `agent-files`. Archived as a FILE it collides with the `agent-files/` DIRECTORY
# the folded agent-mount source contributes (passed as its own mount, prefix
# "agent-files"); on extraction the file blocks the directory and the agent's files are
# lost. The FE display filters the same marker (`isAgentFilesFold`); mirror it here so
# the real content survives.
if zip_path == _AGENT_FILES_LINK_NAME:
continue
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
work.append((zip_path, obj.key, obj.size or 0, obj.mtime))

return work
Expand Down
28 changes: 28 additions & 0 deletions api/oss/tests/pytest/unit/test_mounts_file_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,34 @@ async def test_traversal_key_does_not_alias_a_real_entry(self):
assert zip_paths == ["a/report.txt"]


@pytest.mark.asyncio
class TestArchiveAgentFilesFold:
async def test_agent_files_fold_marker_is_skipped(self):
# geesefs degrades the runner's `agent-files` symlink to a 0-byte OBJECT named `agent-files`
# in the cwd store. Archived as a FILE it collides with the `agent-files/` DIRECTORY the folded
# agent mount contributes, so on extraction the file blocks the directory and the agent's files
# are lost. The bare marker must be skipped; real files and any `agent-files/…` content ship.
mount = _make_mount()
storage = FakeMountStorage()
service = MountsService(
mounts_dao=_StubDAO(mount),
mounts_store=storage,
bucket=_BUCKET,
)
mount_base = service._storage_key(project_id=mount.project_id, mount=mount)
bucket_store = storage._store.setdefault(_BUCKET, {})
for key in ["notes.md", "agent-files", "agent-files/keep.txt"]:
bucket_store[f"{mount_base}{key}"] = b"x"

work = await service.build_archive_work_list(
project_id=mount.project_id,
mounts=[MountArchiveSource(mount_id=mount.id)],
)

zip_paths = {zip_path for zip_path, *_rest in work}
assert zip_paths == {"notes.md", "agent-files/keep.txt"}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated


# ---------------------------------------------------------------------------
# Roundtrip
# ---------------------------------------------------------------------------
Expand Down
Loading