larksuite/cli · error

failed to read secret file %s: %w

Error message

failed to read secret file %s: %w

What it means

ResolveSecretInput resolves a SecretInput to the actual secret value. When the secret is a file reference, it reads the file via vfs.ReadFile and wraps any read failure with this error, including the file path and the underlying cause (missing file, permission denied, etc.).

Source

Thrown at internal/core/secret_resolve.go:30

)

const secretKeyPrefix = "appsecret:"

func secretAccountKey(appId string) string {
	return secretKeyPrefix + appId
}

// ResolveSecretInput resolves a SecretInput to a plain string.
// SecretRef objects are resolved by source (file / keychain).
func ResolveSecretInput(s SecretInput, kc keychain.KeychainAccess) (string, error) {
	if s.Ref == nil {
		return s.Plain, nil
	}
	switch s.Ref.Source {
	case "file":
		data, err := vfs.ReadFile(s.Ref.ID)
		if err != nil {
			return "", fmt.Errorf("failed to read secret file %s: %w", s.Ref.ID, err)
		}
		return strings.TrimSpace(string(data)), nil
	case "keychain":
		return kc.Get(keychain.LarkCliService, s.Ref.ID)
	default:
		return "", fmt.Errorf("unknown secret source: %s", s.Ref.Source)
	}
}

// ForStorage determines how to store a secret in config.json.
// - SecretRef → preserved as-is
// - Plain text → stored in keychain, returns keychain SecretRef
// Returns error if keychain is unavailable (no silent plaintext fallback).
func ForStorage(appId string, input SecretInput, kc keychain.KeychainAccess) (SecretInput, error) {
	if !input.IsPlain() {
		return input, nil // SecretRef → keep as-is
	}
	key := secretAccountKey(appId)

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Verify the file exists at the exact path in the id field (cat the path) and fix the path in config.json if it moved.
  2. Fix file permissions so the CLI user can read it (e.g. chmod 600 /path/to/secret).
  3. Store the secret in the OS keychain by re-running the CLI's config/login flow, switching to a keychain reference.
  4. As a temporary unblock, set the secret inline as a plain string, then move it back to a file reference.

Example fix

// before (config.json)
"appSecret": {"source": "file", "id": "/tmp/secret.txt"} // file deleted
// after
"appSecret": {"source": "file", "id": "/home/me/.lark/appsecret"} // file restored, chmod 600
Defensive patterns

Strategy: validation

Validate before calling

if ref := cfg.AppSecret.Ref; ref != nil && ref.Source == "file" {
	if _, err := os.Stat(ref.ID); err != nil {
		return fmt.Errorf("secret file %s unreadable: %w", ref.ID, err)
	}
}

Try / catch

secret, err := core.ResolveSecretInput(input, kc)
if err != nil {
	var pe *fs.PathError
	if errors.As(err, &pe) {
		return fmt.Errorf("fix or recreate secret file %s: %w", pe.Path, err)
	}
	return err
}

Prevention

When it happens

Trigger: Calling ResolveSecretInput (via authLogoutRun or ResolveConfigFromMulti) when the config appSecret is {"source":"file","id":"<path>"} and the referenced file cannot be read: missing, renamed, wrong permissions, or an unreadable/invalid path.

Common situations: Secret file deleted by cleanup scripts or tmp reapers; config copied to another machine where the absolute path does not exist; permissions changed so the CLI user cannot read it; containers where the host path was never mounted.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/4013b4f09fb64d37. Report an issue: GitHub.