opentofu/opentofu · error

unable to determine credentials file path: %w

Error message

unable to determine credentials file path: %w

What it means

updateLocalHostCredentials (the writer behind storing/forgetting credentials, e.g. during tofu login) first asks s.CredentialsFilePath() for the target file. CredentialsFilePath just returns the path captured when the CredentialsSource was constructed; it only errors when the source was built without a resolvable path. In normal use Config.CredentialsSource already guarantees a path, so this is a defensive guard.

Source

Thrown at internal/command/cliconfig/credentials.go:331

		// Delegate entirely to the helper, then.
		if new == nil {
			return s.helper.ForgetForHost(ctx, host)
		}
		return s.helper.StoreForHost(ctx, host, new)
	default:
		// Should never happen because the above cases are exhaustive
		return fmt.Errorf("invalid credentials location %#v", loc)
	}
}

func (s *CredentialsSource) updateLocalHostCredentials(host svchost.Hostname, new svcauth.NewHostCredentials) error {
	// This function updates the local credentials file in particular,
	// regardless of whether a credentials helper is active. It should be
	// called only indirectly via updateHostCredentials.

	filename, err := s.CredentialsFilePath()
	if err != nil {
		return fmt.Errorf("unable to determine credentials file path: %w", err)
	}

	oldSrc, err := os.ReadFile(filename)
	if err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("cannot read %s: %w", filename, err)
	}

	var raw map[string]interface{}

	if len(oldSrc) > 0 {
		// When decoding we use a custom decoder so we can decode any numbers as
		// json.Number and thus avoid losing any accuracy in our round-trip.
		dec := json.NewDecoder(bytes.NewReader(oldSrc))
		dec.UseNumber()
		err = dec.Decode(&raw)
		if err != nil {
			return fmt.Errorf("cannot read %s: %w", filename, err)
		}

View on GitHub (pinned to 3561785c48)

Solutions

  1. Build the source through Config.CredentialsSource(helperPlugins) so the path comes from credentialsConfigFile()
  2. For tests use EmptyCredentialsSourceForTests(path) with an explicit temp path
  3. Check CredentialsFilePath() returns no error before attempting login/logout flows

Example fix

// before
src := &CredentialsSource{}
err := src.StoreForHost(...)  // unable to determine credentials file path

// after
src, err := cfg.CredentialsSource(helperPlugins)
// or, in tests:
src := EmptyCredentialsSourceForTests(filepath.Join(t.TempDir(), "credentials.tfrc.json"))
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := src.CredentialsFilePath(); err != nil {
	// source built without a path; rebuild it via cfg.CredentialsSource
}

Try / catch

func (s *store) save(host svchost.Hostname, creds svcauth.NewHostCredentials) (err error) {
	defer func() {
		if err != nil && strings.Contains(err.Error(), "unable to determine credentials file path") {
			err = fmt.Errorf("credentials source misconfigured (no file path): %w", err)
		}
	}()
	return s.src.updateHostCredentials(context.Background(), host, creds)
}

Prevention

When it happens

Trigger: Constructing a CredentialsSource directly (e.g. a zero-valued or test-built struct) instead of via Config.CredentialsSource or EmptyCredentialsSourceForTests, then calling an operation that persists credentials.

Common situations: Code (typically tests or forks) that hand-assembles CredentialsSource{} without setting credentialsFilePath and then triggers a store/forget of host credentials.

Related errors


AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15). Data as JSON: /api/errors/6ded996cce49e90f. Report an issue: GitHub.