From b3041c13d3b6da0deca19a0d9ac1b8b5659754d1 Mon Sep 17 00:00:00 2001 From: claude-ceo-assistant Date: Fri, 8 May 2026 16:25:24 -0700 Subject: [PATCH] fix(org-import): emit started event after YAML parse so name is populated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The org.import.started event was firing immediately after request body bind, before the YAML at body.Dir was loaded. Result: payload.name was "" whenever the caller passed `dir` (the common path — the canvas and all live imports use dir, not inline template). Three started rows already in the local platform's structure_events have empty name. Fix: move the started emit (and importStart timestamp) to after the YAML unmarshal / inline-template fallthrough, where tmpl.Name is guaranteed populated. Bonus: pre-parse error returns (invalid body, traversal-rejected dir, file-not-found, YAML expansion fail, YAML unmarshal fail, neither dir nor template provided) no longer emit an orphan started row — every started is now guaranteed a paired completed/failed. Verified live against running platform: re-imported molecule-dev-only, new started row in structure_events carries "Molecule AI Dev Team (dev-only)" instead of "". Tests: full handler suite green (`go test ./internal/handlers/`). Co-Authored-By: Claude Opus 4.7 (1M context) --- workspace-server/internal/handlers/org.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/workspace-server/internal/handlers/org.go b/workspace-server/internal/handlers/org.go index 6ed3a85f..233cc69f 100644 --- a/workspace-server/internal/handlers/org.go +++ b/workspace-server/internal/handlers/org.go @@ -589,12 +589,6 @@ func (h *OrgHandler) Import(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } - importStart := time.Now() - emitOrgEvent(c.Request.Context(), "org.import.started", map[string]any{ - "name": body.Template.Name, - "dir": body.Dir, - "mode": body.Mode, - }) var tmpl OrgTemplate var orgBaseDir string // base directory for files_dir resolution @@ -635,6 +629,19 @@ func (h *OrgHandler) Import(c *gin.Context) { return } + // Emit started AFTER the YAML is loaded so payload.name carries the + // resolved template name (was: empty when caller passed `dir` instead + // of inline `template`). Pre-parse error paths above return without + // emitting — semantically "we couldn't even start an import" — so + // every started event is guaranteed a paired completed/failed below + // (no orphan started rows in structure_events). + importStart := time.Now() + emitOrgEvent(c.Request.Context(), "org.import.started", map[string]any{ + "name": tmpl.Name, + "dir": body.Dir, + "mode": body.Mode, + }) + // Required-env preflight — refuses import when any required_env is // missing from global_secrets. No bypass: the prior `force: true` // escape hatch was removed (issue #2290) because it was the silent