Spaces:
Sleeping
Sleeping
Claude Code Claude Opus 4.6 commited on
Commit ·
5c9db21
1
Parent(s): e5ac55b
Claude Code: Fix Cain health display - check health field first to resolve 'Error: unknown' issue
Browse files- Update read_current_stage() to check 'health' field before 'error' field
- The 'health' field is authoritative: ERROR vs HEALTHY
- Update handle_status_file_read() to clean stale 'unknown' errors on read
- This fixes the display issue where Cain showed RUNNING! Error: unknown
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- error_handlers.py +23 -4
- openclaw/.openclaw/health_monitor.py +16 -1
error_handlers.py
CHANGED
|
@@ -162,31 +162,50 @@ def handle_status_file_read() -> Dict[str, Any]:
|
|
| 162 |
"""
|
| 163 |
try:
|
| 164 |
with open(str(STATUS_FILE), "r") as f:
|
| 165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
except FileNotFoundError:
|
| 167 |
# Status file not found is not an error - return healthy default
|
| 168 |
return {
|
| 169 |
"current_state": "idle",
|
|
|
|
| 170 |
"last_updated": datetime.utcnow().isoformat() + "+00:00",
|
| 171 |
"agent": "cain",
|
| 172 |
-
"
|
|
|
|
| 173 |
"note": "Status file not found - using default"
|
| 174 |
}
|
| 175 |
except PermissionError as e:
|
| 176 |
return {
|
| 177 |
"current_state": "error",
|
|
|
|
| 178 |
"last_updated": datetime.utcnow().isoformat() + "+00:00",
|
| 179 |
"agent": "cain",
|
| 180 |
"error": f"Permission denied reading status file: {str(e)}",
|
| 181 |
-
"status_file": STATUS_FILE
|
| 182 |
}
|
| 183 |
except json.JSONDecodeError as e:
|
| 184 |
return {
|
| 185 |
"current_state": "error",
|
|
|
|
| 186 |
"last_updated": datetime.utcnow().isoformat() + "+00:00",
|
| 187 |
"agent": "cain",
|
| 188 |
"error": f"Invalid JSON in status file: {str(e)}",
|
| 189 |
-
"status_file": STATUS_FILE
|
| 190 |
}
|
| 191 |
|
| 192 |
|
|
|
|
| 162 |
"""
|
| 163 |
try:
|
| 164 |
with open(str(STATUS_FILE), "r") as f:
|
| 165 |
+
data = json.load(f)
|
| 166 |
+
|
| 167 |
+
# CRITICAL: Clean stale "unknown" error strings immediately after reading
|
| 168 |
+
# This prevents "Error: unknown" display issues
|
| 169 |
+
error = data.get('error')
|
| 170 |
+
if isinstance(error, str) and error.strip().lower() in ('unknown', 'none', 'null', ''):
|
| 171 |
+
data['error'] = None
|
| 172 |
+
data['_cleaned_at'] = 'handle_status_file_read'
|
| 173 |
+
# Write back the cleaned data
|
| 174 |
+
try:
|
| 175 |
+
with open(str(STATUS_FILE), "w") as f:
|
| 176 |
+
json.dump(data, f, indent=2)
|
| 177 |
+
except Exception:
|
| 178 |
+
pass # Don't fail if we can't write back
|
| 179 |
+
|
| 180 |
+
return data
|
| 181 |
except FileNotFoundError:
|
| 182 |
# Status file not found is not an error - return healthy default
|
| 183 |
return {
|
| 184 |
"current_state": "idle",
|
| 185 |
+
"stage": "STATUS_FILE_NOT_FOUND",
|
| 186 |
"last_updated": datetime.utcnow().isoformat() + "+00:00",
|
| 187 |
"agent": "cain",
|
| 188 |
+
"error": None,
|
| 189 |
+
"status_file": str(STATUS_FILE),
|
| 190 |
"note": "Status file not found - using default"
|
| 191 |
}
|
| 192 |
except PermissionError as e:
|
| 193 |
return {
|
| 194 |
"current_state": "error",
|
| 195 |
+
"stage": "PERMISSION_ERROR",
|
| 196 |
"last_updated": datetime.utcnow().isoformat() + "+00:00",
|
| 197 |
"agent": "cain",
|
| 198 |
"error": f"Permission denied reading status file: {str(e)}",
|
| 199 |
+
"status_file": str(STATUS_FILE)
|
| 200 |
}
|
| 201 |
except json.JSONDecodeError as e:
|
| 202 |
return {
|
| 203 |
"current_state": "error",
|
| 204 |
+
"stage": "JSON_DECODE_ERROR",
|
| 205 |
"last_updated": datetime.utcnow().isoformat() + "+00:00",
|
| 206 |
"agent": "cain",
|
| 207 |
"error": f"Invalid JSON in status file: {str(e)}",
|
| 208 |
+
"status_file": str(STATUS_FILE)
|
| 209 |
}
|
| 210 |
|
| 211 |
|
openclaw/.openclaw/health_monitor.py
CHANGED
|
@@ -941,7 +941,22 @@ def read_current_stage() -> str:
|
|
| 941 |
with open(status_file) as f:
|
| 942 |
data = json.load(f)
|
| 943 |
|
| 944 |
-
# Check
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 945 |
error = data.get('error')
|
| 946 |
stage = data.get('stage', data.get('current_state', 'UNKNOWN'))
|
| 947 |
|
|
|
|
| 941 |
with open(status_file) as f:
|
| 942 |
data = json.load(f)
|
| 943 |
|
| 944 |
+
# CRITICAL: Check 'health' field first (authoritative source added in recent fix)
|
| 945 |
+
# If health field is present and is "ERROR", treat as error regardless of error field
|
| 946 |
+
# This prevents the "Error: unknown" display issue where error=null but health=ERROR
|
| 947 |
+
health = data.get('health')
|
| 948 |
+
if health and isinstance(health, str):
|
| 949 |
+
health_normalized = health.strip().upper()
|
| 950 |
+
if health_normalized == "ERROR":
|
| 951 |
+
# Health field says ERROR - append _ERROR to stage
|
| 952 |
+
logger.debug(f"Health field is ERROR - returning {stage}_ERROR")
|
| 953 |
+
return f"{stage}_ERROR"
|
| 954 |
+
elif health_normalized == "HEALTHY":
|
| 955 |
+
# Health field explicitly says HEALTHY - ignore error field
|
| 956 |
+
logger.debug(f"Health field is HEALTHY - returning clean stage: {stage}")
|
| 957 |
+
return stage
|
| 958 |
+
|
| 959 |
+
# Fallback: Check for error field (legacy behavior)
|
| 960 |
error = data.get('error')
|
| 961 |
stage = data.get('stage', data.get('current_state', 'UNKNOWN'))
|
| 962 |
|