When WorkOS answers a refresh with a 4xx (invalid_grant on a consumed or revoked token), the token file is left untouched. Every subsequent hook, MCP server and app read finds the same expired token, takes the profile lock, re-sends it, and gets another fast 400 until kcap login. The daemon backs off for an hour on Rejected, but the hooks do not, so a dead credential produces a 400 per hook invocation (visible in the daemon log of 2026-09-12 21:55 onward).
Under the profile lock, a Rejected outcome should retire the stored refresh token (for example clear refresh_token, or mark the file) so GetValidTokensForProfileAsync reports Expired without a network call, kcap status says the login must be repeated, and no process presents the refused token again. Login already overwrites the file.
Split out of #946 / PR #947, which added in-window replays but deliberately left the persisted token shape alone.
When WorkOS answers a refresh with a 4xx (
invalid_granton a consumed or revoked token), the token file is left untouched. Every subsequent hook, MCP server and app read finds the same expired token, takes the profile lock, re-sends it, and gets another fast 400 untilkcap login. The daemon backs off for an hour on Rejected, but the hooks do not, so a dead credential produces a 400 per hook invocation (visible in the daemon log of 2026-09-12 21:55 onward).Under the profile lock, a Rejected outcome should retire the stored refresh token (for example clear
refresh_token, or mark the file) soGetValidTokensForProfileAsyncreports Expired without a network call,kcap statussays the login must be repeated, and no process presents the refused token again. Login already overwrites the file.Split out of #946 / PR #947, which added in-window replays but deliberately left the persisted token shape alone.