{"record":{"id":"ca420778eba7840e","repo":"gastownhall/beads","slug":"write-commit-result-indeterminate-after-connection","errorCode":null,"errorMessage":"write commit result indeterminate after connection loss (not retried to avoid double-apply): %w","messagePattern":"write commit result indeterminate after connection loss \\(not retried to avoid double-apply\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/storage/dolt/store.go","lineNumber":1158,"sourceCode":"\t\tif err == nil {\n\t\t\tif !circuitWriteManaged(ctx) && s.breaker != nil {\n\t\t\t\ts.breaker.RecordSuccess()\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\t// Dolt's exact 1105 autocommit rollback proves the transaction did not\n\t\t// land. This is the only 1105 replayed, and withRetryTx is the boundary\n\t\t// that recreates the complete SQL transaction on every attempt.\n\t\tif isDoltAutocommitRollbackError(err) {\n\t\t\tdoltMetrics.serializationErrors.Add(ctx, 1)\n\t\t\tdoltMetrics.writeRetries.Add(ctx, 1, metric.WithAttributes(attribute.String(\"type\", \"serialization\")))\n\t\t\treturn err\n\t\t}\n\t\t// A commit result marked indeterminate may have landed before its\n\t\t// response was lost. Never replay the callback in that case.\n\t\tif errors.Is(err, ErrCommitIndeterminate) {\n\t\t\terr = s.recordDoltPublicationFailure(ctx, err)\n\t\t\treturn backoff.Permanent(fmt.Errorf(\"write commit result indeterminate after connection loss (not retried to avoid double-apply): %w\", err))\n\t\t}\n\t\t// Serialization failures (1213/1205) guarantee a server-side rollback,\n\t\t// so the write never landed — safe to replay at any phase.\n\t\tif isSerializationError(err) {\n\t\t\tdoltMetrics.serializationErrors.Add(ctx, 1)\n\t\t\tdoltMetrics.writeRetries.Add(ctx, 1, metric.WithAttributes(attribute.String(\"type\", \"serialization\")))\n\t\t\treturn err // retryable\n\t\t}\n\t\t// Connection failures reaching this branch happened before commit;\n\t\t// withWriteTx marks ambiguous commit response loss with the public\n\t\t// ErrCommitIndeterminate sentinel above.\n\t\tif isRetryableError(err) {\n\t\t\tdoltMetrics.writeRetries.Add(ctx, 1, metric.WithAttributes(attribute.String(\"type\", \"connection\")))\n\t\t\tif s.breaker != nil && isConnectionError(err) {\n\t\t\t\ts.breaker.RecordFailure()\n\t\t\t\tif s.breaker.State() == circuitOpen {\n\t\t\t\t\tdoltMetrics.circuitTrips.Add(ctx, 1)\n\t\t\t\t\treturn backoff.Permanent(fmt.Errorf(\"%w (circuit breaker tripped)\", err))","sourceCodeStart":1140,"sourceCodeEnd":1176,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/store.go#L1140-L1176","documentation":"A write transaction's commit produced the ErrCommitIndeterminate sentinel: the connection was lost at commit time, so it is unknown whether the commit landed server-side. The retry loop deliberately converts this to a permanent error and never replays the callback, because replaying could double-apply the write. Before returning, the failure is recorded via recordDoltPublicationFailure for observability/recovery.","triggerScenarios":"tx.Commit() inside withRetryTx/withWriteTx returned a connection-class error (connection lost during commit response), the code classified it as indeterminate, and the retry wrapper stopped all retries and returned this message with the recorded failure wrapped inside.","commonSituations":"Dolt sql-server restarted exactly at commit time; network partition during a write; client read/write timeout firing while the server was still committing; proxy or load balancer dropping the connection mid-response.","solutions":["Do NOT blindly re-run the write — first inspect the data (or the recorded publication failure) to determine whether the commit actually landed.","Check the publication-failure record created by recordDoltPublicationFailure and reconcile: compare expected vs actual row state before re-applying.","Make the write idempotent (deterministic keys/upserts) so a safe re-apply is possible next time.","Investigate the root cause of the connection loss at commit time (server logs, network stability, timeout configuration)."],"exampleFix":"// before: naive retry can double-apply\nfor {\n    if err := applyWrite(ctx); err == nil { break }\n}\n// after: check whether the write landed before re-applying\nerr := applyWrite(ctx)\nif errors.Is(err, storage.ErrCommitIndeterminate) {\n    if !writeAlreadyApplied(tx) { // verify server-side state first\n        _ = applyWrite(ctx)\n    }\n}","handlingStrategy":"try-catch","validationCode":"// keep writes idempotent so indeterminate outcomes are reconcilable\n// use deterministic primary keys / upserts instead of blind inserts\nconst upsert = \"INSERT INTO issues (id, title) VALUES (?, ?) ON DUPLICATE KEY UPDATE title = VALUES(title)\"","typeGuard":"func isIndeterminate(err error) bool { return errors.Is(err, storage.ErrCommitIndeterminate) }","tryCatchPattern":"err := store.Update(ctx, op)\nif errors.Is(err, storage.ErrCommitIndeterminate) {\n    // check server-side state before ever re-applying\n    if !op.AlreadyApplied(ctx, store) {\n        err = store.Update(ctx, op)\n    }\n}","preventionTips":["Never retry a write after ErrCommitIndeterminate without first verifying server-side state.","Design writes to be idempotent (deterministic IDs, upsert semantics).","Harden the network path to the Dolt server (avoid flaky proxies, tune timeouts).","Treat every occurrence as an incident: check recordDoltPublicationFailure output for what may have landed."],"tags":["dolt","commit","indeterminate","connection-loss","data-integrity"],"backgroundTag":"commit-result-indeterminate","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}