{"record":{"id":"416662184eda49c4","repo":"apache/beam","slug":"destination-r-must-be-gcs-path","errorCode":null,"errorMessage":"Destination %r must be GCS path.","messagePattern":"Destination %r must be GCS path\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/io/gcp/gcsfilesystem.py","lineNumber":210,"sourceCode":"    \"\"\"Recursively copy the file tree from the source to the destination\n\n    Args:\n      source_file_names: list of source file objects that needs to be copied\n      destination_file_names: list of destination of the new object\n\n    Raises:\n      ``BeamIOError``: if any of the copy operations fail\n    \"\"\"\n    err_msg = (\n        \"source_file_names and destination_file_names should \"\n        \"be equal in length\")\n    assert len(source_file_names) == len(destination_file_names), err_msg\n\n    def _copy_path(source, destination):\n      \"\"\"Recursively copy the file tree from the source to the destination\n      \"\"\"\n      if not destination.startswith(GCSFileSystem.GCS_PREFIX):\n        raise ValueError('Destination %r must be GCS path.' % destination)\n      # Use copy_tree if the path ends with / as it is a directory\n      if source.endswith('/'):\n        self._gcsIO().copytree(source, destination)\n      else:\n        self._gcsIO().copy(source, destination)\n\n    exceptions = {}\n    for source, destination in zip(source_file_names, destination_file_names):\n      try:\n        _copy_path(source, destination)\n      except Exception as e:  # pylint: disable=broad-except\n        exceptions[(source, destination)] = e\n\n    if exceptions:\n      raise BeamIOError(\"Copy operation failed\", exceptions)\n\n  def rename(self, source_file_names, destination_file_names):\n    \"\"\"Rename the files at the source list to the destination list.","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/io/gcp/gcsfilesystem.py#L192-L228","documentation":"GCSFileSystem._copy_path copies files/trees within GCS and requires the destination path to start with 'gs://'. Passing a non-GCS destination raises this ValueError, since the GCS copy API cannot write outside GCS.","triggerScenarios":"Calling FileSystems.copy([...gs source...], ['/local/dest']) or GCSFileSystem copy helpers with a local or other-scheme destination.","commonSituations":"Configuring a local output/temp destination while Beam resolves the copy through the GCS file system; mismatched source/destination schemes in custom copy code.","solutions":["Ensure every destination starts with 'gs://'.","Use matching schemes: copy local-to-local via LocalFileSystem, GCS-to-GCS via GCSFileSystem.","For local downloads, use the appropriate beam.io/gcsio download API instead of FileSystems.copy."],"exampleFix":"# before\nFileSystems.copy(src_gcs_list, ['/tmp/out'])\n# after\nFileSystems.copy(src_gcs_list, ['gs://my-bucket/out'])","handlingStrategy":"validation","validationCode":"def assert_gcs_destinations(destinations):\n    bad = [d for d in destinations if not str(d).startswith('gs://')]\n    if bad:\n        raise ValueError('Destinations must be gs:// paths: %r' % bad)\n    return destinations","typeGuard":"def all_gcs_paths(paths):\n    return all(isinstance(p, str) and p.startswith('gs://') for p in paths)","tryCatchPattern":"try:\n    FileSystems.copy(srcs, dests)\nexcept ValueError as e:\n    logging.error('Copy destination scheme invalid: %s', e)\n    raise","preventionTips":["Keep source and destination schemes consistent","Normalize destination paths to gs:// before copy calls","Use FileSystems.get_filesystem so local paths go to LocalFileSystem"],"tags":["apache-beam","gcs","path-validation","valueerror"],"backgroundTag":"invalid-url-format","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}