ruvnet/RuView · error · HTTPException

Service '{service_name}' not available

Error message

Service '{service_name}' not available

What it means

check_service_health raises 503 "Service '<name>' not available" when the service name is recognized but the corresponding app.state attribute (pose_service/stream_service/hardware_service) is None — the service was never attached at startup. The route exists, but the backing service object does not.

Source

Thrown at archive/v1/src/api/dependencies.py:272

    request: Request,
    service_name: str
) -> bool:
    """Check if a service is healthy."""
    try:
        if service_name == "pose":
            service = getattr(request.app.state, 'pose_service', None)
        elif service_name == "stream":
            service = getattr(request.app.state, 'stream_service', None)
        elif service_name == "hardware":
            service = getattr(request.app.state, 'hardware_service', None)
        else:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Unknown service: {service_name}"
            )
        
        if not service:
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail=f"Service '{service_name}' not available"
            )
        
        # Check service health
        status_info = await service.get_status()
        if status_info.get("status") != "healthy":
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail=f"Service '{service_name}' is unhealthy: {status_info.get('error', 'Unknown error')}"
            )
        
        return True
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Error checking service health for {service_name}: {e}")

View on GitHub (pinned to 4685618388)

Solutions

  1. Ensure the startup/lifespan code always sets app.state.pose_service/stream_service/hardware_service, even on degraded paths
  2. Retry the health check after startup finishes; gate traffic on readiness, not liveness
  3. If a service is intentionally disabled, return an explicit 'disabled' status instead of leaving the attribute None
Defensive patterns

Strategy: retry

Validate before calling

# After startup, confirm the service is attached before relying on it
async def service_ready(app, attr: str, timeout: float = 10.0) -> bool:
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        if getattr(app.state, attr, None) is not None:
            return True
        await asyncio.sleep(0.2)
    return False

Try / catch

for attempt in range(5):
    r = client.get('/api/health/pose')
    if r.status_code == 200:
        break
    if r.status_code == 503 and 'not available' in r.json().get('detail', ''):
        await asyncio.sleep(2 ** attempt)  # service not initialized yet: back off and retry
        continue
    r.raise_for_status()

Prevention

When it happens

Trigger: A health-check request racing app startup before the lifespan handler attached services; service initialization failed or was skipped (e.g. no hardware present) leaving the attribute None; a partially initialized app where only some services were constructed.

Common situations: Kubernetes readiness probes hitting the port before startup completes; optional services disabled by config while their routes stay registered; swallowed startup exceptions leaving state attributes unset.

Related errors


AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16). Data as JSON: /api/errors/b07e87a5a7108cee. Report an issue: GitHub.