--- phase: 16-api-migration plan: 06 type: execute wave: 1 depends_on: [] files_modified: [n8n-workflow.json] autonomous: true gap_closure: true must_haves: truths: - "Text command 'start/stop/restart ' queries containers via GraphQL, not Docker socket proxy" - "Text command 'update ' queries containers via GraphQL, not Docker socket proxy" - "Text command 'batch' queries containers via GraphQL, not Docker socket proxy" - "Zero active Execute Command nodes with docker-socket-proxy references remain in n8n-workflow.json" artifacts: - path: "n8n-workflow.json" provides: "Main workflow with all text command paths using GraphQL" contains: "UNRAID_HOST" key_links: - from: "n8n-workflow.json (Query Containers for Action)" to: "Unraid GraphQL API" via: "POST to $env.UNRAID_HOST/graphql" pattern: "UNRAID_HOST.*graphql" - from: "n8n-workflow.json (Query Containers for Update)" to: "Unraid GraphQL API" via: "POST to $env.UNRAID_HOST/graphql" pattern: "UNRAID_HOST.*graphql" - from: "n8n-workflow.json (Query Containers for Batch)" to: "Unraid GraphQL API" via: "POST to $env.UNRAID_HOST/graphql" pattern: "UNRAID_HOST.*graphql" --- Migrate the 3 remaining text command entry points in the main workflow from Docker socket proxy Execute Command nodes to Unraid GraphQL API queries. 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. Plans 16-02 through 16-05 introduced defects that required a hotfix (commit 216f3a4). Do NOT repeat these mistakes: 1. **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": [...] } }` 2. **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 }` 3. **GraphQL HTTP Request nodes MUST use Header Auth credential, NOT manual headers.** Using `$env.UNRAID_API_KEY` as a manual header causes `Invalid CSRF token` / `UNAUTHENTICATED` errors. The correct config: - `"authentication": "genericCredentialType"` - `"genericAuthType": "httpHeaderAuth"` - `"credentials": { "httpHeaderAuth": { "id": "unraid-api-key-credential-id", "name": "Unraid API Key" } }` - Do NOT add `x-api-key` to `headerParameters` — the credential handles it. Copy the exact auth config from any existing working node (e.g., "Get Container For Action"). 4. **Node names MUST be unique.** Duplicate names cause connection ambiguity. n8n cannot distinguish which node a connection refers to. 5. **After a GraphQL query chain (HTTP → Normalizer → Registry), downstream Code nodes receive container item arrays, NOT upstream preparation data.** Use `$('Upstream Node Name').item.json` to reference data from before the chain. Using `$input.item.json` will give you a container object, not the preparation data. @/home/luc/.claude/get-shit-done/workflows/execute-plan.md @/home/luc/.claude/get-shit-done/templates/summary.md @.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.containers` from 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) - 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 - 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);` then `const dockerOutput = JSON.stringify(containers);` - The matching sub-workflow (n8n-matching.json) expects `containerList` as a JSON string of the container array, so JSON.stringify the normalized array. - 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.stdout` to `JSON.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.stdout` to `JSON.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). 1. 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-proxy` as a container name pattern, NOT as an API endpoint 2. Search for "executeCommand" node type -- should find ZERO instances (all 3 Execute Command nodes removed) 3. Search for "Query Containers for Action", "Query Containers for Update", "Query Containers for Batch" -- all 3 must exist 4. Search for "Normalize Action Containers", "Normalize Update Containers", "Normalize Batch Containers" -- all 3 must exist 5. Search for "Update Registry (Action)", "Update Registry (Update)", "Update Registry (Batch)" -- all 3 must exist 6. Verify connections: "Parse Action Command" -> "Query Containers for Action", "Parse Update Command" -> "Query Containers for Update", "Is Batch Command" [0] -> "Query Containers for Batch" 7. 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. Task 2: ALREADY COMPLETED — dead code and orphan removal n8n-workflow.json SKIP THIS TASK — already completed in hotfix commit 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. 1. `grep -c "docker-socket-proxy" n8n-workflow.json` returns 2 (only infra-exclusion filter patterns, not API endpoints) 2. `grep -c "executeCommand" n8n-workflow.json` returns 0 (zero Execute Command nodes) 3. `grep -c "UNRAID_HOST" n8n-workflow.json` returns 12+ (9 existing GraphQL nodes + 3 new ones) 4. `grep "Query Containers for Action\|Query Containers for Update\|Query Containers for Batch" n8n-workflow.json` finds all 3 new query nodes 5. Workflow pushes to n8n successfully (HTTP 200) 6. All connection chains intact: Parse Command -> Query -> Normalize -> Registry -> Prepare Match -> Execute Match -> Route Result 7. **Connection integrity check:** All connection dictionary keys match actual node names (no node IDs as keys) 8. **Auth check:** All new HTTP Request nodes use `genericCredentialType` + `httpHeaderAuth` credential, NOT manual `x-api-key` headers 9. **Name uniqueness check:** No duplicate node names exist - 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 After completion, create `.planning/phases/16-api-migration/16-06-SUMMARY.md`