Skip to content

feat(credentials): record, show, and explain saved token expiry - #9930

Merged
waleedlatif1 merged 13 commits into
stagingfrom
feat/credential-expiry-and-slack-rotation
Nov 12, 2026
Merged

waleedlatif1 merged 13 commits into
stagingfrom
feat/credential-expiry-and-slack-rotation

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • When a GitHub or GitLab saved token is verified, Sim now records its expiry. The value goes on the existing credential.access_token_expires_at column. An expiry that has already passed contradicts the vendor accepting the token, so it is dropped. An imminent one is kept.
    • GitLab sends expires_at from /personal_access_tokens/self, and the token stops working at midnight UTC on that date (GitLab docs).
    • GitHub sends the GitHub-Authentication-Token-Expiration header (changelog). GitHub does not document the header's value format. The reader accepts the two forms seen in the API responses that GitHub's own octokit.rb records: a numeric offset (2023-04-19 16:34:22 -0700) and a UTC marker (2022-09-20 23:45:13 UTC). It leaves any other form unread. In that case the token is still accepted, just with no expiry recorded.
  • Reconnecting replaces the stored expiry. A replacement token with no expiry clears it.
  • The workspace and organization credential presentations report an optional expiresAt.
  • The integrations list shows "expires in N days" or "expired". The credential detail page shows the expiry date and time in UTC.
  • When a provider sends a 401 for a saved token whose recorded expiry has passed, the run says the token expired on <date> UTC and asks for a new one. The provider's status and body are kept, and the refusal stays attributed to the provider in logs. A token is never refused on the recorded expiry alone.
  • The GitLab personal-token path reads expires_at with the same strict parser.
  • Not included:
    • Slack token rotation. Slack refresh tokens are single-use and the old one is revoked after an unquantified grace period (Slack docs). Rotation needs a new provider id with durable, compare-and-swap rotating-chain storage, plus a live proof. The design is written up separately. The existing xoxe. refusal stays.
    • Atlassian expiry. API tokens get a 1–365 day expiry when they are created, but Atlassian documents no way for a token to read its own expiry (Atlassian docs).
    • Infisical expiry. The TTL is returned only by the login that issues the token, and a pasted token has no documented self-lookup (Infisical token auth).
    • Expiry notifications. No credential notification system exists yet.

Deploy notes

  • No schema change or migration.
  • During the rolling deploy, an old worker that handles a reconnect of a GitHub/GitLab saved token updates only the secret. If a new worker had already recorded an expiry on that row, the old token's expiry stays until the next reconnect on new code, and the list and run error may show that stale date. Hitting this needs a create and a reconnect of the same credential inside the rollout window. Nothing is refused because of it.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Other: ___________

Testing

  • Unit tests (written red first) cover:
    • the GitHub and GitLab expiry parsers, including imminent expiries, both GitHub header forms, and other zone names being left unread
    • the expiry caption
    • the expired-token explanation in executeTool, for external requests and internal operations
  • A real-Postgres integration test, saved-token-expiry.integration.ts, covers create, list, reconnect replace/clear, org-owned credentials, and the expired-token message.
  • Revert proofs turned the attribution and org-presentation tests red.
  • What to review closely: the compatibility story. No schema change, no change to stored secret shapes, and the contract field is optional. Credentials saved before this have NULL expiry and behave exactly as before.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

GitHub reports a personal access token's expiry in the
GitHub-Authentication-Token-Expiration response header and GitLab in the
expires_at of /personal_access_tokens/self. The token check factory gains an
expiresAt reader, keeps only a credible moment (a real calendar time at least
an hour ahead, since the vendor just accepted the token), and the create and
reconnect paths store it on credential.access_token_expires_at, the column a
personal token already uses for the same meaning. A replacement token that
states no expiry clears the previous one. The workspace credential contract
reports it as an optional expiresAt.
…s it

When a provider answers a saved service-account token with a 401 and the
credential records a vendor-stated expiry that has passed, the run now reports
that the token expired on that date and asks for a new one, instead of the
bare authorization failure. The explanation keeps the provider's status,
statusText, and body, and classifies as user-actionable like a revoked grant.
A token is never refused on the recorded expiry alone: only a refusal the
provider already made is explained, for both external requests and internal
operations.
A connected credential whose saved token has a recorded expiry shows
"expires in N days" or "expired" in the integrations list, the same muted
caption API keys use, and a Token expiry section with the date on its detail
page.
… GitLab personal token

The GitHub and GitLab expiry readers move to one module so the GitLab
personal token path reads expires_at the same strict way: a date that does
not exist is recorded as no expiry instead of rolling over into another day.
…entials

The organization credential presentation reports the same optional expiresAt
the workspace one does.
…r's refusal

The expired-token explanation is a user failure rather than a revoked grant,
so it never triggers the revoked-grant handling, and it inherits the
refusal's failure classification, logged mark, and external response body so
the failure is still attributed to the provider and logged once.
GitHub states the expiry to the second, so the detail page shows the date and
time in UTC rather than a date that can fall on the wrong day locally.
…GitHub's sourced header form

Only an expiry that has already passed contradicts the vendor's acceptance; one that is minutes
away is real and is now recorded. GitHub does not document the expiration header's value format,
so the reader accepts only the numeric-offset form GitHub's own client library records and leaves
any other form unread.
…t the provider's refusal

The expired-token error extended UserFailure, but the boundary always carries the provider
refusal's attribution onto it, so the base did nothing and its docs contradicted the logged
classification. It is now a plain Error, and the run message names the date as UTC.
GitHub's octokit.rb spec cassettes record the GitHub-Authentication-Token-Expiration
header both with a numeric offset and as 'YYYY-MM-DD HH:MM:SS UTC'. Accept the
UTC-marked form as offset zero so those tokens get their expiry recorded; every
other zone name stays unread.
@vercel

vercel Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Nov 12, 2026 1:27pm UTC

Request Review

@greptile-apps

greptile-apps Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues remain.

Summary

Records vendor-reported token expiry, shows it in credential views, and explains provider refusals after a saved token expires.

  • Saved GitHub and GitLab tokens remember the expiry their provider reports.
  • Credential views show when a saved token expires.
  • A provider’s 401 can tell people when their saved token expired.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Verify saved token] --> B[Read vendor expiry]
  B --> C[Save token and optional expiry]
  C --> D[Show expiry and countdown]
  E[Provider returns 401] --> F[Check saved expiry]
  C --> F
  F --> G[Explain expiry while keeping provider error]
Loading

Reviews (3) · Last reviewed commit: "fix(credentials): time a reconnected tok..." · Reviewed by Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 22 files

Reply with feedback, questions, or to request a fix.

Turn on auto-fix | Re-trigger cubic

… past GitLab personal-token expiry

The integrations list and detail captions re-read the clock when the day
count drops or the token expires, instead of only on render. A GitLab
personal token's stated expiry that is already past is dropped, the same
check the service-account validators apply, now shared as
credibleTokenExpiry.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 24 files

Reply with feedback, questions, or to request a fix.

Turn on auto-fix | Re-trigger cubic

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 24 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Turn on auto-fix | Re-trigger cubic

@waleedlatif1
waleedlatif1 merged commit 0d77c23 into staging Nov 12, 2026
80 checks passed

This branch was previously deployed

1 inactive deployment
Preview — f4c00bce Deployed Nov 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant