{"record":{"id":"ad789468729867b5","repo":"googleapis/mcp-toolbox","slug":"failed-to-update-document-w","errorCode":null,"errorMessage":"failed to update document: %w","messagePattern":"failed to update document: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sources/firestore/firestore.go","lineNumber":358,"sourceCode":"\treturn response, nil\n}\n\nfunc (s *Source) UpdateDocument(ctx context.Context, documentPath string, updates []firestore.Update, documentData any, returnData bool) (map[string]any, error) {\n\t// Get the document reference\n\tdocRef := s.FirestoreClient().Doc(documentPath)\n\n\t// Prepare update data\n\tvar writeResult *firestore.WriteResult\n\tvar writeErr error\n\n\tif len(updates) > 0 {\n\t\twriteResult, writeErr = docRef.Update(ctx, updates)\n\t} else {\n\t\twriteResult, writeErr = docRef.Set(ctx, documentData, firestore.MergeAll)\n\t}\n\n\tif writeErr != nil {\n\t\treturn nil, fmt.Errorf(\"failed to update document: %w\", writeErr)\n\t}\n\n\t// Build the response\n\tresponse := map[string]any{\n\t\t\"documentPath\": docRef.Path,\n\t\t\"updateTime\":   writeResult.UpdateTime.Format(\"2006-01-02T15:04:05.999999999Z\"),\n\t}\n\n\t// Add document data if requested\n\tif returnData {\n\t\t// Fetch the updated document to return the current state\n\t\tsnapshot, err := docRef.Get(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to retrieve updated document: %w\", err)\n\t\t}\n\t\t// Convert the document data to simple JSON format\n\t\tsimplifiedData := FirestoreValueToJSON(snapshot.Data())\n\t\tresponse[\"documentData\"] = simplifiedData","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/sources/firestore/firestore.go#L340-L376","documentation":"Thrown by Source.UpdateDocument when either docRef.Update(ctx, updates) (partial update path) or docRef.Set(ctx, documentData, firestore.MergeAll) (merge-set path) fails. The write error is wrapped so the gRPC cause is preserved. It means the document was not modified — Firestore writes are atomic, so no partial update was applied.","triggerScenarios":"Calling UpdateDocument on a non-existent document path via the Update path (fails with NotFound), payload exceeding 1 MiB, no write permission, or an invalid field path in updates (empty string, leading/trailing dots, '..').","commonSituations":"Updating a non-existent document with 'updates' mode (must use Set for upsert semantics), rotated service account lacking write IAM, invalid field-path strings like '.name' or 'a..b', transient gRPC outages or quota exhaustion.","solutions":["If the cause is 'No document to update', create the document first or use the Set/MergeAll path (omit the updates field).","Validate field paths: non-empty, no leading/trailing dots, no '..' segments.","Check the wrapped cause for 'permission denied' and grant roles/datastore.user.","Keep documents under 1 MiB; split large payloads across subcollections.","Retry on UNAVAILABLE/DEADLINE_EXCEEDED with backoff — writes are atomic so retrying is safe."],"exampleFix":"// before\nsource.UpdateDocument(ctx, \"users/u999\", nil, map[string]any{\"age\": 30}) // Update path, doc missing\n// after\nsource.UpdateDocument(ctx, \"users/u999\", map[string]any{\"age\": 30}, nil) // Set + MergeAll upserts","handlingStrategy":"validation","validationCode":"// Go\nfunc validateUpdateTarget(client *firestore.Client, docPath string, updates map[string]any) error {\n    if docPath == \"\" {\n        return errors.New(\"document path is required\")\n    }\n    for f := range updates {\n        if f == \"\" || strings.HasPrefix(f, \".\") || strings.HasSuffix(f, \".\") || strings.Contains(f, \"..\") {\n            return fmt.Errorf(\"invalid field path %q\", f)\n        }\n    }\n    if _, err := client.Doc(docPath).Get(context.Background()); status.Code(err) == codes.NotFound && len(updates) > 0 {\n        return fmt.Errorf(\"document %q does not exist; Update() requires it, use Set path\", docPath)\n    }\n    return nil\n}","typeGuard":"func isValidFieldPath(f string) bool {\n    return f != \"\" && !strings.HasPrefix(f, \".\") && !strings.HasSuffix(f, \".\") && !strings.Contains(f, \"..\")\n}","tryCatchPattern":"resp, err := source.UpdateDocument(ctx, docPath, updates, nil)\nif err != nil {\n    if strings.Contains(err.Error(), \"No document to update\") {\n        resp, err = source.UpdateDocument(ctx, docPath, nil, data)\n    }\n    if err != nil {\n        return fmt.Errorf(\"update failed: %w\", err)\n    }\n}","preventionTips":["Use the Set/MergeAll path when the document may not exist; reserve Update for existing docs.","Sanitize field paths (no empty/dotted segments) before updating.","Keep documents under 1 MiB.","Retry UNAVAILABLE/DEADLINE_EXCEEDED — updates are atomic and idempotent to re-apply."],"tags":["firestore","update","grpc","gcp"],"backgroundTag":"firestore-write-failed","analyzedSha":"8cc6e09de2ad7b8bffc77751799585a1401a48eb","analyzedAt":"2026-09-05T01:10:36.887Z","contentChangedAt":"2026-09-05T01:10:36.887Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}