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
- Build the source through Config.CredentialsSource(helperPlugins) so the path comes from credentialsConfigFile()
- For tests use EmptyCredentialsSourceForTests(path) with an explicit temp path
- 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
- Always construct CredentialsSource via Config.CredentialsSource or EmptyCredentialsSourceForTests
- Never hand-initialize CredentialsSource literals outside package tests
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
- value cannot be treated as JSON string
- empty value
- value is not a JSON object, nor a JSON array
- invalid credentials configuration for OCI registries: %w
- invalid credentials configuration for OCI registries: %w
AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15).
Data as JSON: /api/errors/6ded996cce49e90f.
Report an issue: GitHub.