googleapis/mcp-toolbox · error

failed to update document: %w

Error message

failed to update document: %w

What it means

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.

Source

Thrown at internal/sources/firestore/firestore.go:358

	return response, nil
}

func (s *Source) UpdateDocument(ctx context.Context, documentPath string, updates []firestore.Update, documentData any, returnData bool) (map[string]any, error) {
	// Get the document reference
	docRef := s.FirestoreClient().Doc(documentPath)

	// Prepare update data
	var writeResult *firestore.WriteResult
	var writeErr error

	if len(updates) > 0 {
		writeResult, writeErr = docRef.Update(ctx, updates)
	} else {
		writeResult, writeErr = docRef.Set(ctx, documentData, firestore.MergeAll)
	}

	if writeErr != nil {
		return nil, fmt.Errorf("failed to update document: %w", writeErr)
	}

	// Build the response
	response := map[string]any{
		"documentPath": docRef.Path,
		"updateTime":   writeResult.UpdateTime.Format("2006-01-02T15:04:05.999999999Z"),
	}

	// Add document data if requested
	if returnData {
		// Fetch the updated document to return the current state
		snapshot, err := docRef.Get(ctx)
		if err != nil {
			return nil, fmt.Errorf("failed to retrieve updated document: %w", err)
		}
		// Convert the document data to simple JSON format
		simplifiedData := FirestoreValueToJSON(snapshot.Data())
		response["documentData"] = simplifiedData

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. If the cause is 'No document to update', create the document first or use the Set/MergeAll path (omit the updates field).
  2. Validate field paths: non-empty, no leading/trailing dots, no '..' segments.
  3. Check the wrapped cause for 'permission denied' and grant roles/datastore.user.
  4. Keep documents under 1 MiB; split large payloads across subcollections.
  5. Retry on UNAVAILABLE/DEADLINE_EXCEEDED with backoff — writes are atomic so retrying is safe.

Example fix

// before
source.UpdateDocument(ctx, "users/u999", nil, map[string]any{"age": 30}) // Update path, doc missing
// after
source.UpdateDocument(ctx, "users/u999", map[string]any{"age": 30}, nil) // Set + MergeAll upserts
Defensive patterns

Strategy: validation

Validate before calling

// Go
func validateUpdateTarget(client *firestore.Client, docPath string, updates map[string]any) error {
    if docPath == "" {
        return errors.New("document path is required")
    }
    for f := range updates {
        if f == "" || strings.HasPrefix(f, ".") || strings.HasSuffix(f, ".") || strings.Contains(f, "..") {
            return fmt.Errorf("invalid field path %q", f)
        }
    }
    if _, err := client.Doc(docPath).Get(context.Background()); status.Code(err) == codes.NotFound && len(updates) > 0 {
        return fmt.Errorf("document %q does not exist; Update() requires it, use Set path", docPath)
    }
    return nil
}

Type guard

func isValidFieldPath(f string) bool {
    return f != "" && !strings.HasPrefix(f, ".") && !strings.HasSuffix(f, ".") && !strings.Contains(f, "..")
}

Try / catch

resp, err := source.UpdateDocument(ctx, docPath, updates, nil)
if err != nil {
    if strings.Contains(err.Error(), "No document to update") {
        resp, err = source.UpdateDocument(ctx, docPath, nil, data)
    }
    if err != nil {
        return fmt.Errorf("update failed: %w", err)
    }
}

Prevention

When it happens

Trigger: 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, '..').

Common situations: 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.

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/ad789468729867b5. Report an issue: GitHub.