Claude Code Claude Opus 4.6 commited on
Commit
bfaf859
·
1 Parent(s): 9174d25

Claude Code: Fix stale 'unknown' error strings - convert to null

Browse files

- Add housekeeping function to convert 'unknown' string errors to null
- Fix read_current_stage to auto-repair status files with 'unknown' error
- Ensure healthy agents always have error=null, not error='unknown'

This fixes the "Error: unknown" display issue where the error field
was being set to the string 'unknown' instead of null.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

openclaw/.openclaw/health_monitor.py CHANGED
@@ -802,34 +802,70 @@ def reset_health_monitor():
802
  _global_monitor = None
803
 
804
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
  def _find_status_file() -> Path:
806
  """Dynamically locate cain_status.json using multiple fallback strategies."""
807
  # Strategy 1: CAIN_STATUS_PATH (set by app.py, highest priority)
808
  env_path = os.environ.get("CAIN_STATUS_PATH")
809
  if env_path:
810
- return Path(env_path)
 
 
811
 
812
  # Strategy 2: CAIN_STATUS_FILE (legacy, for compatibility)
813
  env_path = os.environ.get("CAIN_STATUS_FILE")
814
  if env_path:
815
- return Path(env_path)
 
 
816
 
817
  # Strategy 3: OPENCLAW_DATA_DIR + cain_status.json
818
  data_dir = os.environ.get("OPENCLAW_DATA_DIR")
819
  if data_dir:
820
  data_path = Path(data_dir) / "cain_status.json"
821
  if data_path.exists():
 
822
  return data_path
823
 
824
  # Strategy 4: /data/cain_status.json (Docker volume mount)
825
  data_path = Path("/data/cain_status.json")
826
  if data_path.exists():
 
827
  return data_path
828
 
829
  # Strategy 5: Relative to health_monitor.py (in .openclaw/ directory)
830
  base_dir = Path(__file__).parent.resolve()
831
  relative_path = base_dir / "agents" / "cain_status.json"
832
  if relative_path.exists():
 
833
  return relative_path
834
 
835
  # Strategy 6: From /app root (Docker) or workspace root (local)
@@ -838,6 +874,7 @@ def _find_status_file() -> Path:
838
  workspace_root = workspace_root.parent
839
  root_path = workspace_root / "openclaw" / ".openclaw" / "agents" / "cain_status.json"
840
  if root_path.exists():
 
841
  return root_path
842
 
843
  # Strategy 7: Fall back to /data/cain_status.json (default for Docker)
@@ -858,12 +895,20 @@ def read_current_stage() -> str:
858
  error = data.get('error')
859
  stage = data.get('stage', data.get('current_state', 'UNKNOWN'))
860
 
861
- # If error is None, null, empty string, or "unknown", the agent is healthy
862
- # Only append error info if there's an actual error message
863
- # Treat "unknown" error as healthy (it means no specific error occurred)
864
- if error and isinstance(error, str) and error.strip() and error.strip().lower() != "unknown":
865
  return f"{stage}_ERROR"
866
 
 
 
 
 
 
 
 
 
 
867
  return stage
868
  except Exception as e:
869
  # Log the actual error for debugging
 
802
  _global_monitor = None
803
 
804
 
805
+ def _fix_stale_error_field(status_file: Path) -> bool:
806
+ """Fix stale 'unknown' error string values - convert to null."""
807
+ try:
808
+ if not status_file.exists():
809
+ return False
810
+
811
+ with open(status_file, "r") as f:
812
+ data = json.load(f)
813
+
814
+ # Check if error field is the string "unknown" (not null)
815
+ error = data.get("error")
816
+ needs_fix = (
817
+ isinstance(error, str) and
818
+ error.strip().lower() == "unknown"
819
+ )
820
+
821
+ if needs_fix:
822
+ # Fix: set error to null
823
+ data["error"] = None
824
+ data["_fixed"] = "converted 'unknown' string to null"
825
+ with open(status_file, "w") as f:
826
+ json.dump(data, f, indent=2)
827
+ return True
828
+
829
+ return False
830
+ except Exception:
831
+ return False
832
+
833
+
834
  def _find_status_file() -> Path:
835
  """Dynamically locate cain_status.json using multiple fallback strategies."""
836
  # Strategy 1: CAIN_STATUS_PATH (set by app.py, highest priority)
837
  env_path = os.environ.get("CAIN_STATUS_PATH")
838
  if env_path:
839
+ status_path = Path(env_path)
840
+ _fix_stale_error_field(status_path)
841
+ return status_path
842
 
843
  # Strategy 2: CAIN_STATUS_FILE (legacy, for compatibility)
844
  env_path = os.environ.get("CAIN_STATUS_FILE")
845
  if env_path:
846
+ status_path = Path(env_path)
847
+ _fix_stale_error_field(status_path)
848
+ return status_path
849
 
850
  # Strategy 3: OPENCLAW_DATA_DIR + cain_status.json
851
  data_dir = os.environ.get("OPENCLAW_DATA_DIR")
852
  if data_dir:
853
  data_path = Path(data_dir) / "cain_status.json"
854
  if data_path.exists():
855
+ _fix_stale_error_field(data_path)
856
  return data_path
857
 
858
  # Strategy 4: /data/cain_status.json (Docker volume mount)
859
  data_path = Path("/data/cain_status.json")
860
  if data_path.exists():
861
+ _fix_stale_error_field(data_path)
862
  return data_path
863
 
864
  # Strategy 5: Relative to health_monitor.py (in .openclaw/ directory)
865
  base_dir = Path(__file__).parent.resolve()
866
  relative_path = base_dir / "agents" / "cain_status.json"
867
  if relative_path.exists():
868
+ _fix_stale_error_field(relative_path)
869
  return relative_path
870
 
871
  # Strategy 6: From /app root (Docker) or workspace root (local)
 
874
  workspace_root = workspace_root.parent
875
  root_path = workspace_root / "openclaw" / ".openclaw" / "agents" / "cain_status.json"
876
  if root_path.exists():
877
+ _fix_stale_error_field(root_path)
878
  return root_path
879
 
880
  # Strategy 7: Fall back to /data/cain_status.json (default for Docker)
 
895
  error = data.get('error')
896
  stage = data.get('stage', data.get('current_state', 'UNKNOWN'))
897
 
898
+ # CRITICAL: Treat null, None, empty string, or "unknown" string as HEALTHY
899
+ # Only append _ERROR if there's an actual error message (non-empty, not "unknown")
900
+ if error and isinstance(error, str) and error.strip() and error.strip().lower() not in ("", "unknown", "none"):
 
901
  return f"{stage}_ERROR"
902
 
903
+ # Healthy state - fix status file if error is "unknown" string instead of null
904
+ if isinstance(error, str) and error.strip().lower() == "unknown":
905
+ try:
906
+ data["error"] = None
907
+ with open(status_file, "w") as f:
908
+ json.dump(data, f, indent=2)
909
+ except Exception:
910
+ pass # Best effort fix
911
+
912
  return stage
913
  except Exception as e:
914
  # Log the actual error for debugging