langchain-ai/deepagents · error · RuntimeError

archive backend returned no response for {path}

Error message

archive backend returned no response for {path}

What it means

`_previous_content` reads the archived conversation snapshot from the archive backend via `adownload_files([path])` before appending. If the backend returns an empty response list, the code cannot distinguish 'missing' (empty list vs FILE_NOT_FOUND) and raises RuntimeError rather than silently treating history as absent, which would risk overwriting existing archives.

Source

Thrown at libs/code/deepagents_code/offload_middleware.py:571

            self.summary,
            file_path,
            self.state_cutoff,
            self.session_id,
        )

    async def _previous_content(self, path: str) -> tuple[bool, str]:
        """Read the archive snapshot needed to undo an uncommitted append.

        Returns:
            Whether the archive existed and its prior UTF-8 content.

        Raises:
            RuntimeError: If the backend cannot return the archive snapshot.
        """
        responses = await self.backend.adownload_files([path])
        if not responses:
            msg = f"archive backend returned no response for {path}"
            raise RuntimeError(msg)
        response = responses[0]
        if response.error == FILE_NOT_FOUND:
            return False, ""
        if response.error is not None:
            msg = f"archive read failed for {path}: {response.error}"
            raise RuntimeError(msg)
        content = response.content or b""
        return True, content.decode("utf-8")

    async def write(self) -> _ArchiveAppend | None:
        """Append staged messages and retain enough state for rollback.

        Returns:
            The reversible append, or `None` when the SDK could not write it.
        """
        path = self.summarization._get_history_path(self.session_id)
        existed, previous = await self._previous_content(path)
        guard = cast("BackendProtocol", _ArchiveReadGuard(self.backend))

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Fix the backend so it returns a FileDownloadResponse with `error` set instead of returning an empty list.
  2. Check backend connectivity/permissions so `adownload_files` can actually fetch the archive path.
  3. If a wrapper intentionally drops responses, make it surface FILE_NOT_FOUND for missing keys.

Example fix

// before (custom backend)
async def adownload_files(self, paths):
    return [r for r in self._fetch(paths) if r is not None]
// after
async def adownload_files(self, paths):
    return self._fetch(paths)  # return one response per path, with error set on failure
Defensive patterns

Strategy: type-guard

Validate before calling

responses = await backend.adownload_files([path])
if not responses:
    raise BackendMisconfigured(f"{type(backend).__name__} returned no responses for {path}")

Type guard

def has_download_response(responses: list[FileDownloadResponse] | None) -> bool:
    return bool(responses) and len(responses) > 0

Try / catch

try:
    await offload(thread_id)
except RuntimeError as e:
    if "returned no response" in str(e):
        check_backend_health(backend)  # fix the custom/stub backend
    raise

Prevention

When it happens

Trigger: A misbehaving or stubbed backend whose `adownload_files` returns `[]` for the archive path; a custom CompositeBackend child that swallows responses; a storage layer returning no rows without an error code.

Common situations: Custom archive backends (S3/DB-backed) that return empty lists on transient store errors; test fakes that forget to enqueue a response; backend wrappers that filter out failed downloads instead of reporting them as error responses.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/7a75892684396d3e. Report an issue: GitHub.