Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save IgorGanapolsky/0ac03be61c722a2f471b2f829542195b to your computer and use it in GitHub Desktop.

Select an option

Save IgorGanapolsky/0ac03be61c722a2f471b2f829542195b to your computer and use it in GitHub Desktop.
n8n #34269 production workaround: authenticated fail-closed GCS verification
{
"name": "GCS resumable upload - fail-closed verification workaround",
"nodes": [
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [-720, 0],
"id": "e5fd6958-6a52-49fa-b4e9-fb94fa74171f",
"name": "When clicking Test workflow"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "07ae20d0-3868-4558-a354-0bc69131bd38",
"name": "bucketName",
"value": "REPLACE_WITH_BUCKET_NAME",
"type": "string"
},
{
"id": "335a05a4-2581-4401-9e8d-743361fa36b5",
"name": "objectName",
"value": "REPLACE_WITH_DETERMINISTIC_OBJECT_NAME.json",
"type": "string"
},
{
"id": "1af2c2ef-e5fb-4d49-9d5e-d5b34d36413b",
"name": "payload",
"value": "This is a safe test payload. Replace it with the upstream item.",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-496, 0],
"id": "581237b1-1e96-4c05-aadd-63e020514cff",
"name": "Config"
},
{
"parameters": {
"operation": "toJson",
"mode": "each",
"binaryPropertyName": "data",
"options": {
"fileName": "={{ $json.objectName }}",
"format": true
}
},
"type": "n8n-nodes-base.convertToFile",
"typeVersion": 1.1,
"position": [-272, 0],
"id": "41a38fb4-7d89-44b7-aa4c-b6abc9241bd3",
"name": "Convert to File"
},
{
"parameters": {
"authentication": "serviceAccount",
"resource": "object",
"operation": "create",
"bucketName": "={{ $('Config').item.json.bucketName }}",
"objectName": "={{ $('Config').item.json.objectName }}",
"updateProjection": "noAcl",
"createBinaryPropertyName": "data",
"createData": {},
"createQuery": {
"ifGenerationMatch": 0
},
"encryptionHeaders": {},
"requestOptions": {}
},
"type": "n8n-nodes-base.googleCloudStorage",
"typeVersion": 1.1,
"position": [-48, 0],
"id": "49f9cbe2-c4cb-4f78-8194-b3878798f62c",
"name": "Create Object - Continue Error",
"onError": "continueErrorOutput"
},
{
"parameters": {
"authentication": "serviceAccount",
"resource": "object",
"operation": "get",
"bucketName": "={{ $('Config').item.json.bucketName }}",
"objectName": "={{ $('Config').item.json.objectName }}",
"projection": "noAcl",
"alt": "json",
"getParameters": {},
"encryptionHeaders": {}
},
"type": "n8n-nodes-base.googleCloudStorage",
"typeVersion": 1.1,
"position": [192, 0],
"id": "6f140524-fc4f-41ed-934d-4f8e70de02c8",
"name": "Authenticated Object Get"
},
{
"parameters": {
"jsCode": "const expected = $('Config').item.json.objectName;\nconst bucket = $('Config').item.json.bucketName;\nif ($json.name !== expected) {\n throw new Error(`GCS verification failed: expected ${expected}, received ${$json.name ?? 'no object name'}`);\n}\nif (!$json.generation && !$json.etag) {\n throw new Error('GCS verification failed: metadata response has no generation or etag');\n}\nreturn [{\n json: {\n status: 'verified',\n gsUri: `gs://${bucket}/${$json.name}`,\n objectName: $json.name,\n generation: $json.generation ?? null,\n etag: $json.etag ?? null\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [432, 0],
"id": "3620db27-a8f0-40d7-b4ae-5cbd98724202",
"name": "Require Durable Artifact"
}
],
"connections": {
"When clicking Test workflow": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
},
"Config": {
"main": [
[
{
"node": "Convert to File",
"type": "main",
"index": 0
}
]
]
},
"Convert to File": {
"main": [
[
{
"node": "Create Object - Continue Error",
"type": "main",
"index": 0
}
]
]
},
"Create Object - Continue Error": {
"main": [
[
{
"node": "Authenticated Object Get",
"type": "main",
"index": 0
}
],
[
{
"node": "Authenticated Object Get",
"type": "main",
"index": 0
}
]
]
},
"Authenticated Object Get": {
"main": [
[
{
"node": "Require Durable Artifact",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"active": false,
"settings": {
"executionOrder": "v1"
},
"tags": []
}

n8n #34269 production workaround

Import n8n-34269-minimal-workaround.workflow.json, then:

  1. Select the same Google service-account credential on both GCS nodes.
  2. Replace the bucket and deterministic object name in Config.
  3. Replace the sample input with the production item and preserve a stable object name derived from the business key.
  4. Keep ifGenerationMatch: 0. A retry after the first successful upload will not overwrite a changed object; it takes the error branch instead.
  5. Keep both Create outputs connected to Authenticated Object Get.

The run is successful only when the authenticated metadata request returns the expected object name and a generation or etag. An upload failure with no object therefore fails closed at the Get node. A successful upload followed by the known anonymous metadata error recovers through the same authenticated Get and emits a durable gs:// artifact.

No credential IDs, bucket names, object names, or client data are included.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment