Skip to content

Zendesk OAuth token goes stale: switch to client_credentials mint-and-cache #99

Closed

Description

@zamanafzal

Description/Context

AI_ZENDESK_OAUTH_TOKEN is a static access token read from config. Zendesk expires OAuth access tokens in well under a day (30 minutes by default, expires_in is settable at mint time), so the value goes stale on its own. When it does, file_support_ticket gets a 401, the learner is told to email support directly, and the only trace is a log line. Confirmed locally on an expired token:

Failed to create Zendesk support ticket (HTTPStatusError, status 401)

Introduced by #98, where it is listed as a known gap. Inert while AI_ZENDESK_STUB_MODE=True, which is the default, so this blocks talking to a real Zendesk rather than blocking that PR.

Refresh tokens are not the fix. client_credentials is Zendesk's documented grant for server-to-server automation with no user available to approve access, and it deliberately issues no refresh token — you mint a new access token instead. API tokens aren't an alternative either: Zendesk is removing them in phases through 2027-04-30, and accounts created on or after 2026-07-28 can't create them at all.

Note the attribution caveat from Zendesk's docs: actions performed with a client credentials token are attributed to the Zendesk user who created the OAuth client. If attribution matters, the OAuth client should be owned by a dedicated service account.

Plan/Design

  • Replace AI_ZENDESK_OAUTH_TOKEN with AI_ZENDESK_OAUTH_CLIENT_ID / AI_ZENDESK_OAUTH_CLIENT_SECRET.
  • Mint on demand against https://{subdomain}.zendesk.com/oauth/tokens with grant_type=client_credentials and scope=tickets:write users:write, cache the token until shortly before expires_in elapses, and re-mint on a 401.
  • Give the failure an operator-visible signal rather than a log line only — the current behavior is a silent per-request failure that looks identical to the bot deciding not to file.

Activity

  1. zamanafzal commented on Oct 7, 2026

    @zamanafzal
    ContributorAuthor

    Gap is already written up in the Additional Context section of #98, which is still a draft. No value in tracking them separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions