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

  1. Read the wrapped gRPC/ttrpc error code to distinguish transient (Unavailable, DeadlineExceeded) from permanent failures
  2. Retry the commit; if the ingest was lost, recreate the writer, rewrite data, and commit again
  3. Check that the expected digest and size match the actual content to avoid remote-side validation failures
  4. 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

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


AI-assisted analysis of containerd/containerd@4246446a2b (2026-09-02). Data as JSON: /api/errors/205472581e8a5ca7. Report an issue: GitHub.