Fix "me" resolution: stop conflating identity IDs with person IDs#202
Fix "me" resolution: stop conflating identity IDs with person IDs#202
Conversation
ResolvePerson("me") was returning the Launchpad identity ID instead of
the account-scoped person ID. These are different ID spaces — the
identity ID is cross-account (from /authorization.json) while the person
ID is per-account (from /people.json). The identity ID never matches any
person in the account, so every API call using the resolved ID 404'd.
|
Closed since resolving |
There was a problem hiding this comment.
Pull request overview
Fixes incorrect "me" person resolution by joining the stored Launchpad identity to an account person via email, and updates related commands to store/use that email so person-scoped endpoints stop 404’ing.
Changes:
- Persist
UserEmailin stored credentials and addGetUserEmail/SetUserIdentity(id, email)APIs. - Update
"me"resolution to match stored email against the account people list (and switch people/projects fetching to paginatedGetAll). - Route
timeline watch methroughResolvePerson("me")and update login/wizard/profile/me flows to store email.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/names/resolver.go | Email-based "me" resolution; switch people/projects fetching to GetAll pagination. |
| internal/commands/wizard.go | Store user identity + email after fetching /my/profile.json. |
| internal/commands/timeline.go | Use ResolvePerson("me") instead of GetUserID() for personal timeline. |
| internal/commands/profile.go | Store user identity + email after profile creation login flow. |
| internal/commands/people.go | basecamp me stores identity ID + email via SetUserIdentity. |
| internal/commands/auth.go | auth login stores identity ID + email via SetUserIdentity. |
| internal/auth/keyring.go | Add UserEmail to Credentials for persistence. |
| internal/auth/auth_test.go | Add test coverage for SetUserIdentity. |
| internal/auth/auth.go | Implement GetUserEmail() and SetUserIdentity(userID, email). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
`basecamp me` fetches from /authorization.json which returns a cross-account identity ID, not an account-scoped person ID. Storing this identity ID as UserID causes 404s when later used with account-scoped endpoints. Add SetUserEmail to auth.Manager and use it in `basecamp me` so only the email is stored — the identity ID is no longer written to credentials.
The fallback returned a stored identity ID (cross-account) when email matching failed. Since all consumers need account-scoped person IDs, returning a wrong-namespace ID silently is worse than an error.
After the previous two commits, these have zero callers. Replace their tests with TestSetUserEmail which verifies the new method stores email without touching UserID.
Test the full "me" resolution matrix: - Email matches a person in the account → returns person ID - Case-insensitive email matching works - Email stored but not in people list, with identity ID in UserID → returns error, NOT the identity ID (the regression Jorge identified) - No email stored at all → returns auth error - Email-only stored (no UserID) → resolves correctly The key regression test (IdentityID_NotLeaked) fails when the old GetUserID fallback is present, proving the fix is load-bearing. Also adds SetStore to auth.Manager as a test seam for cross-package tests that need to inject a file-backed store.
There was a problem hiding this comment.
No issues found across 10 files
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Add one-off context when rerunning by tagging
@cubic-dev-aiwith guidance or docs links (includingllms.txt) - Ask questions if you need clarification on any suggestion
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address PR review feedback:
- ResolvePerson("me") now returns the getPeople() error instead of
swallowing it and returning a misleading auth error
- Check store.Save errors in test setup with require.NoError
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/names/resolver.go">
<violation number="1" location="internal/names/resolver.go:148">
P2: Misleading error message when email exists but doesn't match any person in the account. After the restructuring, this `return` is only reached when the user has a stored email but it doesn't match anyone in the current account's people list. Telling them to `basecamp auth login` won't help — the issue is account membership, not authentication. Consider a more specific message like `"Your email (%s) was not found in this account's people list. You may not be a member of this account."` or at minimum differentiate it from the empty-email case above.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
When ResolvePerson("me") fails, distinguish between no stored email
(suggests re-login) and email not found in the account (suggests
checking account selection).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Using make() instead of var ensures the cache sentinel (nil check) recognizes an empty result as populated, preventing re-fetches on every call when an account has zero projects or people.
Problem
ResolvePerson("me")resolved to the Launchpad identity ID (cross-account, from/authorization.json) instead of the account-scoped person ID (from/people.json). These are different ID spaces — using an identity ID on account-scoped endpoints like/people/{id}or/reports/todos/assigned/{id}produces 404s.The bug had two parts:
basecamp mestored the identity ID asUserIDviaSetUserIdentityResolvePerson("me")fell back to this stored identity ID when email matching failed, silently returning the wrong-namespace IDFix
basecamp menow callsSetUserEmailinstead ofSetUserIdentity, storing only the email (the identity ID is never written to credentials)ResolvePerson("me")matches the stored email against the account people list — the identity email and person email are the same, making email a reliable join keyGetUserIDfallback is removed — returning a wrong-namespace ID silently is worse than an errorGetUserID/SetUserIDmethods removed (zero production callers after the fix)Other callsites (
auth login,wizard,profile) already store the correct person ID viaSetUserIdentityfrom/my/profile.json— those are unchanged.Test plan
TestResolverResolvePerson_Me_IdentityID_NotLeaked— regression test: identity ID stored as UserID + email not in people list → returns error, NOT the identity ID. Verified this test fails when the old fallback is restored.TestResolverResolvePerson_Me_MatchesByEmail— email matches person → returns person IDTestResolverResolvePerson_Me_CaseInsensitiveEmail— case-insensitive matching worksTestResolverResolvePerson_Me_NoEmail— no email stored → auth errorTestResolverResolvePerson_Me_EmailOnly_NoUserID— new happy path (email only, no UserID)TestSetUserEmail— stores email without touching UserIDmake checkpasses (fmt, vet, lint, test, test-e2e)Files changed
internal/commands/people.gobasecamp me:SetUserIdentity→SetUserEmailinternal/names/resolver.goGetUserIDfallback; surfacegetPeopleerrors; differentiate error messagesinternal/auth/auth.goSetUserEmail,SetStore; removeGetUserID,SetUserIDinternal/auth/auth_test.goTestGetUserID/TestSetUserIDwithTestSetUserEmail; check Save errorsinternal/names/resolver_test.go