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