Incorporates 5 lessons from commit 216f3a4 into the plan:
- Connection keys/targets must use node names, not IDs
- HTTP auth must use Header Auth credential, not manual env vars
- Node names must be unique
- Use $('Node Name') after GraphQL chains, not $input.item.json
- Added validation checklist to verification section
Marks Task 2 as already completed (dead code removal done in hotfix).
Updates node counts from 193 to 181 baseline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
14 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, gap_closure, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | gap_closure | must_haves | |||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 16-api-migration | 06 | execute | 1 |
|
true | true |
|
Purpose: Close the verification gaps that block Phase 17 (docker-socket-proxy removal). The 3 text command paths (start/stop/restart, update, batch) still use Execute Command nodes with curl to the docker-socket-proxy. After this plan, ALL container operations in the main workflow use GraphQL -- zero Docker socket proxy dependencies remain.
Output: Updated n8n-workflow.json with 3 GraphQL query chains replacing 3 Execute Command nodes.
NOTE: Dead code removal (Task 2 originally) and orphan cleanup were already completed in commit 216f3a4. The current node count is 181, not 193. Only Task 1 remains.
<critical_lessons>
Plans 16-02 through 16-05 introduced defects that required a hotfix (commit 216f3a4). Do NOT repeat these mistakes:
-
Connection keys MUST use node NAMES, never node IDs. n8n resolves connections by node name. Using IDs as dictionary keys (e.g.,
"http-get-container-for-action") creates orphaned wiring that silently fails at runtime.- WRONG:
"connections": { "http-my-node-id": { "main": [...] } } - RIGHT:
"connections": { "My Node Display Name": { "main": [...] } }
- WRONG:
-
Connection targets MUST also use node NAMES, never IDs.
- WRONG:
{ "node": "code-normalizer-action", "type": "main", "index": 0 } - RIGHT:
{ "node": "Normalize GraphQL Response (Action)", "type": "main", "index": 0 }
- WRONG:
-
GraphQL HTTP Request nodes MUST use Header Auth credential, NOT manual headers. Using
$env.UNRAID_API_KEYas a manual header causesInvalid CSRF token/UNAUTHENTICATEDerrors. The correct config:"authentication": "genericCredentialType""genericAuthType": "httpHeaderAuth""credentials": { "httpHeaderAuth": { "id": "unraid-api-key-credential-id", "name": "Unraid API Key" } }- Do NOT add
x-api-keytoheaderParameters— the credential handles it. Copy the exact auth config from any existing working node (e.g., "Get Container For Action").
-
Node names MUST be unique. Duplicate names cause connection ambiguity. n8n cannot distinguish which node a connection refers to.
-
After a GraphQL query chain (HTTP → Normalizer → Registry), downstream Code nodes receive container item arrays, NOT upstream preparation data. Use
$('Upstream Node Name').item.jsonto reference data from before the chain. Using$input.item.jsonwill give you a container object, not the preparation data. </critical_lessons>
<execution_context> @/home/luc/.claude/get-shit-done/workflows/execute-plan.md @/home/luc/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/STATE.md @.planning/phases/16-api-migration/16-01-SUMMARY.md @.planning/phases/16-api-migration/16-05-SUMMARY.md @.planning/phases/16-api-migration/16-VERIFICATION.md @n8n-workflow.json @CLAUDE.md Task 1: Replace 3 Execute Command nodes with GraphQL query chains n8n-workflow.json Replace 3 Execute Command nodes that use `curl` to docker-socket-proxy with GraphQL HTTP Request + Normalizer + Registry Update chains. Follow the exact same pattern established in Plan 16-05 (Task 1) for the 6 inline keyboard query paths.Node 1: "Docker List for Action" (id: exec-docker-list-action)
Current: Execute Command node running curl -s --max-time 5 'http://docker-socket-proxy:2375/v1.47/containers/json?all=true'
Position: [1120, 400]
Connected FROM: "Parse Action Command"
Connected TO: "Prepare Action Match Input"
Replace with 3 nodes:
1a. "Query Containers for Action" — HTTP Request node (replaces Execute Command)
- type: n8n-nodes-base.httpRequest, typeVersion: 4.2
- method: POST
- url:
={{ $env.UNRAID_HOST }}/graphql - authentication: genericCredentialType, genericAuthType: httpHeaderAuth
- credentials:
{ "httpHeaderAuth": { "id": "unraid-api-key-credential-id", "name": "Unraid API Key" } } - Do NOT add manual x-api-key headers — the credential handles auth automatically
- sendBody: true, specifyBody: json
- jsonBody:
{"query": "query { docker { containers { id names state image status } } }"} - options: timeout: 15000
- position: [1120, 400]
- Copy the full node structure from an existing working node (e.g., "Get Container For Action") and only change name, id, position, and jsonBody
1b. "Normalize Action Containers" — Code node (GraphQL response normalizer)
- Inline normalizer code (same as Plan 16-01/16-05 pattern):
- Extract
data.docker.containersfrom GraphQL response - Map fields: id->Id, names->Names (add '/' prefix), state->State (RUNNING->running, STOPPED->exited, PAUSED->paused), image->Image, status->Status
- Handle GraphQL errors (check response.errors array)
- Extract
- position: [1230, 400] (shift right to make room)
1c. "Update Registry (Action)" — Code node (Container ID Registry update)
- Inline registry update code (same as Plan 16-01/16-05 pattern):
- Read static data
_containerIdRegistry, parse JSON - Map each normalized container: name (strip '/') -> { name, unraidId: container.Id }
- Write back to static data with JSON.stringify (top-level assignment for persistence)
- Pass through all container items unchanged
- Read static data
- position: [1340, 400] (note: this is where "Prepare Action Match Input" currently sits)
CRITICAL wiring change for "Prepare Action Match Input":
- Move "Prepare Action Match Input" position to [1450, 400] (shift right to accommodate new nodes)
- Update its Code to read normalized containers instead of
stdout:- OLD:
const dockerOutput = $input.item.json.stdout; - NEW:
const containers = $input.all().map(item => item.json);thenconst dockerOutput = JSON.stringify(containers); - The matching sub-workflow (n8n-matching.json) expects
containerListas a JSON string of the container array, so JSON.stringify the normalized array.
- OLD:
- Connection chain: Query Containers for Action -> Normalize Action Containers -> Update Registry (Action) -> Prepare Action Match Input -> Execute Action Match (unchanged)
Node 2: "Docker List for Update" (id: exec-docker-list-update)
Current: Execute Command node running same curl command Position: [1120, 1000] Connected FROM: "Parse Update Command" Connected TO: "Prepare Update Match Input"
Replace with 3 nodes (same pattern):
2a. "Query Containers for Update" — HTTP Request node
- Same config as 1a, position: [1120, 1000]
2b. "Normalize Update Containers" — Code node
- Same normalizer code, position: [1230, 1000]
2c. "Update Registry (Update)" — Code node
- Same registry code, position: [1340, 1000]
Update "Prepare Update Match Input":
- Move position to [1450, 1000]
- Change Code from
$input.item.json.stdouttoJSON.stringify($input.all().map(item => item.json)) - Connection chain: Query Containers for Update -> Normalize Update Containers -> Update Registry (Update) -> Prepare Update Match Input -> Execute Update Match (unchanged)
Node 3: "Get Containers for Batch" (id: exec-docker-list-batch)
Current: Execute Command node running same curl command Position: [1340, -300] Connected FROM: "Is Batch Command" Connected TO: "Prepare Batch Match Input"
Replace with 3 nodes (same pattern):
3a. "Query Containers for Batch" — HTTP Request node
- Same config as 1a, position: [1340, -300]
3b. "Normalize Batch Containers" — Code node
- Same normalizer code, position: [1450, -300]
3c. "Update Registry (Batch)" — Code node
- Same registry code, position: [1560, -300]
Update "Prepare Batch Match Input":
- Move position to [1670, -300]
- Change Code from
$input.item.json.stdouttoJSON.stringify($input.all().map(item => item.json)) - Connection chain: Is Batch Command [output 0] -> Query Containers for Batch -> Normalize Batch Containers -> Update Registry (Batch) -> Prepare Batch Match Input -> Execute Batch Match (unchanged)
Connection updates in the connections object:
- "Parse Action Command" target changes from "Docker List for Action" to "Query Containers for Action"
- "Parse Update Command" target changes from "Docker List for Update" to "Query Containers for Update"
- "Is Batch Command" output 0 target changes from "Get Containers for Batch" to "Query Containers for Batch"
- Add new connection entries for each 3-node chain (Query -> Normalize -> Registry -> Prepare)
- Remove old connection entries for deleted nodes
Important: Use the same inline normalizer and registry update Code exactly as implemented in Plan 16-05 Task 1. Copy the jsCode from any of the 6 existing normalizer/registry nodes already in n8n-workflow.json (e.g., find "Normalize GraphQL Response" or "Update Container Registry" nodes). Do NOT reference utility node templates from the main workflow -- sub-workflow pattern requires inline code (per Phase 16-01 decision).
- Search n8n-workflow.json for "docker-socket-proxy" -- should find ONLY the 2 infra-exclusion filter references in "Check Available Updates" (line ~2776) and "Prepare Update All Batch" (line ~3093) Code nodes which use
socket-proxyas a container name pattern, NOT as an API endpoint - Search for "executeCommand" node type -- should find ZERO instances (all 3 Execute Command nodes removed)
- Search for "Query Containers for Action", "Query Containers for Update", "Query Containers for Batch" -- all 3 must exist
- Search for "Normalize Action Containers", "Normalize Update Containers", "Normalize Batch Containers" -- all 3 must exist
- Search for "Update Registry (Action)", "Update Registry (Update)", "Update Registry (Batch)" -- all 3 must exist
- Verify connections: "Parse Action Command" -> "Query Containers for Action", "Parse Update Command" -> "Query Containers for Update", "Is Batch Command" [0] -> "Query Containers for Batch"
- Push workflow to n8n and verify HTTP 200 response All 3 text command entry points (action, update, batch) query containers via Unraid GraphQL API using the HTTP Request -> Normalizer -> Registry Update -> Prepare Match Input chain. Zero Execute Command nodes remain. Workflow pushes successfully to n8n.
216f3a4.
Removed 12 nodes: 6 dead code chains (Build/Execute/Parse Action Command × 2) and 6 orphan utility templates (GraphQL Response Normalizer, Container ID Registry, GraphQL Error Handler, Unraid API HTTP Template, Callback Token Encoder/Decoder). Node count went from 193 to 181.
The 2 remaining socket-proxy string references in "Check Available Updates" and "Prepare Update All Batch" are functional infrastructure exclusion filters — they will be addressed in Phase 17.
Already verified. Node count is 181.
Completed in prior hotfix.
<success_criteria>
- Zero Execute Command nodes with docker-socket-proxy curl commands
- 3 new GraphQL HTTP Request + Normalizer + Registry Update chains for text command paths
- Total node count: 181 + 9 new - 3 removed = 187
- Workflow pushes to n8n successfully
- All text command paths route through GraphQL before reaching matching sub-workflow
- All new connection keys use node NAMES (not IDs)
- All new HTTP nodes use Header Auth credential (not $env.UNRAID_API_KEY)
- No duplicate node names introduced
- Phase 16 verification gaps closed: all 3 partial truths become fully verified </success_criteria>