{"record":{"id":"86d8196fe1f278be","repo":"larksuite/cli","slug":"appsecret-keychain-key-q-does-not-match-appid-q","errorCode":null,"errorMessage":"appSecret keychain key %q does not match appId %q (expected %q); %s","messagePattern":"appSecret keychain key %q does not match appId %q \\(expected %q\\); (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/core/secret_resolve.go","lineNumber":65,"sourceCode":"\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)\n\t}\n\treturn nil\n}\n\n// RemoveSecretStore cleans up keychain entries when an app is removed.\n// Errors are intentionally ignored — cleanup is best-effort.\nfunc RemoveSecretStore(input SecretInput, kc keychain.KeychainAccess) {\n\tif input.IsSecretRef() && input.Ref.Source == \"keychain\" {\n\t\t_ = kc.Remove(keychain.LarkCliService, input.Ref.ID)\n\t}\n}\n","sourceCodeStart":47,"sourceCodeEnd":80,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/core/secret_resolve.go#L47-L80","documentation":"ValidateSecretKeyMatch guards against a config.json whose appSecret is stored as a keychain SecretRef whose ID does not correspond to the configured appId. The expected key is \"appsecret:<appId>\" (secretAccountKey); when the stored key differs, the CLI refuses to proceed because the keychain entry most likely holds the secret of a different (or previously renamed) app, and silently using it would authenticate against the wrong tenant/app. The message includes a reconfigure hint telling the user how to fix it.","triggerScenarios":"A config.json is hand-edited (e.g. appId changed or copied from another profile) while the appSecret entry still reads {\"source\":\"keychain\",\"id\":\"appsecret:<oldAppId>\"}; validation runs during ResolveConfigFromMulti and finds secret.Ref.ID != \"appsecret:\"+appId.","commonSituations":"Switching to a different Lark app in config.json by hand; copying another machine's config.json while keychain entries were minted under a different appId; running `config init` for one app then manually editing appId; multiple profiles where the keychain entry was created under the wrong account.","solutions":["Run the CLI's reconfigure flow (see the hint in the message) so the secret is re-stored under the correct key `appsecret:<appId>`, e.g. re-run `lark-cli config init` with the right app credentials","If the appSecret is actually stored elsewhere, switch the appSecret entry to a file reference: {\"source\":\"file\",\"id\":\"/path/to/secret\"}, which is not subject to this key match check","Manually re-key: store the correct secret in the keychain under the expected key and update appSecret.id in config.json to exactly \"appsecret:<appId>\""],"exampleFix":"// before (config.json)\n\"appId\": \"cli_a2f8...\",\n\"appSecret\": {\"source\": \"keychain\", \"id\": \"appsecret:cli_b1x9...\"}\n// after\n\"appId\": \"cli_a2f8...\",\n\"appSecret\": {\"source\": \"keychain\", \"id\": \"appsecret:cli_a2f8...\"}","handlingStrategy":"validation","validationCode":"func checkSecretKeyMatch(appId string, secret core.SecretInput) error {\n\treturn core.ValidateSecretKeyMatch(appId, secret) // nil when OK\n}\n// or, before writing config:\nexpected := \"appsecret:\" + appId\nif secret.Ref != nil && secret.Ref.Source == \"keychain\" && secret.Ref.ID != expected {\n\treturn fmt.Errorf(\"re-run config init for appId %s (keychain key is %s)\", appId, secret.Ref.ID)\n}","typeGuard":"func isKeychainRefForApp(s core.SecretInput, appId string) bool {\n\treturn s.Ref != nil && s.Ref.Source == \"keychain\" && s.Ref.ID == \"appsecret:\"+appId\n}","tryCatchPattern":null,"preventionTips":["Always change apps via `config init` rather than hand-editing config.json","If hand-editing, update appSecret.id to \"appsecret:<newAppId>\" together with appId and re-store the secret in the keychain","Prefer file references for secrets when sharing configs across machines"],"tags":["config","keychain","validation"],"backgroundTag":"keychain-secret-key-mismatch","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"}