odysseus-dev/odysseus · error · HTTPException

Codex plugin bundle not found

Error message

Codex plugin bundle not found

What it means

Raised as HTTP 404 by GET /api/codex/plugin.zip when the server cannot find the integrations/codex directory relative to the routes package. The zip is built live from that directory at request time, so the error means the plugin source files simply are not present in the deployment — a packaging/install problem, not a runtime failure.

Source

Thrown at routes/codex_routes.py:223

                },
                "cookbook": {
                    "read": scoped(COOKBOOK_READ_SCOPES),
                    "launch": scoped(COOKBOOK_LAUNCH_SCOPES),
                    "actions": ["tasks", "servers", "output", "serve", "stop"],
                },
            },
            "safety": {
                "email_send_requires_confirmation": True,
                "destructive_actions_should_confirm": True,
            },
        }

    @router.get("/plugin.zip")
    def plugin_zip(request: Request):
        require_authenticated_request(request)
        root = Path(__file__).resolve().parent.parent / "integrations" / "codex"
        if not root.exists():
            raise HTTPException(404, "Codex plugin bundle not found")
        buf = BytesIO()
        with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as zf:
            for path in sorted(root.rglob("*")):
                if path.is_dir() or "__pycache__" in path.parts or path.suffix == ".pyc":
                    continue
                zf.write(path, Path("odysseus") / path.relative_to(root))
        buf.seek(0)
        headers = {"Content-Disposition": 'attachment; filename="odysseus-codex-plugin.zip"'}
        return StreamingResponse(buf, media_type="application/zip", headers=headers)

    @router.get("/todos")
    async def list_todos(request: Request, archived: bool = False, label: str | None = None):
        owner = _scope_owner(request, TODO_READ_SCOPES)
        args: dict[str, Any] = {"action": "list", "archived": archived}
        if label:
            args["label"] = label
        return await do_manage_notes(json.dumps(args), owner=owner)

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Verify integrations/codex exists next to the routes package in the deployed tree; restore it if the build step excluded it.
  2. For PyInstaller, add integrations/codex as datas in the spec file and rebuild.
  3. For Docker, ensure the COPY step includes integrations/ and it is not in .dockerignore.
  4. Update/reinstall the application so the shipped layout matches what the route expects.

Example fix

# before (Dockerfile)
COPY app.py routes/ ./

# after
COPY app.py routes/ integrations/ ./
Defensive patterns

Strategy: try-catch

Validate before calling

async function downloadPluginZip() {
  const head = await fetch('/api/codex/plugin.zip', {method: 'HEAD'});
  if (head.status === 404) {
    console.error('Plugin bundle missing from this deployment — packaging issue.');
    return null;
  }
  return fetch('/api/codex/plugin.zip');
}

Try / catch

try { zip = await getPluginZip() } catch (e) { if (e.status === 404) { showInstallHint('Server build lacks integrations/codex; reinstall server') ; return } throw }

Prevention

When it happens

Trigger: Running from a PyInstaller bundle, Docker image, or source checkout where integrations/codex was excluded from the artifact; the directory renamed or moved; running app.py from a different location so Path(__file__).parent.parent resolves elsewhere.

Common situations: One-file PyInstaller builds (Odysseus.spec) omitting data directories; .dockerignore excluding integrations/; shallow clone missing submodules; stale installed package lacking new files after an upgrade.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/98c5f3e206bb1a83. Report an issue: GitHub.