{"record":{"id":"9a140569aa39b305","repo":"langchain-ai/deepagents","slug":"retention-revisions-must-be-nonnegative","errorCode":null,"errorMessage":"retention_revisions must be nonnegative","messagePattern":"retention_revisions must be nonnegative","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/hooks/transcript.py","lineNumber":173,"sourceCode":"    def __init__(\n        self,\n        root: Path,\n        *,\n        retention_revisions: int = DEFAULT_RETENTION_REVISIONS,\n    ) -> None:\n        \"\"\"Create a store rooted at `root`.\n\n        Args:\n            root: Directory that will contain per-thread transcript files.\n            retention_revisions: Maximum prior `.bak-*` revisions retained per\n                transcript after each rewrite.\n\n        Raises:\n            ValueError: If `retention_revisions` is negative.\n        \"\"\"\n        if retention_revisions < 0:\n            msg = \"retention_revisions must be nonnegative\"\n            raise ValueError(msg)\n        self.root = root.expanduser().resolve()\n        self.retention_revisions = retention_revisions\n        self._buffers: dict[tuple[str, str | None], _TranscriptBuffer] = {}\n        self._lock = threading.RLock()\n        _ensure_private_directories(self.root, self.root)\n\n    def thread_path(self, thread_id: str) -> Path:\n        \"\"\"Return the materialized path for a thread transcript.\n\n        Args:\n            thread_id: Conversation thread identifier.\n\n        Returns:\n            Absolute JSONL path for the thread.\n        \"\"\"\n        return self.root / f\"{_safe_component(thread_id)}.jsonl\"\n\n    def agent_path(self, thread_id: str, agent_id: str) -> Path:","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/hooks/transcript.py#L155-L191","documentation":"TranscriptStore keeps a configurable number of historical transcript revisions; a negative retention_revisions is nonsensical (it would mean deleting more revisions than exist / infinite pruning). __init__ validates up front and raises ValueError.","triggerScenarios":"Constructing the transcript store (TranscriptStore(root, retention_revisions=...)) with a negative integer, typically from a parsed config value like retention_revisions=-1 or a subtracting expression that underflowed.","commonSituations":"Config file with retention_revisions: -1 intended as \"unlimited\"; computing retention as some_count - kept where kept > count; copying an example default incorrectly.","solutions":["Pass retention_revisions >= 0 (0 typically meaning keep no extra historical revisions; check docs for the keep-current semantics).","If \"unlimited\" was intended, use the store's option for disabling retention instead of a negative number.","Clamp or validate config at load time before constructing the store."],"exampleFix":"// before\nstore = TranscriptStore(root, retention_revisions=-1)  # ValueError\n\n// after\nrevisions = max(0, config.get(\"retention_revisions\", 3))\nstore = TranscriptStore(root, retention_revisions=revisions)","handlingStrategy":"validation","validationCode":"revisions = config.get(\"retention_revisions\", 3)\nif not isinstance(revisions, int) or revisions < 0:\n    raise ValueError(\"retention_revisions must be a nonnegative int\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate retention settings at config load time","Use max(0, value) clamping for derived values","Treat negative values as config typos, not \"unlimited\"","Document that 0 is the minimum accepted value"],"tags":["validation","transcript","value-error"],"backgroundTag":"invalid-config-value","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}