{"record":{"id":"c482b2e634e63fde","repo":"JuliusBrussee/caveman","slug":"langfuse-decode-w","errorCode":null,"errorMessage":"langfuse decode: %w","messagePattern":"langfuse decode: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/importers/langfuse.go","lineNumber":63,"sourceCode":"\tobs, err := decodeLangfuse(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trows := make([]Span, 0, len(obs))\n\tfor _, o := range obs {\n\t\trows = append(rows, mapLangfuse(o, opts))\n\t}\n\treturn rows, nil\n}\n\nfunc decodeLangfuse(data []byte) ([]langfuseObservation, error) {\n\tvar env langfuseEnvelope\n\tif err := json.Unmarshal(data, &env); err == nil && env.Observations != nil {\n\t\treturn env.Observations, nil\n\t}\n\tvar arr []langfuseObservation\n\tif err := json.Unmarshal(data, &arr); err != nil {\n\t\treturn nil, fmt.Errorf(\"langfuse decode: %w\", err)\n\t}\n\treturn arr, nil\n}\n\nfunc mapLangfuse(o langfuseObservation, opts Options) Span {\n\tstart, startNs := parseTimeFlexible(o.StartTime)\n\tend, endNs := parseTimeFlexible(o.EndTime)\n\n\tstatus := \"ok\"\n\tswitch o.Level {\n\tcase \"ERROR\":\n\t\tstatus = \"error\"\n\tcase \"WARNING\":\n\t\tstatus = \"unset\"\n\t}\n\n\tspanType := o.Type\n\tif spanType == \"\" {","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/importers/langfuse.go#L45-L81","documentation":"decodeLangfuse tries a langfuseEnvelope with an Observations array first, then a bare []langfuseObservation; if the bare-array unmarshal also fails, its json error is wrapped as 'langfuse decode: %w'. The wrapped message distinguishes shape problems ('invalid character ...') from field-type mismatches on a specific observation property.","triggerScenarios":"Passing the Langfuse traces export instead of the observations export (different schema); an object wrapper keyed differently than \"observations\"; an observation whose typed field (e.g. latencyNumbers) holds the wrong JSON type; empty or truncated input.","commonSituations":"Downloading the wrong export artifact from Langfuse UI (traces vs observations); API response shape change between Langfuse versions; saving an error body from a rate-limited request; concatenating NDJSON pages.","solutions":["Check the wrapped json error to see if the top-level shape or a typed field failed","Ensure input is an observations export: {\"observations\":[...]} or a bare array of observation objects","If the field type changed upstream, coerce it to the expected type in pre-processing","Confirm the saved file is complete JSON, not a truncated or multi-document stream"],"exampleFix":"// before\nraw := []byte(`{\"traces\":[{\"id\":\"t1\"}]}`) // wrong export\nrows, sum, err := importers.Import(importers.FormatLangfuse, raw, opts)\n\n// after\nraw := []byte(`{\"observations\":[{\"id\":\"o1\",\"startTime\":\"2026-01-01T00:00:00Z\"}]}`)\nrows, sum, err := importers.Import(importers.FormatLangfuse, raw, opts)","handlingStrategy":"validation","validationCode":"func looksLikeLangfuse(data []byte) bool {\n\td := bytes.TrimSpace(data)\n\treturn len(d) > 0 && (d[0] == '[' || bytes.Contains(d, []byte(\"\\\"observations\\\"\")))\n}","typeGuard":null,"tryCatchPattern":"if _, _, err := importers.Import(importers.FormatLangfuse, raw, opts); err != nil { if strings.Contains(err.Error(), \"langfuse decode\") { return fmt.Errorf(\"not a Langfuse observations export: %w\", err) } }","preventionTips":["Download the observations export, not traces, when using this format","Automate Langfuse export fetching to avoid wrong-artifact mistakes","Validate the file contains \"observations\":[ before import"],"tags":["go","import","langfuse","json","decode"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}