flipped-aurora/gin-vue-admin · warning

unknown error

Error message

unknown error

What it means

Within DeleteFiles, per-key failures returned in the DeleteObjects response (out.Errors) are converted into DeleteFailure entries. When the service omits the Message pointer (nil), the code substitutes the placeholder 'unknown error'. This is not a transport failure — the batch succeeded but some individual keys could not be deleted and R2 did not explain why.

Source

Thrown at server/utils/upload/cloudflare_r2.go:145

		Delete: &types.Delete{
			Objects: objects,
			Quiet:   aws.Bool(true),
		},
	})
	if err != nil {
		return nil, errors.New("function client.DeleteObjects() failed, err:" + err.Error())
	}

	for _, e := range out.Errors {
		key := ""
		if e.Key != nil {
			key = *e.Key
		}
		msg := "unknown error"
		if e.Message != nil {
			msg = *e.Message
		}
		failed = append(failed, DeleteFailure{Key: key, Err: errors.New(msg)})
	}
	return failed, nil
}

// ListFiles 按前缀列举存储对象,cursor 映射到 ContinuationToken。
func (c *CloudflareR2) ListFiles(ctx context.Context, prefix, cursor string, limit int) (files []FileInfo, nextCursor string, hasMore bool, err error) {
	client, err := c.newR2Client()
	if err != nil {
		return nil, "", false, err
	}
	bucket := global.GVA_CONFIG.CloudflareR2.Bucket

	if limit <= 0 {
		limit = 100
	}

	input := &s3.ListObjectsV2Input{
		Bucket:  aws.String(bucket),

View on GitHub (pinned to 3136500ef3)

Solutions

  1. Treat the DeleteFailure entry by Key: re-attempt a single DeleteObject for that key to obtain a concrete error
  2. Log the raw DeleteObjects response Errors array to see Code/Key even when Message is nil
  3. Sanitize key names (no leading '/', control chars, over-length) before batch delete
  4. Check for object lock/retention settings on the bucket blocking deletion
  5. Report to Cloudflare support if R2 consistently returns empty error messages for valid keys

Example fix

// before
msg := "unknown error"
if e.Message != nil { msg = *e.Message }
// after
msg := "unknown error"
if e.Message != nil { msg = *e.Message }
if e.Code != nil { msg = *e.Code + ": " + msg }
Defensive patterns

Strategy: type-guard

Validate before calling

// before batch delete, confirm each key exists and is deletable
for _, k := range keys {
    if _, err := uploader.Exists(ctx, k); err != nil {
        return fmt.Errorf("cannot verify key %s: %w", k, err)
    }
}

Type guard

func describeFailure(e types.Error) string {
    var key, msg, code string
    if e.Key != nil { key = *e.Key }
    if e.Message != nil { msg = *e.Message } else { msg = "unknown error" }
    if e.Code != nil { code = *e.Code }
    return fmt.Sprintf("key=%s code=%s msg=%s", key, code, msg)
}

Try / catch

failed, err := uploader.DeleteFiles(ctx, keys)
if err != nil { return err }
for _, f := range failed {
    logger.Warn("per-key delete failure", zap.String("key", f.Key), zap.String("detail", f.Err.Error()))
    // re-attempt single delete to obtain a concrete error
    if derr := uploader.DeleteFile(ctx, f.Key); derr != nil {
        logger.Error("single delete also failed", zap.String("key", f.Key), zap.Error(derr))
    }
}

Prevention

When it happens

Trigger: DeleteObjects batch partially fails and R2 returns an error entry with a nil Message and/or nil Key — typically for keys rejected at a lower level (invalid key encoding, lock/retention conflicts) where the S3-compatible layer doesn't populate the message field.

Common situations: Keys containing characters R2 rejects, objects under retention/legal hold, keys that were listed but vanished mid-batch with an unpopulated error entry, S3-compatible layer differences between AWS and R2 response population.

Related errors


AI-assisted analysis of flipped-aurora/gin-vue-admin@3136500ef3 (2026-08-31). Data as JSON: /api/errors/12533d30df12528b. Report an issue: GitHub.