datawhalechina/hello-agents · error · FileReadError
无法读取学习计划:{e}
Error message
无法读取学习计划:{e} What it means
FileReadError raised by FileManager.read_plan when plan.md exists but read_text fails. Typical causes: permission denied on the file, an undecodable byte sequence under strict UTF-8 decoding (UnicodeDecodeError), or the path being removed between the exists() check and the read (TOCTOU race).
Source
Thrown at Co-creation-projects/Yixiang-Wu-LearningAgent/core/file_manager.py:119
读取学习计划
Args:
domain: 领域名称
Returns:
计划内容
Raises:
FileNotFoundError: 如果计划不存在
"""
plan_path = self.BASE_DIR / domain / "plan.md"
if not plan_path.exists():
raise FileNotFoundError(f"学习计划不存在:{domain}")
try:
return plan_path.read_text(encoding="utf-8")
except Exception as e:
raise FileReadError(f"无法读取学习计划:{e}")
def domain_exists(self, domain: str) -> bool:
"""
检查领域是否存在
Args:
domain: 领域名称
Returns:
是否存在
"""
return (self.BASE_DIR / domain).exists()
def list_domains(self) -> List[str]:
"""
列出所有学习领域
Returns:View on GitHub (pinned to 606a07d341)
Solutions
- If encoding is suspect, read bytes and decode with errors='replace' or detect encoding, or write everything as UTF-8 consistently
- Fix file ownership/permissions (chmod/chown) so the reading process can access it
- Drop the exists() pre-check and let the try block handle FileNotFoundError separately, removing the race
- Catch OSError specifically and chain with `from e`
Example fix
# before
if not plan_path.exists():
raise FileNotFoundError(f"学习计划不存在:{domain}")
try:
return plan_path.read_text(encoding="utf-8")
except Exception as e:
raise FileReadError(f"无法读取学习计划:{e}")
# after
try:
return plan_path.read_text(encoding="utf-8")
except FileNotFoundError:
raise FileNotFoundError(f"学习计划不存在:{domain}") from None
except UnicodeDecodeError as e:
raise FileReadError(f"plan.md 不是有效 UTF-8: {e}") from e
except OSError as e:
raise FileReadError(f"无法读取学习计划: {e}") from e Defensive patterns
Strategy: try-catch
Validate before calling
def plan_readable(plan_path: Path) -> bool:
try:
return plan_path.is_file() and os.access(plan_path, os.R_OK)
except OSError:
return False Try / catch
try:
plan = fm.read_plan(domain)
except FileNotFoundError:
... # create default
except FileReadError as e:
if 'UnicodeDecodeError' in str(e) or 'codec' in str(e):
# re-read leniently
plan = (BASE_DIR / domain / 'plan.md').read_bytes().decode('utf-8', 'replace')
else:
raise Prevention
- Write every file with encoding='utf-8' (this code does) and never edit generated notes with legacy-encoding editors
- Separate FileNotFoundError from FileReadError in the handler — they need different recovery paths
- Remove the exists() pre-check so the read is the single source of truth (avoids TOCTOU)
When it happens
Trigger: plan.md written by another tool in GBK/Latin-1 encoding then read with encoding='utf-8'; file owned by another user with mode 600; file deleted concurrently after the exists() check; BASE_DIR on a network mount that returns I/O errors.
Common situations: Notes edited on Windows with a legacy-encoding editor; files created by root in a container then read by an unprivileged process; concurrent agent processes pruning the workspace.
Related errors
AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14).
Data as JSON: /api/errors/6200321640bb6580.
Report an issue: GitHub.