{"record":{"id":"d04f5c9137e6b772","repo":"wailsapp/wails","slug":"await-response-text","errorCode":null,"errorMessage":"${await response.text()}","messagePattern":"\\$\\{await response\\.text\\(\\)\\}","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts","lineNumber":179,"sourceCode":"        response = await sendChunked(url, headers, bodyStr);\n    } else {\n        response = await fetch(url, { method: 'POST', headers, body: bodyStr });\n    }\n    if (!response.ok) {\n      const ct = response.headers.get(\"Content-Type\");\n      if (ct?.includes(\"application/json\")) {\n          const json: CallErrorType = await response.json();\n          let err;\n          switch (json.kind) {\n              case \"ReferenceError\": err = new ReferenceError(json.message); break;\n              case \"TypeError\":      err = new TypeError(json.message); break;\n              case \"RuntimeError\":   err = new RuntimeError(json.message); break;\n              default:               err = new Error(json.message);\n          }\n          err.cause = json.cause;\n          throw err\n      }\n      throw new Error(await response.text());\n    }\n\n    if ((response.headers.get(\"Content-Type\")?.indexOf(\"application/json\") ?? -1) !== -1) {\n        return response.json();\n    } else {\n        return response.text();\n    }\n}\n\n// sendChunked splits a large serialised request body into CHUNK_THRESHOLD-sized\n// byte chunks and sends them serially.  Encoding to UTF-8 bytes before slicing\n// prevents corruption of non-BMP characters (surrogate pairs) that would occur\n// when splitting at JavaScript string indices.  The Go transport assembles the\n// raw bytes before processing.  Only the final chunk's response carries the RPC result.\nasync function sendChunked(url: URL, headers: Record<string, string>, bodyStr: string): Promise<Response> {\n    const chunkId = nanoid();\n    const bodyBytes = new TextEncoder().encode(bodyStr);\n    const totalChunks = Math.ceil(bodyBytes.length / CHUNK_THRESHOLD);","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/wailsapp/wails/blob/0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3/v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts#L161-L197","documentation":"GdiplusStartup initializes the GDI+ library, returning a Status code where Ok(0) means success. The w32 wrapper panics with 'GdiplusStartup failed with status <name>' for any non-Ok status. In practice GdiplusStartup almost never fails unless COM/GDI state is broken, the token storage is corrupt, or initialization is attempted repeatedly/unbalanced against GdiplusShutdown. The status name in the message (from GetGpStatus) identifies which condition fired, e.g. GdiplusNotInitialized or OutOfMemory.","triggerScenarios":"Calling GdiplusStartup a second time without GdiplusShutdown in between on the same token variable; calling startup after the process has corrupted GDI state through handle leaks; running it on a thread whose apartment state conflicts with prior COM initialization; out-of-memory conditions from enormous bitmap allocations.","commonSituations":"An image-decoding feature (thumbnailer, screenshot encoder) that initializes GDI+ per request but shuts down only sometimes; app-level OOM from loading huge images that then surfaces in the next GDI+ init; initialization racing between two goroutines calling into w32 concurrently.","solutions":["Initialize GDI+ exactly once at feature start and pair it with a single GdiplusShutdown at feature end (sync.Once or explicit lifecycle owner).","Serialize startup/shutdown with a mutex if multiple code paths can trigger GDI+ use.","Read the status name in the panic message: OutOfMemory points at memory pressure, not API misuse — free large buffers first.","Verify the GdiplusStartupInput struct was built with a valid GdiplusVersion (1).","If it fails only in a long-running app, check the GDI object count for exhaustion feeding into GDI+ init."],"exampleFix":"// before\nfunc encode(img []byte){\n\tvar in w32.GdiplusStartupInput; in.GdiplusVersion = 1\n\tw32.GdiplusStartup(&in, nil) // called per request; unbalanced with shutdown\n\t... // occasional second startup -> panic\n}\n\n// after\nvar gpOnce sync.Once\nfunc ensureGdiplus(){\n\tgpOnce.Do(func(){\n\t\tvar in w32.GdiplusStartupInput; in.GdiplusVersion = 1\n\t\tw32.GdiplusStartup(&in, nil)\n\t})\n}","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"defer func() {\n\tif r := recover(); r != nil {\n\t\tif msg, _ := r.(string); strings.HasPrefix(msg, \"GdiplusStartup\") {\n\t\t\terr = fmt.Errorf(\"gdiplus init: %v\", r)\n\t\t\treturn\n\t\t}\n\t\tpanic(r)\n\t}\n}()\nw32.GdiplusStartup(&input, nil)","preventionTips":["Initialize GDI+ once per process via sync.Once and shut down once at exit.","Always set GdiplusStartupInput.GdiplusVersion = 1.","Read the status name in the panic message — OutOfMemory means memory pressure, not API misuse."],"tags":["windows","gdiplus","lifecycle","syscall","panic"],"backgroundTag":null,"analyzedSha":"0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3","analyzedAt":"2026-08-15T14:17:36.034Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}