From 5f4fa9db0f0f1ce69916f7b257809780d360027c Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Fri, 3 Jul 2026 14:23:36 +0800 Subject: [PATCH] fix(wasmguest): frame empty InvokeResponse as (ptr,0), not packed 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A successful hook whose response message has no set fields (LoadResponse, UnloadResponse) proto-marshals to zero bytes. bn_invoke's `len==0 → return 0` shortcut collided with the "callee could not produce an envelope" sentinel, so every successful empty-response hook — notably HOOK_LOAD — looked like an INTERNAL failure and got the instance discarded. Frame the empty case as (ptr, 0) with a real 1-byte-backed pointer instead; the host reads zero bytes into a valid empty InvokeResponse. Surfaced by the WO-WZ-003 end-to-end capability test (first exercise of LOAD). Co-Authored-By: Claude Fable 5 --- plugin/wasmguest/exports.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/plugin/wasmguest/exports.go b/plugin/wasmguest/exports.go index df8618a..831b6a8 100644 --- a/plugin/wasmguest/exports.go +++ b/plugin/wasmguest/exports.go @@ -77,10 +77,19 @@ func bnInvoke(hookID uint32, ptr uint32, length uint32) uint64 { // bn_invoke returns). Release it whether or not the host calls bn_free. delete(pinned, ptr) - lastResponse = resp + // A logically-empty InvokeResponse (Error nil + empty payload — e.g. + // LoadResponse/UnloadResponse) proto-marshals to ZERO bytes. That is a + // valid success envelope, not the "couldn't produce an envelope" sentinel, + // so frame it as (ptr, 0) with a real pointer. Returning packed 0 here + // would make the host mistake every empty-response hook (notably a + // successful LOAD) for an INTERNAL failure and discard the instance. A + // 1-byte backing keeps the pointer valid while the reported length stays 0 + // (the host reads zero bytes → an empty InvokeResponse, which is correct). if len(resp) == 0 { - return 0 + lastResponse = []byte{0} + return uint64(bufPtr(lastResponse)) << 32 } + lastResponse = resp return uint64(bufPtr(resp))<<32 | uint64(uint32(len(resp))) }