router-for-me/CLIProxyAPI · warning

failed to merge metadata: %w

Error message

failed to merge metadata: %w

What it means

misc.MergeMetadata failed while merging the storage struct with its injected Metadata map during save. MergeMetadata marshals the token storage to JSON to combine top-level fields with metadata, so failure means json.Marshal hit an unmarshalable value — almost impossible for the well-defined CodexTokenStorage fields, and only realistic if Metadata contains unsupported types (chan, func, complex) or a value with a broken MarshalJSON.

Source

Thrown at internal/auth/codex/token.go:67

// data in JSON format to the specified file path for persistent storage.
// It merges any injected metadata into the top-level JSON object.
//
// Parameters:
//   - authFilePath: The full path where the token file should be saved
//
// Returns:
//   - error: An error if the operation fails, nil otherwise
func (ts *CodexTokenStorage) SaveTokenToFile(authFilePath string) error {
	misc.LogSavingCredentials(authFilePath)
	ts.Type = "codex"
	if err := os.MkdirAll(filepath.Dir(authFilePath), 0700); err != nil {
		return fmt.Errorf("failed to create directory: %v", err)
	}

	// Merge metadata using helper
	data, errMerge := misc.MergeMetadata(ts, ts.Metadata)
	if errMerge != nil {
		return fmt.Errorf("failed to merge metadata: %w", errMerge)
	}

	f, err := os.Create(authFilePath)
	if err != nil {
		return fmt.Errorf("failed to create token file: %w", err)
	}
	defer func() {
		if errClose := f.Close(); errClose != nil {
			log.Errorf("codex token storage: close token file error: %v", errClose)
		}
	}()

	if err = json.NewEncoder(f).Encode(data); err != nil {
		return fmt.Errorf("failed to write token to file: %w", err)
	}
	return nil
}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Inspect ts.Metadata for non-JSON-serializable values (funcs, channels, unsupported floats) before saving.
  2. Pre-validate metadata yourself: `_, err := json.Marshal(meta)` and fix or drop offending entries.
  3. Keep Metadata as map[string]string or plain JSON types.
  4. If a custom type is needed, give it a correct MarshalJSON implementation.

Example fix

// before
ts.Metadata = map[string]any{"done": make(chan struct{})}

// after
ts.Metadata = map[string]any{"source": "cli", "login_at": time.Now().Format(time.RFC3339)}
Defensive patterns

Strategy: validation

Validate before calling

// Validate metadata is JSON-safe before saving
if ts.Metadata != nil {
    if _, err := json.Marshal(ts.Metadata); err != nil {
        return fmt.Errorf("metadata not serializable: %w", err)
    }
}

Try / catch

if err := ts.SaveTokenToFile(path); err != nil {
    if strings.Contains(err.Error(), "failed to merge metadata") {
        // strip non-JSON metadata and retry the save
    }
}

Prevention

When it happens

Trigger: Caller set Metadata containing func/chan/map with non-string keys before SaveTokenToFile; a custom metadata type whose MarshalJSON errors; NaN/Inf float values in metadata.

Common situations: Integrations injecting rich metadata objects into the token storage; almost never occurs via the standard login flow, whose Metadata stays string-keyed and JSON-safe.

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/6e5efb319149dab8. Report an issue: GitHub.