GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — feat(credentials): record, show, and explain saved token expiry by waleedlatif1 · Pull Request #9930 · simstudioai/sim

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Nov 12, 2026

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)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

greptile-apps[bot] reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
waleedlatif1 added 11 commits November 11, 2026 19:23
@waleedlatif1
feat(credentials): record when a saved GitHub or GitLab token expires
3572f3a
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.
@waleedlatif1
feat(credentials): say a saved token expired when its provider refuse…
36cd897
…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.
@waleedlatif1
feat(integrations): show when a saved token expires
2442d2e
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.
@waleedlatif1
refactor(credentials): keep the expired saved-token error private to …
f0df3e5
…its module
@waleedlatif1
refactor(credentials): share the vendor token expiry readers with the…
271d348
… 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.
@waleedlatif1
feat(credentials): report a saved token's expiry on organization cred…
123daee
…entials

The organization credential presentation reports the same optional expiresAt
the workspace one does.
@waleedlatif1
fix(credentials): log an expired saved token's refusal as the provide…
b95575e
…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.
@waleedlatif1
fix(integrations): show a saved token's expiry time in UTC
96ef87c
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.
@waleedlatif1
fix(credentials): keep an imminent stated token expiry and read only …
f670711
…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.
@waleedlatif1
fix(credentials): say an expired saved token's date is UTC and keep i…
c901a86
…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.
@waleedlatif1
fix(credentials): read GitHub's UTC-marked token expiration header form
6fafaf9
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 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

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 12:16pm UTC

Request Review

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vercel
vercel Bot temporarily deployed to Preview November 12, 2026 11:49 Inactive
@greptile-apps

greptile-apps Bot commented Nov 12, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge, with a non-blocking countdown issue after reconnecting on an open page.

Findings

  1. P2 Replacement token countdown runs late ▶

Summary

Records vendor-reported expiry for saved GitHub and GitLab tokens and shows it in credential views.

  • Saved GitHub and GitLab tokens keep the expiry dates their providers report.
  • Credential screens show how long a saved token has before it expires.
  • Runs explain a saved token’s expiry after a provider returns 401.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Verify saved token] --> B[Read vendor expiry]
  B --> C[Store token and expiry together]
  C --> D[Credential list and detail]
  D --> E[Timed expiry caption]
  C --> F[Run tool]
  F --> G{Provider returns 401?}
  G -->|Yes, recorded expiry passed| H[Explain expiry and keep provider failure]
  G -->|Otherwise| I[Keep existing result]
Loading

Reviews (2) · Last reviewed commit: "fix(credentials): keep saved-token expir..." · Reviewed by Greptile

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

greptile-apps[bot]
greptile-apps Bot reviewed Nov 12, 2026
View reviewed changes
Comment thread apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx Resolved

Uh oh!

There was an error while loading. Please reload this page.

cubic-dev-ai[bot]
cubic-dev-ai Bot reviewed Nov 12, 2026
View reviewed changes

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

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

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

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread apps/sim/lib/credentials/saved-token-expiry.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread apps/sim/lib/credentials/gitlab-personal-token.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx Resolved

Uh oh!

There was an error while loading. Please reload this page.

@waleedlatif1
fix(credentials): keep saved-token expiry captions current and drop a…
900fdcb
… 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.
@vercel
vercel Bot temporarily deployed to Preview November 12, 2026 12:16 Inactive
@waleedlatif1

waleedlatif1 commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator Author

@greptile

greptile-apps[bot] reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@waleedlatif1

waleedlatif1 commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

cubic-dev-ai[bot] reacted with eyes emoji
All reactions
  • 👀 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

greptile-apps[bot]
greptile-apps Bot reviewed Nov 12, 2026
View reviewed changes
Comment thread apps/sim/app/workspace/[workspaceId]/integrations/hooks/use-saved-token-expiry-label.ts
Comment on lines +16 to +21
useEffect(() => {
const untilChange = msUntilSavedTokenExpiryLabelChanges(new Date(expiresAtMs), now)
if (untilChange === null) return
const timer = setTimeout(() => setNow(Date.now()), untilChange)
return () => clearTimeout(timer)
}, [expiresAtMs, now])

@greptile-apps greptile-apps Bot Nov 12, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

P2 Replacement token countdown runs late

useSavedTokenExpiryLabel keeps the old now when reconnecting changes expiresAt. If the page opened at noon and a reconnect at 18:00 supplies a token expiring at 19:00, the hook schedules its next update seven hours later instead of one hour later. The caption keeps claiming the token has time left after it expires. Read the current clock when expiresAt changes and calculate the timer from that fresh time.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
cubic-dev-ai[bot]
cubic-dev-ai Bot reviewed Nov 12, 2026
View reviewed changes

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

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

1 issue found across 24 files

Confidence score: 5/5

  • vendor-token-expiry.ts accepts invalid offsets like +2460, which can produce a shifted expiry timestamp. Validate the offset hours and minutes before converting.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/lib/credentials/vendor-token-expiry.ts">

<violation number="1" location="apps/sim/lib/credentials/vendor-token-expiry.ts:35">
P3: The GitHub parser accepts invalid numeric timezone offsets such as `+2460` and records the resulting shifted timestamp. Restrict the offset to valid hour/minute ranges before converting it, so malformed headers remain unset.</violation>
</file>

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

Turn on auto-fix | Re-trigger cubic

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread apps/sim/lib/credentials/vendor-token-expiry.ts
* the API responses GitHub's own client library records (octokit/octokit.rb spec cassettes), and
* any other form is left unread.
*/
const GITHUB_TOKEN_EXPIRATION = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}) ([+-]\d{4}|UTC)$/

@cubic-dev-ai cubic-dev-ai Bot Nov 12, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

P3: The GitHub parser accepts invalid numeric timezone offsets such as +2460 and records the resulting shifted timestamp. Restrict the offset to valid hour/minute ranges before converting it, so malformed headers remain unset.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/lib/credentials/vendor-token-expiry.ts, line 35:

<comment>The GitHub parser accepts invalid numeric timezone offsets such as `+2460` and records the resulting shifted timestamp. Restrict the offset to valid hour/minute ranges before converting it, so malformed headers remain unset.</comment>

<file context>
@@ -0,0 +1,71 @@
+ * the API responses GitHub's own client library records (octokit/octokit.rb spec cassettes), and
+ * any other form is left unread.
+ */
+const GITHUB_TOKEN_EXPIRATION = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}) ([+-]\d{4}|UTC)$/
+
+/**
</file context>
Fix with cubic

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

This branch was previously deployed

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

Reviewers

@greptile-apps greptile-apps[bot] greptile-apps[bot] left review comments
@cubic-dev-ai cubic-dev-ai[bot] cubic-dev-ai[bot] left review comments

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@waleedlatif1
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.