{"record":{"id":"8d08a940f2776b8c","repo":"apache/beam","slug":"matchcontinuously-interval-must-be-positive","errorCode":null,"errorMessage":"MatchContinuously interval must be positive.","messagePattern":"MatchContinuously interval must be positive\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/io/fileio.py","lineNumber":449,"sourceCode":"      if not has_deduplication:\n        raise ValueError(\n            'MatchContinuously(timestamp_cursor=True) deduplicates, so it '\n            'requires has_deduplication=True.')\n      if not match_updated_files:\n        _LOGGER.warning(\n            'MatchContinuously(timestamp_cursor=True) implies '\n            'match_updated_files=True.')\n        self.match_upd = True\n    else:\n      _LOGGER.warning(\n          'Matching Continuously is stateful, and can scale poorly. '\n          'Consider using Pub/Sub Notifications '\n          '(https://cloud.google.com/storage/docs/pubsub-notifications) '\n          'if possible')\n\n  def expand(self, pbegin) -> beam.PCollection[filesystem.FileMetadata]:\n    if Duration.of(self.interval).micros <= 0:\n      raise ValueError('MatchContinuously interval must be positive.')\n    if self.has_deduplication:\n      match_files = self._match_deduplicated(pbegin)\n    else:\n      match_files = self._match_all_each_poll(pbegin)\n\n    # Apply windowing last because dedup relies on the global window.\n    if self.apply_windowing:\n      match_files = match_files | beam.WindowInto(FixedWindows(self.interval))\n\n    return match_files\n\n  def _match_deduplicated(self,\n                          pbegin) -> beam.PCollection[filesystem.FileMetadata]:\n    # Watch emits each file once per dedup key: the path, joined by the mtime\n    # when matching updated files. stop_timestamp bounds the polls to\n    # [start, stop).\n    clock = _PollClock()\n    if self.stop_ts == MAX_TIMESTAMP:","sourceCodeStart":431,"sourceCodeEnd":467,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/io/fileio.py#L431-L467","documentation":"MatchContinuously polls a filesystem at a fixed interval; expand() validates this interval at pipeline construction time. A zero or negative interval would make the periodic match impulse invalid or produce no ticks, so Beam raises ValueError before running the pipeline.","triggerScenarios":"Calling MatchContinuously(..., interval=0), a negative timedelta/Duration, or expand() on a transform built with such an interval.","commonSituations":"Computing an interval dynamically (e.g. from config or a division that yields 0) and passing it unvalidated; mistaking interval units (seconds vs microseconds) resulting in 0.","solutions":["Pass a positive interval, e.g. interval=30 (seconds) or a positive datetime.timedelta.","Validate/normalize the interval value before constructing the transform, clamping to a positive minimum.","Check units: Beam Durations are in seconds/Duration objects; ensure the computation isn't truncating to 0."],"exampleFix":"# before\nMatchContinuously('/data/*', interval=interval_from_config)  # 0\n# after\ninterval = max(interval_from_config, 1)\nMatchContinuously('/data/*', interval=interval)","handlingStrategy":"validation","validationCode":"from apache_beam.utils.windowed_value import Duration\nassert Duration.of(interval).micros > 0, 'interval must be positive'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Clamp dynamic interval values to a positive minimum.","Prefer explicit datetime.timedelta(seconds=...) literals over computed integers."],"tags":["python","apache-beam","io","validation"],"backgroundTag":"value-out-of-range","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}