{"record":{"id":"04a1401c680648b1","repo":"Panniantong/Agent-Reach","slug":"mcporter-04a140","errorCode":null,"errorMessage":"mcporter 配置文件不存在","messagePattern":"mcporter 配置文件不存在","errorType":"exception","errorClass":"McporterConfigError","httpStatus":null,"severity":"error","filePath":"agent_reach/channels/mcporter.py","lineNumber":123,"sourceCode":"    if os.path.lexists(project_path):\n        layers.append((project_path, \"project\"))\n    return layers\n\n\ndef _read_config_object(config_path: Path) -> dict:\n    try:\n        raw = read_small_text_no_follow(\n            config_path,\n            max_bytes=_MAX_CONFIG_BYTES,\n        )\n    except PrivatePathError as exc:\n        raise McporterConfigError(\n            f\"mcporter 配置文件无法安全读取：{exc}\"\n        ) from exc\n    except (OSError, UnicodeError) as exc:\n        raise McporterConfigError(\"mcporter 配置文件无法安全读取\") from exc\n    if raw is None:\n        raise McporterConfigError(\"mcporter 配置文件不存在\")\n    try:\n        payload = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        raise McporterConfigError(\"mcporter 配置不是有效的 UTF-8 JSON\") from exc\n    if not isinstance(payload, dict):\n        raise McporterConfigError(\"mcporter 配置顶层必须是对象\")\n    return payload\n\n\ndef configured_server_names(output: str) -> set[str]:\n    \"\"\"Return exact configured server names from ``mcporter ... --json``.\n\n    Paths, descriptions, endpoints, and other metadata are deliberately\n    ignored: only each server object's ``name`` field is a routing signal.\n    \"\"\"\n    try:\n        payload = json.loads(output)\n    except (json.JSONDecodeError, TypeError) as exc:","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/Panniantong/Agent-Reach/blob/93ae1d18c37b707dec053c7c4f9d91cd8ef8943d/agent_reach/channels/mcporter.py#L105-L141","documentation":"Raised when read_small_text_no_follow() returns None for a config path — its contract for FileNotFoundError — yet the layer-selection step (os.path.lexists) saw the path exist. So the file vanished between selection and open, or a broken symlink existed (lexists true, open ENOENT, since O_NOFOLLOW refuses the link itself).","triggerScenarios":"Race: file deleted/moved between _select_config_layers() and the read (concurrent git checkout, editor atomic save, another doctor run). Or the path is a dangling symlink: lexists() returns True, os.open with O_NOFOLLOW hits ENOENT → returns None → this error.","commonSituations":"Broken symlink after dotfile repo cleanup; editors (and agents) doing rename-based atomic writes while doctor runs; config managed by a watcher that recreates files.","solutions":["If a broken symlink: remove it or repoint it at a real target — ls -la the path to confirm","Re-run doctor; if it was a transient atomic-save race it resolves once the writer finishes","Avoid running doctor concurrently with config-regeneration tooling"],"exampleFix":"# before: ~/.config/mcporter/mcporter.json -> /mnt/removed/share/mcporter.json (dangling)\n# after\n rm ~/.config/mcporter/mcporter.json  # layer then skipped, or restore a real file","handlingStrategy":"retry","validationCode":"import os\n\ndef layer_stable(path) -> bool:\n    return os.path.exists(path) and not os.path.islink(path) or (os.path.islink(path) and os.path.exists(os.path.realpath(path)))","typeGuard":null,"tryCatchPattern":"from agent_reach.channels.mcporter import McporterConfigError\nfor attempt in range(2):\n    try:\n        names = inspect_mcporter_config(root).names\n        break\n    except McporterConfigError as exc:\n        if \"不存在\" in str(exc) and attempt == 0:\n            continue  # transient atomic-save race; retry once\n        raise","preventionTips":["Don't run doctor while another tool rewrites mcporter configs","Remove dangling symlinks immediately after dotfile cleanup","Atomic-save writers should replace files, not unlink+create"],"tags":["mcporter","mcp","filesystem","race-condition","symlink"],"backgroundTag":null,"analyzedSha":"93ae1d18c37b707dec053c7c4f9d91cd8ef8943d","analyzedAt":"2026-08-14T22:54:06.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}