{"record":{"id":"af1fc9254c2163a1","repo":"apache/seatunnel","slug":"gcsfile-path-must-point-to-a-prefix-below-the-buck","errorCode":null,"errorMessage":"GcsFile path must point to a prefix below the bucket root when schema_save_mode is %s or data_save_mode is %s","messagePattern":"GcsFile path must point to a prefix below the bucket root when schema_save_mode is (.+?) or data_save_mode is (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"seatunnel-connectors-v2/connector-file/connector-file-gcs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/gcs/sink/GcsFileSinkFactory.java","lineNumber":151,"sourceCode":"            createSink(TableSinkFactoryContext context) {\n        ReadonlyConfig readonlyConfig = context.getOptions();\n        validateDestructivePath(readonlyConfig);\n        CatalogTable catalogTable = context.getCatalogTable();\n        return () -> new GcsFileSink(readonlyConfig, catalogTable);\n    }\n\n    private static void validateDestructivePath(ReadonlyConfig readonlyConfig) {\n        SchemaSaveMode schemaSaveMode = readonlyConfig.get(FileBaseSinkOptions.SCHEMA_SAVE_MODE);\n        DataSaveMode dataSaveMode = readonlyConfig.get(FileBaseSinkOptions.DATA_SAVE_MODE);\n        if (schemaSaveMode != SchemaSaveMode.RECREATE_SCHEMA\n                && dataSaveMode != DataSaveMode.DROP_DATA) {\n            return;\n        }\n\n        String filePath = readonlyConfig.get(FileBaseSinkOptions.FILE_PATH);\n        String normalizedPath = new Path(filePath).toUri().normalize().getPath();\n        if (normalizedPath == null || normalizedPath.isEmpty() || \"/\".equals(normalizedPath)) {\n            throw new IllegalArgumentException(\n                    String.format(\n                            \"GcsFile path must point to a prefix below the bucket root when \"\n                                    + \"schema_save_mode is %s or data_save_mode is %s\",\n                            schemaSaveMode, dataSaveMode));\n        }\n    }\n}\n","sourceCodeStart":133,"sourceCodeEnd":159,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-file/connector-file-gcs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/gcs/sink/GcsFileSinkFactory.java#L133-L159","documentation":"When schema_save_mode or data_save_mode is a destructive mode (e.g. DELETE_DATA / overwrite-style cleanup), GcsFileSinkFactory validates that the configured file path is a real prefix below the bucket root. A path that normalizes to empty or \"/\" would make the destructive mode wipe the whole bucket, so it throws IllegalArgumentException.","triggerScenarios":"createSink → validateDestructivePath: FILE_PATH normalizes to \"/\" or empty (e.g. file_path = \"/\" or \"gs://bucket/\" with no prefix) while a destructive save mode is selected.","commonSituations":"User set file_path = \"gs://mybucket/\" intending to write at bucket root while using DELETE_DATA save mode; templated path variable resolved to empty leaving just the bucket; copy-pasted example config with root path kept in place.","solutions":["Set file_path to a concrete prefix under the bucket, e.g. file_path = \"gs://mybucket/output/data\"","If bucket-root writes are intended, change schema_save_mode/data_save_mode to non-destructive values (e.g. CREATE_SCHEMA_WHEN_NOT_EXIST / APPEND)","Ensure any template variables used in file_path actually resolve to non-empty values"],"exampleFix":"// before\nfile_path = \"gs://mybucket/\"          // root path + destructive save mode\nschema_save_mode = \"DELETE_SCHEMA\"\n// after\nfile_path = \"gs://mybucket/output/2026-09-10\"\nschema_save_mode = \"DELETE_SCHEMA\"","handlingStrategy":"validation","validationCode":"// Java: validate destructive-mode path before job submission\nString filePath = config.get(\"file_path\");\nString normalized = new Path(filePath).toUri().normalize().getPath();\nString schemaMode = config.get(\"schema_save_mode\");\nString dataMode = config.get(\"data_save_mode\");\nboolean destructive = \"DELETE_SCHEMA\".equals(schemaMode) || \"DELETE_DATA\".equals(dataMode);\nif (destructive && (normalized == null || normalized.isEmpty() || \"/\".equals(normalized))) {\n    throw new IllegalArgumentException(\"Destructive save mode requires a prefix below bucket root\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    factory.createSink(...);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"prefix below the bucket root\")) {\n        logger.error(\"Set file_path to a concrete prefix (not bucket root) or use non-destructive save modes\");\n    }\n    throw e;\n}","preventionTips":["Always configure a non-root file_path when using DELETE_DATA/DELETE_SCHEMA modes","Verify templated path variables resolve to non-empty values","Review save_mode settings before running against production buckets"],"tags":["gcs","config","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}