{"record":{"id":"135658a213d890dd","repo":"Tencent/WeKnora","slug":"failed-to-delete-file-from-obs-w","errorCode":null,"errorMessage":"failed to delete file from OBS: %w","messagePattern":"failed to delete file from OBS: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/application/service/file/obs.go","lineNumber":219,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get file from OBS: %w\", err)\n\t}\n\n\treturn output.Body, nil\n}\n\nfunc (s *obsFileService) DeleteFile(ctx context.Context, filePath string) error {\n\tobjectKey, err := s.parseObsFilePath(filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = s.client.DeleteObject(ctx, &s3.DeleteObjectInput{\n\t\tBucket: aws.String(s.bucketName),\n\t\tKey:    aws.String(objectKey),\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete file from OBS: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (s *obsFileService) GetFileURL(ctx context.Context, filePath string) (string, error) {\n\tif strings.HasPrefix(filePath, \"http://\") || strings.HasPrefix(filePath, \"https://\") {\n\t\treturn filePath, nil\n\t}\n\n\tobjectKey, err := s.parseObsFilePath(filePath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif s.proxyDomain != \"\" {\n\t\treturn s.proxyDomain + \"/\" + strings.TrimPrefix(objectKey, \"/\"), nil\n\t}","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/file/obs.go#L201-L237","documentation":"DeleteFile wraps the error from s3 DeleteObject when the OBS delete call fails. Note S3-compatible DeleteObject often returns success even for a missing key, so this error usually reflects transport/permission problems rather than the object not existing.","triggerScenarios":"Calling DeleteFile when the client cannot reach OBS (network/DNS), credentials lack s3:DeleteObject permission, or the bucket/region configuration is wrong.","commonSituations":"Read-only IAM policy applied to the deletion job's credentials; network partition between app and OBS; wrong bucket name after config change so the delete goes to a non-existent bucket.","solutions":["Unwrap and classify: AccessDenied → fix IAM permissions; network errors → retry with backoff","Verify the credentials used by the OBS service include delete permission","Confirm bucketName/endpoint config matches the bucket that owns the file","If delete is part of a saga/cleanup, log the wrapped error and mark the record for later garbage collection instead of failing the whole transaction"],"exampleFix":"// before\nif err != nil { return fmt.Errorf(\"failed to delete file from OBS: %w\", err) }\n// after\nif err != nil {\n    if isRetryableNetErr(err) {\n        return retryBackoff(func() error { return s.DeleteFile(ctx, filePath) }, 3)\n    }\n    return fmt.Errorf(\"failed to delete file from OBS: %w\", err)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := svc.DeleteFile(ctx, p)\nif err != nil {\n    if isRetryableNetErr(err) {\n        err = retryBackoff(3, func() error { return svc.DeleteFile(ctx, p) })\n    }\n    if err != nil {\n        log.Warn(\"obs delete deferred\", \"path\", p, \"err\", err) // mark for GC\n    }\n}","preventionTips":["Grant s3:DeleteObject to the service credentials","For user-initiated deletes, never fail the business transaction solely on the storage delete — queue for GC","Monitor delete error rates to catch permission regressions early"],"tags":["obs","s3","delete","permissions"],"backgroundTag":"object-storage-delete-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}