{"record":{"id":"6c3ad570b5bb76c8","repo":"chenhg5/cc-connect","slug":"max-update-message-no-message-id-in-reply-contex","errorCode":null,"errorMessage":"max: update message: no message id in reply context","messagePattern":"max: update message: no message id in reply context","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/max/max.go","lineNumber":500,"sourceCode":"\t\treturn fmt.Errorf(\"max: upload audio: %w\", err)\n\t}\n\tbody := &maxSendBody{\n\t\tAttachments: []maxOutAttachment{{\n\t\t\tType:    \"audio\",\n\t\t\tPayload: maxTokenPayload{Token: token},\n\t\t}},\n\t}\n\treturn p.postMessage(ctx, rctx.chatID, body)\n}\n\n// UpdateMessage implements core.MessageUpdater via PUT /messages?message_id=.\nfunc (p *Platform) UpdateMessage(ctx context.Context, replyCtx any, content string) error {\n\trctx, ok := replyCtx.(replyContext)\n\tif !ok {\n\t\treturn fmt.Errorf(\"max: unexpected replyCtx type %T\", replyCtx)\n\t}\n\tif rctx.messageID == \"\" {\n\t\treturn fmt.Errorf(\"max: update message: no message id in reply context\")\n\t}\n\tbody := maxSendBody{Text: content, Format: \"markdown\"}\n\tdata, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPut, p.apiBase+\"/messages\", bytes.NewReader(data))\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.setAuth(req)\n\tq := req.URL.Query()\n\tq.Set(\"message_id\", rctx.messageID)\n\treq.URL.RawQuery = q.Encode()\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := p.client.Do(req)\n\tif err != nil {","sourceCodeStart":482,"sourceCodeEnd":518,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/max/max.go#L482-L518","documentation":"After the type assertion succeeds, UpdateMessage checks that the replyContext carries a non-empty messageID, because editing is done via PUT /messages?message_id=<id>. This error means the reply context is a valid MAX context but was created without a message id — typically a context reconstructed from a session key (\"max:{chatID}\") where only the chatID is restored, so the platform doesn't know which message to edit.","triggerScenarios":"Calling UpdateMessage with a replyContext produced by ReconstructReplyCtx (which sets chatID but leaves messageID empty); a replyContext value-built by custom code without messageID; an inbound event whose Body.Mid was empty so handleMessage stored an empty id.","commonSituations":"Resuming a session after a daemon restart: the reply context is rebuilt from the persisted session key, losing the message id, and a later edit (e.g. streaming update or /history rewrite) targets it; custom integrations that persist and restore reply contexts without the message id field.","solutions":["Edit using a reply context captured from an actual MAX inbound message (handleMessage sets messageID from msg.Body.Mid) rather than a reconstructed one.","Persist the messageID alongside the session if you need to edit after a restart; reconstruct it yourself only if the API allows editing by a stored id.","Check whether the message you are trying to edit is one your bot sent in reply — MAX's edit API needs that message's id; obtain it from the send response instead of the inbound context.","Fall back to sending a new message (Reply) when no messageID is available, instead of editing."],"exampleFix":"// before\nrctx, _ := platform.ReconstructReplyCtx(\"max:\" + chatID) // messageID is empty\nerr := platform.UpdateMessage(ctx, rctx, newText) // fails: no message id\n\n// after\nif rctx, ok := liveReplyCtx.(replyContext); ok && rctx.messageID != \"\" {\n    err = platform.UpdateMessage(ctx, liveReplyCtx, newText)\n} else {\n    err = platform.Reply(ctx, liveReplyCtx, newText) // fallback: send new message\n}","handlingStrategy":"validation","validationCode":"// Called before UpdateMessage; messageID is unexported, so gate on origin:\nfunc canEdit(replyCtx any) bool {\n    rctx, ok := replyCtx.(replyContext)\n    return ok && rctx.messageID != \"\"\n}","typeGuard":"rctx, ok := replyCtx.(replyContext)\nif !ok || rctx.messageID == \"\" {\n    // cannot edit: reconstructed or id-less context — use Reply instead\n}","tryCatchPattern":"if err := platform.UpdateMessage(ctx, replyCtx, content); err != nil {\n    if strings.Contains(err.Error(), \"no message id in reply context\") {\n        return platform.Reply(ctx, replyCtx, content) // graceful degradation: new message\n    }\n    return err\n}","preventionTips":["Use reply contexts captured from live inbound messages for edits, not ReconstructReplyCtx output (which has an empty messageID).","Persist the bot's own sent-message id if you need to edit after a daemon restart.","Design edit flows to degrade gracefully to sending a fresh message when no id is known.","Log the session-key provenance of a reply context so you can tell reconstructed (chatID-only) from live ones."],"tags":["go","reply-context","message-editing","max-platform","missing-identifier"],"backgroundTag":"empty-required-field","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}