larksuite/cli · error

keychain unavailable: %w hint: use file: reference in config

Error message

keychain unavailable: %w
hint: use file: reference in config to bypass keychain

What it means

ForStorage stores a plain-text secret into the OS keychain and returns a keychain SecretRef for config.json. This error means the keychain Set operation failed (no keyring service, headless environment, locked keyring, access denied, etc.), and it includes a hint to use a file reference in config to bypass the keychain entirely.

Source

Thrown at internal/core/secret_resolve.go:50

		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)
	if err := kc.Set(keychain.LarkCliService, key, input.Plain); err != nil {
		return SecretInput{}, fmt.Errorf("keychain unavailable: %w\nhint: use file: reference in config to bypass keychain", err)
	}
	return SecretInput{Ref: &SecretRef{Source: "keychain", ID: key}}, nil
}

// ValidateSecretKeyMatch checks that the appSecret keychain key references the
// expected appId. This prevents silent mismatches when config.json is edited by
// hand (e.g. appId changed but appSecret.id still points to the old app).
// Only applicable when appSecret is a keychain SecretRef; other forms are skipped.
func ValidateSecretKeyMatch(appId string, secret SecretInput) error {
	if secret.Ref == nil || secret.Ref.Source != "keychain" {
		return nil
	}
	expected := secretAccountKey(appId)
	if secret.Ref.ID != expected {
		return fmt.Errorf(
			"appSecret keychain key %q does not match appId %q (expected %q); %s",
			secret.Ref.ID, appId, expected, reconfigureHint(),
		)

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Follow the error hint: store the secret in a file and reference it in config.json as {"source":"file","id":"<path>"} instead of a plain string.
  2. Install/start a keyring service (e.g. gnome-keyring) and unlock it, then retry the command.
  3. Run the command in a desktop session or environment where a secret service is available over D-Bus.
  4. In SSH/CI setups, unlock the keyring and export DBUS_SESSION_BUS_ADDRESS before running the CLI.

Example fix

# before: plain secret requires keychain
lark config init --app-secret s3cr3t
# after: file reference bypasses keychain
echo -n 's3cr3t' > ~/.lark/appsecret && chmod 600 ~/.lark/appsecret
# then in config.json: "appSecret": {"source": "file", "id": "/home/me/.lark/appsecret"}
Defensive patterns

Strategy: fallback

Validate before calling

// probe keychain availability before passing a plain secret
if err := kc.Set("lark-cli-probe", "lark-cli-probe", "ok"); err != nil {
	// keychain unavailable: use a file reference instead of a plain secret
}

Try / catch

stored, err := core.ForStorage(appID, input, kc)
if err != nil {
	if strings.Contains(err.Error(), "keychain unavailable") {
		// fallback: write secret to a 0600 file and use {source: file, id: path}
	}
	return err
}

Prevention

When it happens

Trigger: Calling ForStorage (via Build, configInitRun, profileAddRun) with a plain (non-ref) appSecret while kc.Set fails - e.g. no keyring daemon, SSH session without an unlocked keyring, unsupported platform, or keychain access denied.

Common situations: Running config init or auth login on a headless Linux server without gnome-keyring/kwallet; CI containers with no secret service on D-Bus; macOS Keychain denying access; WSL without keyring integration.

Related errors


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