t8y2/dbx · error

Oracle TNS_ADMIN must be a directory containing tnsnames.ora

Error message

Oracle TNS_ADMIN must be a directory containing tnsnames.ora: %s

What it means

After the TNS_ADMIN path stats successfully, oracleTNSNamesPath checks that it is actually a directory (info.IsDir). A file (e.g. someone set TNS_ADMIN to tnsnames.ora itself) fails this check with this message.

Source

Thrown at agents/drivers/oracle-go/tns.go:93

	aliases, err := readOracleTNSAliases(tnsNamesPath, make(map[string]bool), 0)
	if err != nil {
		return "", err
	}
	descriptor, ok := aliases[strings.ToUpper(config.Alias)]
	if !ok {
		return "", fmt.Errorf("Oracle TNS alias %q was not found in %s", config.Alias, tnsNamesPath)
	}
	return descriptor, nil
}

func oracleTNSNamesPath(tnsAdmin string) (string, error) {
	path := filepath.Clean(strings.TrimSpace(tnsAdmin))
	info, err := os.Stat(path)
	if err != nil {
		return "", fmt.Errorf("Oracle TNS_ADMIN directory is not accessible: %s", path)
	}
	if !info.IsDir() {
		return "", fmt.Errorf("Oracle TNS_ADMIN must be a directory containing tnsnames.ora: %s", path)
	}
	tnsNamesPath := filepath.Join(path, "tnsnames.ora")
	if info, err := os.Stat(tnsNamesPath); err != nil || info.IsDir() {
		return "", fmt.Errorf("Oracle tnsnames.ora was not found in TNS_ADMIN directory: %s", path)
	}
	return tnsNamesPath, nil
}

func readOracleTNSAliases(path string, visited map[string]bool, depth int) (map[string]string, error) {
	if depth > 8 {
		return nil, fmt.Errorf("Oracle TNS include depth exceeds 8 files")
	}
	absolutePath, err := filepath.Abs(path)
	if err != nil {
		return nil, fmt.Errorf("Failed to resolve Oracle TNS file path: %w", err)
	}
	if visited[absolutePath] {
		return map[string]string{}, nil

View on GitHub (pinned to c0390bff16)

Solutions

  1. Set TNS_ADMIN to the directory containing tnsnames.ora, not the file itself: /opt/oracle/network/admin not /opt/oracle/network/admin/tnsnames.ora.
  2. Verify with ls -ld that the path is a directory (d in permissions).
  3. Fix any config templating that appends 'tnsnames.ora' to the value.
  4. Check symlinks resolve to a directory (readlink -f <path>).

Example fix

// before
dsn := "jdbc:oracle:thin:@ALIAS?TNS_ADMIN=/opt/oracle/network/admin/tnsnames.ora" // file path
// after
dsn := "jdbc:oracle:thin:@ALIAS?TNS_ADMIN=/opt/oracle/network/admin"
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(tnsAdmin)
if err == nil && !info.IsDir() {
	return errors.New("TNS_ADMIN must be the directory containing tnsnames.ora, not the file itself")
}

Try / catch

if strings.Contains(err.Error(), "must be a directory") {
	// strip a trailing tnsnames.ora component and retry once
	dir := filepath.Dir(tnsAdmin)
}

Prevention

When it happens

Trigger: buildDSNForConnect -> resolveOracleTNSAlias -> oracleTNSNamesPath is given a TNS_ADMIN value pointing at a regular file rather than the directory that contains tnsnames.ora.

Common situations: Setting TNS_ADMIN to the full path of tnsnames.ora instead of its containing directory; a symlink resolving to a file; config tools substituting a file path variable where a directory was expected.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/54897847d6860578. Report an issue: GitHub.