Skip to content

Commit c7f0e6f

Browse files
joe4devclaude
andcommitted
fix(init): preserve runtime init-error payload fields (e.g. empty stackTrace)
SendInitErrorResponse round-tripped the runtime's /init/error payload through a typed struct whose stackTrace used omitempty, dropping an empty-but-present "stackTrace": [] (as AWS emits for Runtime.HandlerNotFound). Decode into a map and only inject requestId, forwarding the runtime's fields verbatim. Fixes test_lambda_handler_not_found. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c5466a9 commit c7f0e6f

1 file changed

Lines changed: 11 additions & 15 deletions

File tree

cmd/localstack/custom_interop.go

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -250,13 +250,12 @@ func (c *CustomInteropServer) SendInitErrorResponse(resp *interop.ErrorInvokeRes
250250
// AwaitInitializedWithDetails unblocks in main.go, so the fallback observes the flag.
251251
c.initErrorForwarded.Store(true)
252252

253-
// Deserialize the raw payload so we can include the requestId and structured fields.
254-
var parsed struct {
255-
ErrorMessage string `json:"errorMessage"`
256-
ErrorType string `json:"errorType"`
257-
StackTrace []string `json:"stackTrace,omitempty"`
258-
}
259-
if err := json.Unmarshal(resp.Payload, &parsed); err != nil {
253+
// Forward the runtime's structured payload as-is and only inject the requestId. Decoding
254+
// into a map rather than a typed struct preserves fields exactly as the runtime emitted
255+
// them — in particular an empty but present "stackTrace": [] (e.g. Runtime.HandlerNotFound),
256+
// which a typed struct with omitempty would drop on re-marshal.
257+
var payload map[string]any
258+
if err := json.Unmarshal(resp.Payload, &payload); err != nil {
260259
log.WithError(err).Warn("Failed to parse init error payload; forwarding raw payload")
261260
if err := c.localStackAdapter.SendStatus(Error, resp.Payload); err != nil {
262261
log.WithError(err).WithField("runtime-id", c.localStackAdapter.RuntimeId).
@@ -265,14 +264,11 @@ func (c *CustomInteropServer) SendInitErrorResponse(resp *interop.ErrorInvokeRes
265264
return c.delegate.SendInitErrorResponse(resp)
266265
}
267266

268-
requestId := c.delegate.GetCurrentInvokeID()
269-
adaptedResp := lsapi.ErrorResponse{
270-
ErrorMessage: parsed.ErrorMessage,
271-
ErrorType: parsed.ErrorType,
272-
RequestId: &requestId,
273-
StackTrace: parsed.StackTrace,
274-
}
275-
body, err := json.Marshal(adaptedResp)
267+
// No invocation is active during the init phase, so this is typically blank; AWS still
268+
// includes a (blank) requestId in the init error payload.
269+
payload["requestId"] = c.delegate.GetCurrentInvokeID()
270+
271+
body, err := json.Marshal(payload)
276272
if err != nil {
277273
log.WithError(err).Error("Failed to marshal adapted init error response")
278274
body = resp.Payload

0 commit comments

Comments
 (0)