{"record":{"id":"39a37c800a2d0bab","repo":"langchain-ai/langchain","slug":"time-at-least-must-be-in-the-past","errorCode":null,"errorMessage":"time_at_least must be in the past","messagePattern":"time_at_least must be in the past","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/indexing/base.py","lineNumber":303,"sourceCode":"                E.g., use to validate that the time in the postgres database\n                is equal to or larger than the given timestamp, if not\n                raise an error.\n                This is meant to help prevent time-drift issues since\n                time may not be monotonically increasing!\n\n        Raises:\n            ValueError: If the length of keys doesn't match the length of group\n                ids.\n            ValueError: If time_at_least is in the future.\n        \"\"\"\n        if group_ids and len(keys) != len(group_ids):\n            msg = \"Length of keys must match length of group_ids\"\n            raise ValueError(msg)\n        for index, key in enumerate(keys):\n            group_id = group_ids[index] if group_ids else None\n            if time_at_least and time_at_least > self.get_time():\n                msg = \"time_at_least must be in the past\"\n                raise ValueError(msg)\n            self.records[key] = {\"group_id\": group_id, \"updated_at\": self.get_time()}\n\n    async def aupdate(\n        self,\n        keys: Sequence[str],\n        *,\n        group_ids: Sequence[str | None] | None = None,\n        time_at_least: float | None = None,\n    ) -> None:\n        \"\"\"Async upsert records into the database.\n\n        Args:\n            keys: A list of record keys to upsert.\n            group_ids: A list of group IDs corresponding to the keys.\n\n            time_at_least: Optional timestamp. Implementation can use this\n                to optionally verify that the timestamp IS at least this time\n                in the system that stores.","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/indexing/base.py#L285-L321","documentation":"`ValueError` from `RecordManager.update`/`aupdate`: the `time_at_least` argument is a timestamp greater than the record manager's current clock (`get_time()`). The indexer uses this to guard against time-drift — if the caller believes more time has passed than the store's clock shows, updates could be ordered incorrectly, so the write is rejected.","triggerScenarios":"Calling `record_manager.update(keys, time_at_least=ts)` with `ts > record_manager.get_time()`. In practice this fires when the client machine's clock is ahead of the record manager's database clock (e.g. SQL-backed managers using the DB clock), or a caller passes milliseconds against a seconds-based clock.","commonSituations":"Clock skew between app servers and the database hosting the record manager; passing a Unix timestamp in milliseconds (`time.time()*1000`) to a seconds-based manager; timezone/DST arithmetic bugs producing future timestamps; NTP not synced in containers.","solutions":["Pass `time_at_least=None` (the indexer normally omits it) unless you specifically need drift protection.","Compare clocks before the call: `if time_at_least > record_manager.get_time(): time_at_least = None` or align with `record_manager.get_time()`.","Fix the clock skew: sync NTP on app hosts and the DB server, or verify you are using the same time unit (seconds vs ms).","If you control the manager, make `get_time()` and `time_at_least` use one source of truth (e.g. both DB time or both app time)."],"exampleFix":"# before\nrecord_manager.update(keys, time_at_least=time.time() * 1000)  # ms vs s drift\n\n# after\nrecord_manager.update(keys, time_at_least=None)\n# or\nrecord_manager.update(keys, time_at_least=record_manager.get_time())","handlingStrategy":"validation","validationCode":"if time_at_least is not None and time_at_least > record_manager.get_time():\n    logger.warning(\"time_at_least ahead of record manager clock; omitting it\")\n    time_at_least = None\nrecord_manager.update(keys, time_at_least=time_at_least)","typeGuard":"null","tryCatchPattern":"try:\n    record_manager.update(keys, time_at_least=ts)\nexcept ValueError as e:\n    if \"time_at_least must be in the past\" in str(e):\n        record_manager.update(keys)  # retry without the drift guard\n    else:\n        raise","preventionTips":["Prefer time_at_least=None unless you specifically need drift protection","Sync clocks (NTP/chrony) on app hosts and the record manager database","Use record_manager.get_time() as your timestamp source instead of time.time()"],"tags":["record-manager","time-drift","validation"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}