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
- Ensure the startup/lifespan code always sets app.state.pose_service/stream_service/hardware_service, even on degraded paths
- Retry the health check after startup finishes; gate traffic on readiness, not liveness
- 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
- Gate traffic on the health endpoint (readiness) rather than the port being open
- Always set app.state service attributes during startup, even on degraded paths
- Return an explicit 'disabled' status for intentionally absent services instead of None
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
- Service '{service_name}' is unhealthy: {status_info.get('err
- Service '{service_name}' health check failed
- Unknown service: {service_name}
- Health check failed: {str(e)}
- JWT authentication is not configured. In development mode, e
AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16).
Data as JSON: /api/errors/b07e87a5a7108cee.
Report an issue: GitHub.