{"record":{"id":"cee632dadf624b45","repo":"wailsapp/wails","slug":"invalid-stream-batch-acknowledgement","errorCode":null,"errorMessage":"invalid stream batch acknowledgement","messagePattern":"invalid stream batch acknowledgement","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/internal/runtime/desktop/@wailsio/runtime/src/stream.ts","lineNumber":782,"sourceCode":"                [HDR_KIND]: String(KIND_DATA),\n                [HDR_BATCH]: String(frames.length - sent),\n                \"Content-Type\": \"application/octet-stream\",\n            },\n            body: body as BodyInit,\n            signal,\n        });\n        if (resp.ok) return;\n        if (resp.status !== 429) {\n            throw new Error(await resp.text());\n        }\n        // The receiver took a prefix of the batch. Resend only what is left,\n        // which keeps ordering without re-delivering anything.\n        const accepted = Number(resp.headers.get(HDR_BATCH) ?? 0);\n        const remaining = frames.length - sent;\n        if (!Number.isInteger(accepted) || accepted < 0 || accepted >= remaining) {\n            // Zero is valid (no progress), but an out-of-range acknowledgement\n            // is a protocol error. Keep zero on the retry path below.\n            if (accepted !== 0) throw new Error(\"invalid stream batch acknowledgement\");\n        }\n        sent += accepted;\n        body = buildBatch(frames.slice(sent));\n        await sleep(wait, signal);\n        wait = Math.min(wait * 2, 50);\n    }\n}\n\nfunction buildBatch(frames: Uint8Array[]): Uint8Array {\n    let size = 4;\n    for (const f of frames) size += 4 + f.byteLength;\n    const out = new Uint8Array(size);\n    const view = new DataView(out.buffer);\n    view.setUint32(0, frames.length);\n    let off = 4;\n    for (const f of frames) {\n        view.setUint32(off, f.byteLength);\n        off += 4;","sourceCodeStart":764,"sourceCodeEnd":800,"githubUrl":"https://github.com/wailsapp/wails/blob/0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3/v3/internal/runtime/desktop/@wailsio/runtime/src/stream.ts#L764-L800","documentation":"LockResource maps loaded resource data into a pointer; it returns NULL on failure. The w32 wrapper panics with 'LockResource failed' on NULL. The documented contract is that LockResource fails when the HGLOBAL passed in was not produced by LoadResource, or the underlying resource data could not be mapped. Notably, resources loaded via LoadResource do not need unlocking — there is no UnlockResource — so the failure almost always means the handle is wrong or the load failed earlier.","triggerScenarios":"Passing an HGLOBAL from GlobalAlloc instead of LoadResource; using the HGLOBAL after the module that owns the resource was freed by FreeLibrary; chaining from a FindResource whose failure was ignored so LoadResource got a null HRSRC and returned 0 (panicking first at LoadResource).","commonSituations":"Custom asset loaders reading embedded RCDATA where the LoadResource step's return was not checked in an older code path; unloading a plugin DLL while its icon data pointer is still in use.","solutions":["Check LoadResource's return (non-zero) before LockResource, and FindResource before that — validate the whole chain.","Ensure the HGLOBAL comes from LoadResource(hModule, hResInfo), never from GlobalAlloc.","Keep the owning module loaded (defer FreeLibrary) for as long as the locked resource pointer is in use.","Remember there is no unlock: treat the pointer as valid for the module's lifetime."],"exampleFix":"// before\nhRes := w32.LoadResource(hMod, hrs)\np := w32.LockResource(hRes) // hRes==0 unchecked in some path -> panic\n\n// after\nhRes := w32.LoadResource(hMod, hrs)\nif hRes == 0 {\n\treturn errors.New(\"LoadResource failed\")\n}\np := w32.LockResource(hRes)\nif p == nil {\n\treturn errors.New(\"LockResource failed\")\n}","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate FindResource and LoadResource returns (non-zero) before LockResource.","Only pass HGLOBALs that came from LoadResource, never GlobalAlloc.","Keep the owning module loaded while the resource pointer is in use."],"tags":["windows","resources","win32","syscall","panic"],"backgroundTag":null,"analyzedSha":"0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3","analyzedAt":"2026-08-15T14:17:36.034Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}