{"record":{"id":"4e237c475d800698","repo":"larksuite/cli","slug":"keychain-unavailable-w-hint-use-file-reference","errorCode":null,"errorMessage":"keychain unavailable: %w\nhint: use file: reference in config to bypass keychain","messagePattern":"keychain unavailable: %w\nhint: use file: reference in config to bypass keychain","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/core/secret_resolve.go","lineNumber":50,"sourceCode":"\t\treturn strings.TrimSpace(string(data)), nil\n\tcase \"keychain\":\n\t\treturn kc.Get(keychain.LarkCliService, s.Ref.ID)\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unknown secret source: %s\", s.Ref.Source)\n\t}\n}\n\n// ForStorage determines how to store a secret in config.json.\n// - SecretRef → preserved as-is\n// - Plain text → stored in keychain, returns keychain SecretRef\n// Returns error if keychain is unavailable (no silent plaintext fallback).\nfunc ForStorage(appId string, input SecretInput, kc keychain.KeychainAccess) (SecretInput, error) {\n\tif !input.IsPlain() {\n\t\treturn input, nil // SecretRef → keep as-is\n\t}\n\tkey := secretAccountKey(appId)\n\tif err := kc.Set(keychain.LarkCliService, key, input.Plain); err != nil {\n\t\treturn SecretInput{}, fmt.Errorf(\"keychain unavailable: %w\\nhint: use file: reference in config to bypass keychain\", err)\n\t}\n\treturn SecretInput{Ref: &SecretRef{Source: \"keychain\", ID: key}}, nil\n}\n\n// ValidateSecretKeyMatch checks that the appSecret keychain key references the\n// expected appId. This prevents silent mismatches when config.json is edited by\n// hand (e.g. appId changed but appSecret.id still points to the old app).\n// Only applicable when appSecret is a keychain SecretRef; other forms are skipped.\nfunc ValidateSecretKeyMatch(appId string, secret SecretInput) error {\n\tif secret.Ref == nil || secret.Ref.Source != \"keychain\" {\n\t\treturn nil\n\t}\n\texpected := secretAccountKey(appId)\n\tif secret.Ref.ID != expected {\n\t\treturn fmt.Errorf(\n\t\t\t\"appSecret keychain key %q does not match appId %q (expected %q); %s\",\n\t\t\tsecret.Ref.ID, appId, expected, reconfigureHint(),\n\t\t)","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/core/secret_resolve.go#L32-L68","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Install/start a keyring service (e.g. gnome-keyring) and unlock it, then retry the command.","Run the command in a desktop session or environment where a secret service is available over D-Bus.","In SSH/CI setups, unlock the keyring and export DBUS_SESSION_BUS_ADDRESS before running the CLI."],"exampleFix":"# before: plain secret requires keychain\nlark config init --app-secret s3cr3t\n# after: file reference bypasses keychain\necho -n 's3cr3t' > ~/.lark/appsecret && chmod 600 ~/.lark/appsecret\n# then in config.json: \"appSecret\": {\"source\": \"file\", \"id\": \"/home/me/.lark/appsecret\"}","handlingStrategy":"fallback","validationCode":"// probe keychain availability before passing a plain secret\nif err := kc.Set(\"lark-cli-probe\", \"lark-cli-probe\", \"ok\"); err != nil {\n\t// keychain unavailable: use a file reference instead of a plain secret\n}","typeGuard":null,"tryCatchPattern":"stored, err := core.ForStorage(appID, input, kc)\nif err != nil {\n\tif strings.Contains(err.Error(), \"keychain unavailable\") {\n\t\t// fallback: write secret to a 0600 file and use {source: file, id: path}\n\t}\n\treturn err\n}","preventionTips":["In headless/CI environments, always use file secret references instead of plain appSecret values.","Ensure gnome-keyring/kwallet (Linux) or Keychain access (macOS) is installed, running, and unlocked.","When using SSH, unlock the keyring and export DBUS_SESSION_BUS_ADDRESS before auth commands.","Follow the error's built-in hint: switch config to a file reference to bypass the keychain."],"tags":["keychain","secret","config","environment"],"backgroundTag":"keychain-unavailable","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}