fix: derive provider_tokens_set from local git provider cache#123
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
all-hands-bot
left a comment
There was a problem hiding this comment.
🟢 Good taste - Elegant, pragmatic solution
This PR fixes a real production bug where GitHub integrations stopped working after PR #98. The solution correctly derives provider_tokens_set from localStorage, which is already the source of truth for git provider tokens. The implementation is simple, well-tested, and doesn't introduce any risks.
[RISK ASSESSMENT]
This is a straightforward bug fix that:
- Restores broken functionality without changing APIs
- Reuses existing localStorage data (already populated by SecretsService)
- Includes proper test coverage
- Has no agent behavior or eval/benchmark impact
VERDICT:
✅ Worth merging - Clean fix with no issues
KEY INSIGHT:
Deriving provider_tokens_set from localStorage is the right call - it's already the authoritative source for git provider tokens after successful server saves, and this approach avoids adding unnecessary backend API calls.
Description
After saving a GitHub Personal Access Token on
/settings/integrations, the UI never enters the "GitHub configured" state and direct GitHub API calls (e.g.getUser, suggested tasks, repo/branch pickers) never fire — even though the token is persisted on both the agent server and inlocalStorage.Steps to reproduce
http://localhost:3001.http://localhost:3001/settings/integrations.Expected behavior
/settings/integrationsshows the configured-token UI (host displayed, token masked, Disconnect Tokens enabled).api.github.comusing the saved token.Actual behavior
PUT /api/settings/secretsreturns200).workspace/.openhands/secrets.json) and locally (localStorage["openhands-agent-server-git-provider-tokens"]).Root cause
PR #98 (commit
7fe650d) migrated git provider tokens off the legacyPATCH /api/settings { provider_tokens_set }flow and onto the agent server'sPUT /api/settings/secretsendpoint. Two halves of that migration were left undone:SettingsResponsemodel never returns aprovider_tokens_setfield — by design (see comment inopenhands/agent_server/persistence/models.py:262-265: the agent server tracks only custom secrets and does not own provider tokens).settings.provider_tokens_set.<provider>everywhere —useUserProviders(src/hooks/use-user-providers.ts:8-11),routes/git-settings.tsx:62-75,git-control-bar-repo-button.tsx:30,git-control-bar-branch-button.tsx:24. With the field permanently absent from the response,useUserProviders().providersalways returns[], gating off every downstream GitHub interaction.Out of scope (separate follow-up)
The agent runtime cloning code (
software-agent-sdk/openhands-workspace/openhands/workspace/cloud/repo.py:40-45) looks for canonical secret names (github_token,gitlab_token, …) while the GUI savesGIT_PROVIDER_GITHUB_TOKEN. Server-sidegit clonewill still fail to authenticate even after this fix lands. Track separatelySummary
/settings/integrationsand unblocks every downstream GitHub API call in the GUI (repo picker, branch picker, suggested tasks).settings.provider_tokens_setfrom the existinglocalStoragecache thatSecretsService.addGitProvideralready populates after each successful server save — no new HTTP calls, no backend changes.Why
PR #98 moved git provider tokens onto the agent-server
PUT /api/settings/secretsendpoint, but the agent-server'sSettingsResponsedoes not return aprovider_tokens_setfield (intentional — seeopenhands/agent_server/persistence/models.py:262-265). The GUI still readssettings.provider_tokens_set.<provider>fromuseUserProviders,routes/git-settings.tsx, and the chat git-control-bar buttons. With the field permanently absent,useUserProviders().providers === []and every GitHub-gated code path remained inert even though the token had been saved on both the server and in localStorage.What changed
src/api/settings-service/settings-service.api.ts—syncDerivedSettingsnow foldsgetStoredGitProviders()intoprovider_tokens_set. The map is re-derived on every call (including in-memory cache hits), so the existing React-Query invalidation inuseAddGitProviders.onSuccessimmediately surfaces the configured state to all consumers.Why localStorage and not
GET /api/settings/secretsSecretsService.addGitProviderwrites server-side first; localStorage is only updated on success (src/api/secrets-service.ts:303-315). So localStorage presence is already a reliable signal that the secret is on the server.hostalongsidetoken; the secrets list endpoint returns onlyname/description, which would force fragile description parsing.getStoredGitProviderToken(src/api/git-providers/provider-handler.ts:64-68), keeping both consumers aligned on a single source of truth.