chenhg5/cc-connect · error

googlechat: read credentials_file: %w

Error message

googlechat: read credentials_file: %w

What it means

googlechat.New reads the service-account key file with os.ReadFile and wraps any OS-level failure as 'googlechat: read credentials_file: %w'. This fires after credentials_file is non-empty, so the path was provided but could not be read.

Source

Thrown at platform/googlechat/googlechat.go:101

func New(opts map[string]any) (core.Platform, error) {
	subscription, _ := opts["subscription"].(string)
	subscription = strings.TrimSpace(subscription)
	if subscription == "" {
		return nil, fmt.Errorf("googlechat: subscription is required (the Pub/Sub subscription your Chat app publishes to)")
	}
	projectID, err := projectFromSubscription(subscription)
	if err != nil {
		return nil, err
	}

	credentialsFile, _ := opts["credentials_file"].(string)
	credentialsFile = strings.TrimSpace(credentialsFile)
	if credentialsFile == "" {
		return nil, fmt.Errorf("googlechat: credentials_file is required (the Chat app's service-account key, used to pull events and send replies)")
	}
	keyBytes, err := os.ReadFile(credentialsFile)
	if err != nil {
		return nil, fmt.Errorf("googlechat: read credentials_file: %w", err)
	}
	conf, err := google.JWTConfigFromJSON(keyBytes,
		chatBotScope, "https://www.googleapis.com/auth/pubsub")
	if err != nil {
		return nil, fmt.Errorf("googlechat: parse service account credentials: %w", err)
	}
	botClient := conf.Client(context.Background())

	allowFrom, _ := opts["allow_from"].(string)

	core.CheckAllowFrom("googlechat", allowFrom)

	return &Platform{
		subscription:    subscription,
		projectID:       projectID,
		credentialsFile: credentialsFile,
		tokenSource:     conf.TokenSource(context.Background()),
		allowFrom:       allowFrom,

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check the wrapped cause: 'no such file or directory' → fix the path; 'permission denied' → chmod/chown so the cc-connect process user can read the key
  2. Use an absolute path in credentials_file instead of a relative one
  3. Under systemd/daemon, confirm the key is mounted/available before the service starts and the WorkingDirectory is not misleading
  4. Verify with ls -l <path> as the same user the daemon runs as

Example fix

// before
credentials_file = "sa.json"
// after
credentials_file = "/etc/cc-connect/sa.json"  # absolute, readable by daemon user
Defensive patterns

Strategy: validation

Validate before calling

// Go: check readability before New
if info, err := os.Stat(path); err != nil {
    return fmt.Errorf("credentials_file %q: %w", path, err)
} else if info.IsDir() {
    return fmt.Errorf("credentials_file %q is a directory", path)
}
// and confirm the daemon user can read it

Try / catch

p, err := core.CreatePlatform("googlechat", opts)
if err != nil && strings.Contains(err.Error(), "read credentials_file") {
    return fmt.Errorf("check that the service-account key path is absolute and readable by the cc-connect user: %w", err)
}

Prevention

When it happens

Trigger: googlechat.New: os.ReadFile(credentialsFile) returns an error — file does not exist, permission denied on file or parent directories, path is a directory, or an I/O error — inside the platform constructor at startup.

Common situations: Typo in the path; relative path resolved against a different working directory when running under systemd; file mounted/secret not yet mounted at startup; restrictive file permissions after copying the key with umask 077 as a different user.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/b5d890421b2ef97c. Report an issue: GitHub.