containerd/containerd · error
commit failed: %w
Error message
commit failed: %w
What it means
remoteWriter.Commit sends a WriteAction_COMMIT request through the content proxy. Transport-level failures of that RPC are converted to native errors and wrapped as 'commit failed'. This happens after the data has been written, when finalizing the blob ingest on the remote side.
Source
Thrown at core/content/proxy/content_writer.go:130
err = err1
}
}()
var base content.Info
for _, opt := range opts {
if err := opt(&base); err != nil {
return err
}
}
resp, err := rw.send(&contentapi.WriteContentRequest{
Action: contentapi.WriteAction_COMMIT,
Total: size,
Offset: rw.offset,
Expected: expected.String(),
Labels: base.Labels,
})
if err != nil {
return fmt.Errorf("commit failed: %w", errgrpc.ToNative(err))
}
if size != 0 && resp.Offset != size {
return fmt.Errorf("unexpected size: %v != %v", resp.Offset, size)
}
actual := digest.Digest(resp.Digest)
if expected != "" && actual != expected {
return fmt.Errorf("unexpected digest: %v != %v", resp.Digest, expected)
}
rw.digest = actual
rw.offset = resp.Offset
return nil
}
func (rw *remoteWriter) Truncate(size int64) error {
// This truncation won't actually be validated until a write is issued.View on GitHub (pinned to 4246446a2b)
Solutions
- Read the wrapped gRPC/ttrpc error code to distinguish transient (Unavailable, DeadlineExceeded) from permanent failures
- Retry the commit; if the ingest was lost, recreate the writer, rewrite data, and commit again
- Check that the expected digest and size match the actual content to avoid remote-side validation failures
- Increase context timeout for large blobs so commit is not cut off
Example fix
// before: short-lived ctx
err := rw.Commit(ctx, size, expectedDigest)
// after: dedicated timeout + one retry
ctx2, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
if err := rw.Commit(ctx2, size, expectedDigest); err != nil {
if status.Code(err) == codes.Unavailable {
return rw.Commit(ctx2, size, expectedDigest)
}
return err
} Defensive patterns
Strategy: retry
Validate before calling
st, err := rw.Status()
if err != nil { return err }
if size != 0 && st.Offset != size {
return fmt.Errorf("aborting commit: written %d of %d", st.Offset, size)
} Try / catch
err := rw.Commit(ctx, size, expected)
if err != nil {
if status.Code(err) == codes.Unavailable || status.Code(err) == codes.DeadlineExceeded {
ctx2, cancel := context.WithTimeout(context.Background(), commitTimeout)
defer cancel()
err = rw.Commit(ctx2, size, expected)
}
}
return err Prevention
- Use a fresh, long-lived context for commit
- Commit exactly once per writer; concurrent commits cause RPC conflicts
- Handle ErrAlreadyExists from duplicate commits as success when content matches
When it happens
Trigger: Calling Commit on a proxied content writer when the commit RPC fails — daemon shutdown between write and commit, context timeout, ingest expired/collected remotely, or the ref was already committed by another process.
Common situations: Push finalized after the containerd daemon restarted; slow networks causing context deadline during commit; concurrent writers using the same ingest ref; expected digest mismatches reported by the remote as RPC errors.
Related errors
- error getting writer status: %w
- failed to send write: %w
- NotImplemented
- unsupported content client %T: %w
- unable to make connection: %w
AI-assisted analysis of containerd/containerd@4246446a2b (2026-09-02).
Data as JSON: /api/errors/205472581e8a5ca7.
Report an issue: GitHub.