Compare commits

...

1 Commits

Author SHA1 Message Date
Molecule AI Dev Engineer B (MiniMax) 1e1790c98b fix(observability): enrich server log on CommitMemory plugin error
The POST /workspaces/:id/memories handler returns a generic
HTTP 500 'failed to store memory' when the underlying v2
memory plugin's CommitMemory call errors. The current
log.Printf('Commit memory error (plugin): %v', err) emits
only the error — operators have no workspace, scope, or
namespace context to diagnose recurring main incidents
(continuous-synth E2E + HMA memory-commit both currently
fail with this 500; the backend error is swallowed).

Fix: enrich the server-side log line with workspaceID, the
requested scope, the resolved v2 namespace, a structured
err_class=<type> field (for log-aggregator filtering),
and the quoted err text (preserves trailing whitespace /
special chars that %v would munge).

Hard constraint (same discipline as the #2392 leak fix):
the underlying err stays server-log-only. The HTTP
response body is UNCHANGED — still 500 'failed to store
memory' with no plugin error leaked to the client.

No behavior change to the write path itself. The change
is one log.Printf line + a 9-line comment explaining the
no-leak discipline. The new log line is:

  log.Printf('Commit memory plugin error: workspace=%s scope=%s namespace=%s err_class=%T err=%q',
      workspaceID, body.Scope, nsName, err, err)

Unblocks operator diagnosis of the memory-v2 backend
without changing the client surface or weakening the
server's error-disclosure posture.
2026-06-08 03:27:25 +00:00
+20 -18
View File
@@ -226,25 +226,27 @@ func (h *MemoriesHandler) Commit(c *gin.Context) {
Source: contract.MemorySourceUser,
})
if err != nil {
// The underlying plugin error must NOT leak to the HTTP response body
// (generic 500 keeps client surface stable). Emit full operator context
// (workspace, scope, namespace, error type + message) server-side so
// recurring incidents (continuous-synth E2E, HMA memory-commit, etc.)
// can be distinguished in the log aggregator.
log.Printf(
"Commit memory plugin error: workspace=%s scope=%s namespace=%s err_class=%T err=%q",
workspaceID, body.Scope, nsName, err, err,
)
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to store memory"})
return
}
memoryID := resp.ID
// Server-side log ONLY. The client response below is the generic
// 500 — the underlying plugin error must NOT leak to the HTTP
// response body (clients have no business seeing the memory
// plugin's internal error class, message, or stack; the same
// discipline as the #2392 leak fix). We include enough context
// here for an operator to diagnose the failure from the server
// log: workspace id, requested scope, the resolved v2 namespace,
// the concrete Go error type (for log-aggregator filtering via
// `err_class=...`), and the quoted error message (preserves
// trailing whitespace / special chars that %v would munge).
log.Printf(
"Commit memory plugin error: workspace=%s scope=%s namespace=%s err_class=%T err=%q",
workspaceID, body.Scope, nsName, err, err,
)
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to store memory"})
return
}
memoryID := resp.ID
// #767 Audit: write a GLOBAL memory audit log entry for forensic replay.
// Records a SHA-256 hash of the content — never plaintext — so the audit
// trail can prove what was written without leaking sensitive values.
// Failure is non-fatal: a logging error must not roll back a successful write.
if body.Scope == "GLOBAL" {
// #767 Audit: write a GLOBAL memory audit log entry for forensic replay.
// Records a SHA-256 hash of the content — never plaintext — so the audit
// Hash the sanitised content so the audit trail reflects what was
// actually persisted (not the raw, potentially secret-bearing input).
sum := sha256.Sum256([]byte(content))