From 07e619403cd6e4de21f4dea09e5d593871a1e96b Mon Sep 17 00:00:00 2001 From: Molecule AI Fullstack Engineer Date: Mon, 18 May 2026 06:23:21 +0000 Subject: [PATCH] fix(handlers): truncate manifest.json at last '}' before JSON parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo-root manifest.json has a trailing JSON5-style // comment appended by the Integration Tester CI trigger (// Triggered by Integration Tester at 2026-05-10T08:52Z). Go's encoding/json throws "invalid character '/' after top-level value" on the '/', causing loadRuntimesFromManifest to fall back to the fallback allowlist at runtime registry init. In E2E Chat runs (MOLECULE_ENV=development), the platform server starts from workspace-server/ and resolves ../manifest.json — the file with the trailing comment — so the echo runtime is never registered and all round-trip Chat tests fail. Fix: bytes.LastIndex(data, []byte("}")) + slice to that position before json.Unmarshal, so any trailing comment or garbage after the closing brace is discarded silently. Two regression tests added: - TestLoadRuntimesFromManifest_StripsJSON5TrailingComment: real manifest content + "// Triggered by ..." after } - TestLoadRuntimesFromManifest_ExtraDataAfterClosingBrace: valid JSON + arbitrary trailing text Closes #1480 Co-Authored-By: Claude Opus 4.7 --- .../internal/handlers/runtime_registry.go | 8 ++++ .../handlers/runtime_registry_test.go | 47 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/workspace-server/internal/handlers/runtime_registry.go b/workspace-server/internal/handlers/runtime_registry.go index 0efa2ec0c..9a46eb73a 100644 --- a/workspace-server/internal/handlers/runtime_registry.go +++ b/workspace-server/internal/handlers/runtime_registry.go @@ -25,6 +25,7 @@ package handlers // to the pre-refactor hardcoded list so nothing regresses. import ( + "bytes" "encoding/json" "log" "os" @@ -101,6 +102,13 @@ func loadRuntimesFromManifest(path string) (map[string]struct{}, error) { if err != nil { return nil, err } + // Truncate at the last '}' to drop trailing JSON5-style // comments + // (e.g. "// Triggered by Integration Tester at 2026-05-10T08:52Z") + // that would otherwise cause Go's json.Unmarshal to fail with + // "invalid character '/' after top-level value". + if last := bytes.LastIndex(data, []byte("}")); last != -1 { + data = data[:last+1] + } var m manifestFile if err := json.Unmarshal(data, &m); err != nil { return nil, err diff --git a/workspace-server/internal/handlers/runtime_registry_test.go b/workspace-server/internal/handlers/runtime_registry_test.go index 78c2c6878..f75fe0f7a 100644 --- a/workspace-server/internal/handlers/runtime_registry_test.go +++ b/workspace-server/internal/handlers/runtime_registry_test.go @@ -83,6 +83,53 @@ func TestLoadRuntimesFromManifest_MalformedJSON(t *testing.T) { } } +func TestLoadRuntimesFromManifest_StripsJSON5TrailingComment(t *testing.T) { + // Regression: manifest.json has a trailing JSON5-style // comment appended + // by the Integration Tester (// Triggered by ...). Go's encoding/json + // throws "invalid character '/' after top-level value" on the '/'. + // loadRuntimesFromManifest must truncate at the last '}' before parsing + // so the comment is ignored and the valid JSON body is processed. + dir := t.TempDir() + path := filepath.Join(dir, "manifest.json") + content := `{ + "workspace_templates": [ + {"name": "claude-code-default", "repo": "org/t-cc"}, + {"name": "langgraph", "repo": "org/t-lg"} + ] + } + // Triggered by Integration Tester at 2026-05-10T08:52Z` + _ = os.WriteFile(path, []byte(content), 0600) + + got, err := loadRuntimesFromManifest(path) + if err != nil { + t.Fatalf("expected successful parse despite trailing comment, got: %v", err) + } + for _, must := range []string{"claude-code", "langgraph", "external", "kimi", "kimi-cli"} { + if _, ok := got[must]; !ok { + t.Errorf("expected runtime %q in set after JSON5 comment strip: %v", must, keys(got)) + } + } +} + +func TestLoadRuntimesFromManifest_ExtraDataAfterClosingBrace(t *testing.T) { + // A file with trailing garbage after the closing '}' (not a // comment) + // should also be tolerated — the parser truncates at last '}'; + // anything after is discarded. + dir := t.TempDir() + path := filepath.Join(dir, "manifest.json") + content := `{"workspace_templates":[{"name":"hermes","repo":"org/t"}]} +extra data that should be ignored` + _ = os.WriteFile(path, []byte(content), 0600) + + got, err := loadRuntimesFromManifest(path) + if err != nil { + t.Fatalf("expected parse with trailing data: %v", err) + } + if _, ok := got["hermes"]; !ok { + t.Errorf("expected hermes runtime after truncation: %v", keys(got)) + } +} + // TestRealManifestParses — sanity check against the actual // monorepo manifest.json so a future schema change to that file // (e.g. workspace_templates → workspace_runtime_templates) surfaces -- 2.52.0