HKUDS/nanobot · error · TuiUnavailableError

TUI release manifest is incomplete

Error message

TUI release manifest is incomplete

What it means

In _release_manifest, after parsing all valid lines the set of checksummed names does not equal the expected bundle names — the manifest is missing entries (or contains none). A complete manifest is required before any bundle file is trusted; the fault is an incomplete MANIFEST.sha256 in the release.

Solutions

  1. Re-publish the release with a manifest covering every bundle file (binary, sidecar files, manifest excluded)
  2. Compare the manifest against the archive contents to find the missing entry
  3. Retry the download in case the manifest was truncated
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at nanobot/cli/tui_launcher.py:325 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of HKUDS/nanobot@42f37dc4c0 (2026-08-26). Data as JSON: /api/errors/1abe197ddbb3d77b. Report an issue: GitHub.

Appendix: source

Thrown at nanobot/cli/tui_launcher.py:325

    try:
        lines = raw.decode("utf-8").splitlines()
    except UnicodeDecodeError as exc:
        raise TuiUnavailableError("TUI release manifest is not valid UTF-8") from exc
    checksums: dict[str, str] = {}
    for line in lines:
        digest, separator, name = line.partition("  ")
        digest = digest.lower()
        if (
            separator != "  "
            or name not in expected_names
            or name in checksums
            or len(digest) != 64
            or any(character not in "0123456789abcdef" for character in digest)
        ):
            raise TuiUnavailableError("TUI release manifest is malformed")
        checksums[name] = digest
    if set(checksums) != expected_names:
        raise TuiUnavailableError("TUI release manifest is incomplete")
    return checksums


def _verified_release_archive(raw: bytes, asset: str) -> dict[str, bytes]:
    expected_names = set(_release_bundle_names(asset))
    files: dict[str, bytes] = {}
    try:
        with zipfile.ZipFile(io.BytesIO(raw)) as archive:
            entries = archive.infolist()
            names = [entry.filename for entry in entries if not entry.is_dir()]
            if len(names) != len(entries) or len(names) != len(set(names)):
                raise TuiUnavailableError("TUI release archive contains invalid entries")
            if set(names) != expected_names:
                raise TuiUnavailableError("TUI release archive is incomplete")
            for entry in entries:
                limit = 150 * 1024 * 1024 if entry.filename == asset else _TUI_RELEASE_LIMITS[
                    entry.filename
                ]

View on GitHub (pinned to 42f37dc4c0)