{ "name": "Record n8n failures in Linear with duplicate checks", "active": false, "nodes": [ { "id": "workflow-error", "name": "Workflow error", "type": "n8n-nodes-base.errorTrigger", "typeVersion": 1, "position": [ 0, 0 ], "parameters": {} }, { "id": "recovery-settings", "name": "Recovery settings", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 220, 0 ], "parameters": { "jsCode": "return [{json:{event:$json,settings:{teamId:'YOUR_LINEAR_TEAM_UUID',instanceKey:'YOUR_INSTANCE_KEY',n8nBaseUrl:'https://n8n.example.com',createIssue:false}}}];" } }, { "id": "build-safe-failure-reference", "name": "Build safe failure reference", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 440, 0 ], "parameters": { "jsCode": "const {event,settings}= $json;\nconst validId = v => typeof v==='string' && /^[A-Za-z0-9_-]{1,80}$/.test(v);\nif(!settings || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(settings.teamId)) throw new Error('Set a valid Linear team UUID');\nif(!validId(settings.instanceKey)||settings.instanceKey==='YOUR_INSTANCE_KEY') throw new Error('Set a stable nonsecret instanceKey');\nif(typeof settings.createIssue!=='boolean') throw new Error('createIssue must be a boolean');\nconst origin=typeof settings.n8nBaseUrl==='string'?settings.n8nBaseUrl.replace(/\\/$/,''):'';\nif(!/^https:\\/\\/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}(?::[0-9]{1,5})?$/.test(origin)||origin==='https://n8n.example.com') throw new Error('Set an HTTPS origin for your n8n instance');\nconst workflowId=event?.workflow?.id;\nif(!validId(workflowId)) throw new Error('Missing workflow ID');\nconst executionId=event?.execution?.retryOf||event?.execution?.id;\nconst triggerTime=event?.trigger?.error?.timestamp;\nlet failureKey,executionUrl=null;\nif(validId(executionId)) {failureKey='execution-'+executionId;executionUrl=origin+'/execution/'+encodeURIComponent(executionId);}\nelse if(Number.isSafeInteger(triggerTime)&&triggerTime>0) failureKey='trigger-'+triggerTime;\nelse throw new Error('No stable execution ID or trigger error timestamp; inspect source before retry');\nconst marker='[n8n-failure:'+settings.instanceKey+':'+workflowId+':'+failureKey+']';\nconst title='Automation failure for workflow '+workflowId;\nconst description=marker+'\\n\\nA workflow failed. Inspect its saved execution or trigger history in n8n.\\n\\nWorkflow: '+origin+'/workflow/'+encodeURIComponent(workflowId)+(executionUrl?'\\nExecution: '+executionUrl:'\\nTrigger failure timestamp: '+triggerTime)+'\\n\\nRaw error messages, input data, stack traces and customer content are intentionally omitted.';\nconst query='query($filter:IssueFilter!){issues(first:2,includeArchived:true,filter:$filter){nodes{id title description url team{id}} pageInfo{hasNextPage}}}';\nreturn [{json:{settings,marker,title,description,queryBody:{query,variables:{filter:{team:{id:{eq:settings.teamId}},description:{contains:marker}}}}}}];" } }, { "id": "find-previous-recovery-issue", "name": "Find previous recovery issue", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 660, 0 ], "parameters": { "method": "POST", "url": "https://api.linear.app/graphql", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ $json.queryBody }}", "options": { "timeout": 30000 } } }, { "id": "check-search-and-choose-action", "name": "Check search and choose action", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 880, 0 ], "parameters": { "jsCode": "const p=$('Build safe failure reference').first().json;\nconst r=$json;\nif(r.errors?.length || !r.data?.issues || !Array.isArray(r.data.issues.nodes)||typeof r.data.issues.pageInfo?.hasNextPage!=='boolean') throw new Error('Linear search incomplete or failed');\nconst rows=r.data.issues.nodes;\nif(r.data.issues.pageInfo.hasNextPage || rows.length>1) throw new Error('Multiple recovery matches; reconcile manually');\nif(rows.length && (rows[0].team?.id!==p.settings.teamId || !rows[0].description?.includes(p.marker))) throw new Error('Recovery issue identity mismatch');\nif(rows.length) return [{json:{create:false,status:'existing_issue',issueId:rows[0].id,url:rows[0].url}}];\nif(!p.settings.createIssue) return [{json:{create:false,status:'preview',title:p.title,description:p.description}}];\nreturn [{json:{create:true,createBody:{query:'mutation($input:IssueCreateInput!){issueCreate(input:$input){success issue{id}}}',variables:{input:{teamId:p.settings.teamId,title:p.title,description:p.description}}}}}];" } }, { "id": "create-enabled-and-needed", "name": "Create enabled and needed", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 1100, 0 ], "parameters": { "conditions": { "options": { "caseSensitive": true, "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "condition", "leftValue": "={{ $json.create }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} } }, { "id": "create-recovery-issue", "name": "Create recovery issue", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 1320, 0 ], "parameters": { "method": "POST", "url": "https://api.linear.app/graphql", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ $json.createBody }}", "options": { "timeout": 30000 } } }, { "id": "validate-creation-response", "name": "Validate creation response", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1540, 0 ], "parameters": { "jsCode": "if($json.errors?.length||$json.data?.issueCreate?.success!==true||typeof $json.data.issueCreate.issue?.id!=='string') throw new Error('Creation is unconfirmed. Search Linear before retrying');\nreturn [{json:{issueId:$json.data.issueCreate.issue.id,queryBody:{query:'query($id:String!){issue(id:$id){id title description url team{id}}}',variables:{id:$json.data.issueCreate.issue.id}}}}];" } }, { "id": "read-created-issue", "name": "Read created issue", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 1760, 0 ], "parameters": { "method": "POST", "url": "https://api.linear.app/graphql", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ $json.queryBody }}", "options": { "timeout": 30000 } } }, { "id": "verify-recovery-receipt", "name": "Verify recovery receipt", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1980, 0 ], "parameters": { "jsCode": "const p=$('Build safe failure reference').first().json, expected=$('Validate creation response').first().json.issueId, issue=$json.data?.issue;\nif($json.errors?.length||issue?.id!==expected||issue?.team?.id!==p.settings.teamId||issue?.title!==p.title||issue?.description!==p.description||typeof issue?.url!=='string') throw new Error('Issue creation readback is unconfirmed. Inspect Linear before retrying');\nreturn [{json:{status:'created_and_verified',issueId:issue.id,url:issue.url}}];" } }, { "id": "setup-and-privacy", "name": "Setup and privacy", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 0, -420 ], "parameters": { "content": "## Send n8n failures to Linear\n\nConfigure the team UUID, stable instance key and HTTPS n8n origin. Bind an HTTP Header Auth credential named Authorization to every HTTP Request node. Leave createIssue false for preview.\n\nSelect this workflow in the error settings of workflows you own. Test with a controlled automatic failure. Raw errors and payloads are never sent to Linear.\n\nMatching a previous issue prevents sequential duplicates. Serialize executions; simultaneous searches and creates are not atomic. After a timeout, search Linear before retrying. See the companion guide.", "width": 1060, "height": 340 } } ], "connections": { "Workflow error": { "main": [ [ { "node": "Recovery settings", "type": "main", "index": 0 } ] ] }, "Recovery settings": { "main": [ [ { "node": "Build safe failure reference", "type": "main", "index": 0 } ] ] }, "Build safe failure reference": { "main": [ [ { "node": "Find previous recovery issue", "type": "main", "index": 0 } ] ] }, "Find previous recovery issue": { "main": [ [ { "node": "Check search and choose action", "type": "main", "index": 0 } ] ] }, "Check search and choose action": { "main": [ [ { "node": "Create enabled and needed", "type": "main", "index": 0 } ] ] }, "Create enabled and needed": { "main": [ [ { "node": "Create recovery issue", "type": "main", "index": 0 } ] ] }, "Create recovery issue": { "main": [ [ { "node": "Validate creation response", "type": "main", "index": 0 } ] ] }, "Validate creation response": { "main": [ [ { "node": "Read created issue", "type": "main", "index": 0 } ] ] }, "Read created issue": { "main": [ [ { "node": "Verify recovery receipt", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "saveDataErrorExecution": "none", "saveDataSuccessExecution": "none", "saveManualExecutions": false } }