{"record":{"id":"29c8ac012493a42f","repo":"Panniantong/Agent-Reach","slug":"error-29c8ac","errorCode":null,"errorMessage":"当前配置是只读的，不能保存","messagePattern":"当前配置是只读的，不能保存","errorType":"exception","errorClass":"ConfigReadOnlyError","httpStatus":null,"severity":"error","filePath":"agent_reach/config.py","lineNumber":155,"sourceCode":"            payload = read_small_text_no_follow(\n                self.config_path,\n                max_bytes=_MAX_CONFIG_BYTES,\n            )\n        except PrivatePathError as exc:\n            raise ConfigSecurityError(str(exc)) from exc\n        if payload is None:\n            self.data = {}\n            return\n\n        loaded = yaml.safe_load(payload) or {}\n        if not isinstance(loaded, dict):\n            raise ConfigError(\"配置文件顶层必须是对象\")\n        self.data = loaded\n\n    def save(self):\n        \"\"\"Save config atomically, refusing mutation in read-only mode.\"\"\"\n        if self.read_only:\n            raise ConfigReadOnlyError(\"当前配置是只读的，不能保存\")\n        self._ensure_dir()\n        _atomic_write_yaml(self.config_path, self.data)\n\n    def get(self, key: str, default: Any = None) -> Any:\n        \"\"\"Get a config value. Also checks environment variables (uppercase).\"\"\"\n        # Config file first\n        if key in self.data:\n            return self.data[key]\n        # Then env var (uppercase)\n        env_val = os.environ.get(key.upper())\n        if env_val:\n            return env_val\n        return default\n\n    def set(self, key: str, value: Any):\n        \"\"\"Set a config value and save.\"\"\"\n        if self.read_only:\n            raise ConfigReadOnlyError(\"当前配置是只读的，不能修改\")","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/Panniantong/Agent-Reach/blob/93ae1d18c37b707dec053c7c4f9d91cd8ef8943d/agent_reach/config.py#L137-L173","documentation":"Config supports an explicit read-only mode (Config(read_only=True); the CLI uses it for --dry-run and safe mode). Calling save() on such an instance raises ConfigReadOnlyError('当前配置是只读的，不能保存') ('the current config is read-only and cannot be saved') before touching the filesystem. This is a deliberate guard so inspection flows can never mutate credentials.","triggerScenarios":"Library code that holds a Config(read_only=True) instance (e.g. during doctor/dry-run) and then calls set(...) + save(); mixing a read-only Config into a code path that assumes write access; writing a helper that receives a Config without knowing its mode.","commonSituations":"Integrations reusing the CLI's read-only Config object for convenience; new contributors adding write features without checking the flag; dry-run code paths accidentally reaching a save() call.","solutions":["Create a writable instance when you intend to persist: config = Config() (read_only defaults to False)","Check the mode before saving: if config.read_only: re-open writable or skip","In CLI flows, never route mutations through the dry-run/safe-mode Config instance","Catch ConfigReadOnlyError specifically (it subclasses ConfigError) to report 'skipped write in read-only mode' instead of crashing"],"exampleFix":"# before\ncfg = Config(read_only=True)   # e.g. reused from a dry-run path\ncfg.set('github_token', tok)\ncfg.save()                     # raises ConfigReadOnlyError\n\n# after\ncfg = Config()                 # writable when you need to save\ncfg.set('github_token', tok)\ncfg.save()","handlingStrategy":"type-guard","validationCode":"from agent_reach.config import Config\n\ncfg = Config()\nif cfg.read_only:\n    cfg = Config()  # re-open writable before planning any save()","typeGuard":"from agent_reach.config import Config\n\ndef is_writable_config(cfg: Config) -> bool:\n    \"\"\"Type/mode guard: True only when save() is permitted.\"\"\"\n    return isinstance(cfg, Config) and not getattr(cfg, \"read_only\", False)","tryCatchPattern":"from agent_reach.config import ConfigReadOnlyError\n\ntry:\n    cfg.set(key, value)\n    cfg.save()\nexcept ConfigReadOnlyError:\n    log.info(\"skipped persisting %s: config opened read-only\", key)","preventionTips":["Never share one Config instance between inspection (read_only=True) and mutation flows","Check `cfg.read_only` before calling set/save in library code that receives a Config","Reserve read-only mode for doctor/dry-run; create a fresh writable Config for configure paths"],"tags":["config","read-only","save","state-error","python"],"backgroundTag":null,"analyzedSha":"93ae1d18c37b707dec053c7c4f9d91cd8ef8943d","analyzedAt":"2026-08-14T22:54:06.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}