From 9539759db6cf9d3a6d9a7bfb1e6e45ea3fe8ec26 Mon Sep 17 00:00:00 2001 From: dtoro Date: Fri, 10 Jul 2026 09:32:45 +0200 Subject: [PATCH] fix: NULL-scan bug in LXC target resolution for entities without a host attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found live: `run` against lxc:caddy failed with "missing pve_id" even though pve_id=121 was present — caddy is an inventory-seeded LXC with no `host` attribute at all (only pct_create-provisioned LXCs set one). The combined query scanned attributes->>'host' (SQL NULL) into a plain Go string, which errors the whole Scan — including the pve_id column that scanned fine. COALESCE the host column to '' so a missing host attribute degrades to the documented default instead of failing the whole resolution. Co-Authored-By: Claude Opus 4.8 --- internal/httpapi/phase3.go | 7 ++++++- internal/mcp/server.go | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/httpapi/phase3.go b/internal/httpapi/phase3.go index b64f69b..1e64b65 100644 --- a/internal/httpapi/phase3.go +++ b/internal/httpapi/phase3.go @@ -163,7 +163,12 @@ func resolveRunTarget(ctx context.Context, pool *db.Pool, targetSlug string) (ho } if strings.HasPrefix(targetSlug, "lxc:") { var pveID, hostAttr string - if qerr := pool.QueryRow(ctx, "SELECT attributes->>'pve_id', attributes->>'host' FROM entities WHERE slug = $1", targetSlug).Scan(&pveID, &hostAttr); qerr != nil || pveID == "" { + // COALESCE the host column: many older LXC entities (seeded from + // inventory, not provisioned by pct_create) have pve_id but no host + // attribute at all. Scanning a SQL NULL into a plain string errors + // the whole row, wrongly reporting "missing pve_id" even when it was + // present — COALESCE avoids the NULL, "" is handled below. + if qerr := pool.QueryRow(ctx, "SELECT attributes->>'pve_id', COALESCE(attributes->>'host', '') FROM entities WHERE slug = $1", targetSlug).Scan(&pveID, &hostAttr); qerr != nil || pveID == "" { return "", "", nil, fmt.Errorf("LXC not found or missing pve_id: %s", targetSlug) } hostSlug := hostAttr diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 56163e4..bc95888 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -1196,7 +1196,12 @@ func resolveExecTarget(ctx context.Context, pool *db.Pool, targetSlug string) (h } if strings.HasPrefix(targetSlug, "lxc:") { var pveID, hostAttr string - if qerr := pool.QueryRow(ctx, "SELECT attributes->>'pve_id', attributes->>'host' FROM entities WHERE slug = $1", targetSlug).Scan(&pveID, &hostAttr); qerr != nil || pveID == "" { + // COALESCE the host column: many older LXC entities (seeded from + // inventory, not provisioned by pct_create) have pve_id but no host + // attribute at all. Scanning a SQL NULL into a plain string errors + // the whole row, wrongly reporting "missing pve_id" even when it was + // present — COALESCE avoids the NULL, "" is handled below. + if qerr := pool.QueryRow(ctx, "SELECT attributes->>'pve_id', COALESCE(attributes->>'host', '') FROM entities WHERE slug = $1", targetSlug).Scan(&pveID, &hostAttr); qerr != nil || pveID == "" { return "", "", nil, fmt.Errorf("LXC not found or missing pve_id: %s", targetSlug) } hostSlug := hostAttr