The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Claude Status Checker (Real ccusage)",
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
240,
300
]
},
{
"parameters": {
"command": "timeout 10s claude status || echo 'TIMEOUT_OR_ERROR'"
},
"id": "claude-status-check",
"name": "Check Claude Status",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
460,
300
]
},
{
"parameters": {
"jsCode": "// Process Claude status output and determine if it's working or out of tokens\nconst statusOutput = $json;\n\n// Check if command timed out or failed\nif (statusOutput.exitCode !== 0 || \n statusOutput.stdout.includes('TIMEOUT_OR_ERROR') || \n statusOutput.stderr.includes('timeout') ||\n !statusOutput.stdout.trim()) {\n \n return [{\n json: {\n claudeStatus: 'unavailable',\n needsTokenCheck: true,\n message: 'Claude is not responding - likely out of tokens'\n }\n }];\n}\n\n// Check for common Claude error patterns that indicate token issues\nconst tokenErrorPatterns = [\n 'rate limit',\n 'quota exceeded',\n 'no tokens',\n 'insufficient tokens',\n 'credit limit',\n 'usage limit'\n];\n\nconst statusText = statusOutput.stdout.toLowerCase();\nconst hasTokenError = tokenErrorPatterns.some(pattern => statusText.includes(pattern));\n\nif (hasTokenError) {\n return [{\n json: {\n claudeStatus: 'token_exhausted',\n needsTokenCheck: true,\n message: 'Claude reports token/quota issues'\n }\n }];\n}\n\n// Claude is responding normally\nreturn [{\n json: {\n claudeStatus: 'available',\n needsTokenCheck: false,\n message: 'Claude is available and responding',\n statusDetails: statusOutput.stdout\n }\n}];"
},
"id": "process-status",
"name": "Process Status Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
680,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "condition1",
"leftValue": "={{ $json.needsTokenCheck }}",
"rightValue": null,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "check-if-tokens-needed",
"name": "Check If Token Info Needed",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
900,
300
]
},
{
"parameters": {
"command": "CLAUDE_CONFIG_DIR=/home/node/.claude ccusage blocks"
},
"id": "check-usage",
"name": "Check Claude Usage",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1120,
200
]
},
{
"parameters": {
"jsCode": "// Parse real ccusage blocks output to get exact block timing\nconst usageOutput = $('Check Claude Usage').item.json;\nconst statusInfo = $('Process Status Response').item.json;\n\nif (usageOutput.exitCode !== 0) {\n const errorResponse = {\n status: 'error',\n claudeStatus: statusInfo.claudeStatus,\n message: '\ud83d\udea8 **Claude Status Check Failed**\\n\\nClaude is not responding and I couldn\\'t check usage information.\\n\\nPlease wait a few minutes and try again.',\n recommendation: 'Wait 5-10 minutes before retrying',\n timestamp: new Date().toISOString()\n };\n \n return [{ json: errorResponse }];\n}\n\nlet usageText = usageOutput.stdout;\nconst now = new Date();\n\n// Parse ccusage blocks output to find the current ACTIVE block\nfunction parseActiveBlock(output) {\n const lines = output.split('\\n');\n let activeBlockInfo = null;\n let remainingInfo = null;\n \n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n \n // Look for ACTIVE block line - format is split across two lines\n if (line.includes('ACTIVE')) {\n // Extract block start time from current line\n const blockStartMatch = line.match(/([0-9]+\\/[0-9]+\\/[0-9]+,\\s+[0-9]+:[0-9]+:[0-9]+\\s+[AP]M)/);\n const usageMatch = line.match(/([0-9]+\\.[0-9]+)%/);\n \n // Look for remaining time in the next line\n let remainingTimeStr = null;\n if (i + 1 < lines.length) {\n const nextLine = lines[i + 1];\n const remainingMatch = nextLine.match(/([0-9]+m)\\s+remaining/);\n if (remainingMatch) {\n const fullRemainingMatch = nextLine.match(/([0-9]+h\\s+[0-9]+m)\\s+remaining/);\n remainingTimeStr = fullRemainingMatch ? fullRemainingMatch[1] : remainingMatch[1];\n }\n }\n \n if (blockStartMatch && remainingTimeStr) {\n const blockStartStr = blockStartMatch[1];\n const blockStartTime = new Date(blockStartStr);\n \n // Calculate next block start (5 hours after current block start)\n const nextBlockStart = new Date(blockStartTime.getTime() + 5 * 60 * 60 * 1000);\n \n // Parse remaining time\n const timeParseMatch = remainingTimeStr.match(/(?:([0-9]+)h\\s+)?([0-9]+)m/);\n if (timeParseMatch) {\n const hoursRemaining = timeParseMatch[1] ? parseInt(timeParseMatch[1]) : 0;\n const minutesRemaining = parseInt(timeParseMatch[2]);\n const totalMinutesRemaining = hoursRemaining * 60 + minutesRemaining;\n \n activeBlockInfo = {\n blockStart: blockStartStr,\n blockStartTime: blockStartTime,\n remainingTime: remainingTimeStr,\n nextBlockStart: nextBlockStart,\n hoursRemaining: hoursRemaining,\n minutesRemaining: minutesRemaining,\n totalMinutesRemaining: totalMinutesRemaining,\n usagePercent: usageMatch ? parseFloat(usageMatch[1]) : null\n };\n }\n }\n }\n \n // Look for REMAINING tokens info\n if (line.includes('REMAINING')) {\n const tokensMatch = line.match(/([0-9,]+)/);\n const percentMatch = line.match(/([0-9]+\\.[0-9]+)%/);\n if (tokensMatch && percentMatch) {\n remainingInfo = {\n remainingTokens: tokensMatch[1],\n remainingPercent: parseFloat(percentMatch[1])\n };\n }\n }\n }\n \n return { activeBlockInfo, remainingInfo };\n}\n\n// Parse the block information\nconst { activeBlockInfo, remainingInfo } = parseActiveBlock(usageText);\n\nif (!activeBlockInfo) {\n // Fallback if no active block found\n const errorResponse = {\n status: 'error',\n claudeStatus: statusInfo.claudeStatus,\n message: '\ud83d\udea8 **No Active Block Found**\\n\\nCannot determine current Claude block status. This might mean:\\n\\n\u2022 No recent Claude usage found\\n\u2022 Claude session may have expired\\n\u2022 Try using Claude once to initialize a session\\n\\n**Raw output:**\\n```\\n' + usageText.substring(0, 500) + '\\n```',\n recommendation: 'Use Claude to start a new session, then check again',\n timestamp: now.toISOString()\n };\n \n return [{ json: errorResponse }];\n}\n\n// Format wait time\nlet waitTimeText;\nif (activeBlockInfo.hoursRemaining > 0) {\n waitTimeText = `${activeBlockInfo.hoursRemaining}h ${activeBlockInfo.minutesRemaining}m`;\n} else {\n waitTimeText = `${activeBlockInfo.minutesRemaining}m`;\n}\n\nconst nextBlockFormatted = activeBlockInfo.nextBlockStart.toLocaleString();\n\n// Extract key usage info from the blocks output\nlet recentUsageInfo = '';\nconst lines = usageText.split('\\n');\nfor (const line of lines) {\n if (line.includes('\u2502') && (line.includes('ACTIVE') || line.includes('sonnet-4') || line.includes('opus-4') || line.includes('%'))) {\n recentUsageInfo += line + '\\n';\n }\n}\n\n// Build comprehensive response message\nlet message = `\u23f3 **Claude Token Block Status** (Live Data)\\n\\n`;\nmessage += `**Current Status:** ${statusInfo.message}\\n\\n`;\nmessage += `**\ud83d\udd50 Real Block Info:**\\n`;\nmessage += `\u2022 Current block started: **${activeBlockInfo.blockStart}**\\n`;\nmessage += `\u2022 Block usage: **${activeBlockInfo.usagePercent || 'N/A'}%** used\\n`;\nmessage += `\u2022 Time remaining: **${waitTimeText}**\\n`;\nmessage += `\u2022 Next block starts: **${nextBlockFormatted}**\\n`;\nmessage += `\u2022 Current time: ${now.toLocaleString()}\\n\\n`;\n\nif (remainingInfo) {\n message += `**\ud83d\udcca Token Status:**\\n`;\n message += `\u2022 Remaining tokens: ${remainingInfo.remainingTokens} (${remainingInfo.remainingPercent}%)\\n\\n`;\n}\n\nif (recentUsageInfo.trim()) {\n message += `**\ud83d\udcc8 Block Details:**\\n\\`\\`\\`\\n${recentUsageInfo.trim()}\\n\\`\\`\\`\\n\\n`;\n}\n\n// Smart recommendations based on remaining time\nif (activeBlockInfo.totalMinutesRemaining <= 15) {\n message += `**\ud83c\udfc3\u200d\u2642\ufe0f Very Short Wait:** Next block starts very soon!\\n\\n`;\n} else if (activeBlockInfo.totalMinutesRemaining <= 60) {\n message += `**\u23f0 Short Wait:** Next block starts in ${waitTimeText}\\n\\n`;\n} else {\n message += `**\u23f3 Medium Wait:** Next block starts in ${waitTimeText}\\n\\n`;\n}\n\nmessage += `**\ud83d\udca1 Smart Recommendations:**\\n`;\nif (activeBlockInfo.totalMinutesRemaining <= 15) {\n message += `\u2022 \ud83c\udfc3\u200d\u2642\ufe0f Very short wait - try again in ${waitTimeText}\\n`;\n} else if (activeBlockInfo.totalMinutesRemaining <= 60) {\n message += `\u2022 \u23f0 Short wait - set a timer for ${nextBlockFormatted}\\n`;\n} else if (activeBlockInfo.totalMinutesRemaining <= 180) {\n message += `\u2022 \ud83d\udcc5 Medium wait - check back at ${nextBlockFormatted}\\n`;\n} else {\n message += `\u2022 \ud83d\udd50 Longer wait - set a reminder for ${nextBlockFormatted}\\n`;\n}\nmessage += `\u2022 \ud83d\udcdd Use the time to plan your next Claude tasks\\n`;\nmessage += `\u2022 \ud83d\udd04 Current block is ${activeBlockInfo.usagePercent || 'N/A'}% used\\n\\n`;\n\nmessage += `---\\n*Live status at ${now.toLocaleString()} based on real ccusage blocks data*`;\n\nconst response = {\n status: 'token_exhausted',\n claudeStatus: statusInfo.claudeStatus,\n message: message,\n waitTime: waitTimeText,\n nextBlockStart: nextBlockFormatted,\n minutesUntilNextBlock: activeBlockInfo.totalMinutesRemaining,\n blockInfo: {\n currentTime: now.toISOString(),\n blockStartTime: activeBlockInfo.blockStart,\n nextBlockStart: activeBlockInfo.nextBlockStart.toISOString(),\n hoursRemaining: activeBlockInfo.hoursRemaining,\n minutesRemaining: activeBlockInfo.minutesRemaining,\n usagePercent: activeBlockInfo.usagePercent || null,\n remainingTokens: remainingInfo?.remainingTokens || null,\n remainingPercent: remainingInfo?.remainingPercent || null\n },\n rawUsageOutput: usageText,\n timestamp: now.toISOString()\n};\n\nreturn [{ json: response }];"
},
"id": "process-usage-info",
"name": "Process Real Block Data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1340,
200
]
},
{
"parameters": {
"jsCode": "// Build success response for when Claude is available\nconst statusInfo = $('Process Status Response').item.json;\n\nconst message = `\u2705 **Claude is Available**\\n\\n${statusInfo.message}\\n\\n${statusInfo.statusDetails ? `**Status Details:**\\n\\`\\`\\`\\n${statusInfo.statusDetails}\\n\\`\\`\\`\\n\\n` : ''}`;\nmessage += `**\ud83d\udca1 Status:** Ready to process requests\\n\\n`;\nmessage += `---\\n*Status checked at ${new Date().toLocaleString()}*`;\n\nconst response = {\n status: 'available',\n claudeStatus: statusInfo.claudeStatus,\n message: message,\n timestamp: new Date().toISOString()\n};\n\nreturn [{ json: response }];"
},
"id": "build-success-response",
"name": "Build Success Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
400
]
}
],
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "Check Claude Status",
"type": "main",
"index": 0
}
]
]
},
"Check Claude Status": {
"main": [
[
{
"node": "Process Status Response",
"type": "main",
"index": 0
}
]
]
},
"Process Status Response": {
"main": [
[
{
"node": "Check If Token Info Needed",
"type": "main",
"index": 0
}
]
]
},
"Check If Token Info Needed": {
"main": [
[
{
"node": "Check Claude Usage",
"type": "main",
"index": 0
}
],
[
{
"node": "Build Success Response",
"type": "main",
"index": 0
}
]
]
},
"Check Claude Usage": {
"main": [
[
{
"node": "Process Real Block Data",
"type": "main",
"index": 0
}
]
]
},
"Process Real Block Data": {
"main": [
[]
]
},
"Build Success Response": {
"main": [
[]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateCredsSetupCompleted": true
},
"tags": [
{
"name": "Claude Status",
"id": "claude-status"
},
{
"name": "Real ccusage",
"id": "real-ccusage"
},
{
"name": "Token Management",
"id": "token-management"
},
{
"name": "AI Automation",
"id": "ai-automation"
}
]
}
About this workflow
Claude Status Checker (Real ccusage). Uses manualTrigger, executeCommand. Event-driven trigger; 7 nodes.
Source: https://gitlab.com/huyhq8/gitlab-ai-automation/-/blob/develop/n8n/workflows/claude-status-checker.json — original creator credit. Request a take-down →