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

  1. If encoding is suspect, read bytes and decode with errors='replace' or detect encoding, or write everything as UTF-8 consistently
  2. Fix file ownership/permissions (chmod/chown) so the reading process can access it
  3. Drop the exists() pre-check and let the try block handle FileNotFoundError separately, removing the race
  4. 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

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.