Skip to content

fix(mobile): auto-refresh on PostHog 401/403 auth failures#2408

Open
Gilbert09 wants to merge 2 commits into
mainfrom
posthog-code/mobile-auto-refresh-401
Open

fix(mobile): auto-refresh on PostHog 401/403 auth failures#2408
Gilbert09 wants to merge 2 commits into
mainfrom
posthog-code/mobile-auto-refresh-401

Conversation

@Gilbert09
Copy link
Copy Markdown
Member

Summary

  • Ports the desktop fix from fix(auth): auto-refresh on PostHog 401/403 auth failures #2186 to the React Native mobile app.
  • Adds authedFetch(url, init) in apps/mobile/src/lib/api.ts that transparently refreshes the OAuth token and retries once when a PostHog request returns 401, or 403 with an authentication-error body (code: "authentication_failed" / type: "authentication_error"). If refresh fails, the original response is returned so callers stay on their existing error / sign-out paths.
  • Threads authedFetch through every PostHog request site (tasks, inbox, mcp, skills, push tokens, useUserQuery, useProjectsQuery). A module-level in-flight promise dedups concurrent 401s so a stampede only triggers one refresh.

Scope notes

  • streamCloudTask (the SSE endpoint) keeps its raw fetch — it has its own resumption flow via Last-Event-ID / abort signal and can't simply be replayed with a fresh token after the body has started streaming.
  • The mobile MCP transport (features/mcp/service.ts) bakes the token into the transport at construction time. That's the mobile analogue of the desktop MCP proxy fix, but rebuilding the long-lived Client on auth failure is meaningfully more invasive than the API layer change and is left for a follow-up.

Test plan

  • pnpm --filter @posthog/mobile test — 115 tests pass (including the new src/lib/api.test.ts, which mirrors apps/code/src/renderer/api/fetcher.test.ts plus a dedup test for concurrent 401s).
  • pnpm --filter @posthog/mobile lint clean.
  • tsc --noEmit clean for the touched files (pre-existing unrelated errors in *.test.tsx component renderer tests remain).
  • Smoke test on a build: expire the access token, kick off a task action, confirm the request retries silently instead of bubbling a hard 401.

Mirror the desktop fix from #2186 on mobile. PostHog API
requests now transparently refresh the OAuth token and retry once on
a 401 — or a 403 whose body is shaped like an authentication error
(`code: "authentication_failed"` / `type: "authentication_error"`).
If the refresh itself fails, the original response is returned so
callers stay on their existing error/sign-out paths.

Introduces `authedFetch(url, init)` in `apps/mobile/src/lib/api.ts`
and threads it through every PostHog request site (`tasks`, `inbox`,
`mcp`, `skills`, push tokens, user/projects queries). A module-level
in-flight promise dedups concurrent 401s so a stampede only triggers
a single refresh. The SSE stream endpoint (`streamCloudTask`) keeps
its raw `fetch` — it has its own resumption flow via `Last-Event-ID`
and can't be replayed with a fresh token.

Adds `apps/mobile/src/lib/api.test.ts` mirroring the desktop fetcher
tests, plus dedup coverage for the stampede case.

Generated-By: PostHog Code
Task-Id: bece83a6-7305-4a9d-b7c3-c5cb62ee3e49
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 28, 2026

Comments Outside Diff (1)

  1. apps/mobile/src/lib/api.test.ts, line 858-898 (link)

    P2 Prefer parameterised tests for the "no-retry" and "retry" cases

    The team's convention is to use parameterised tests wherever multiple cases exercise the same assertion. The two "does not retry" tests (403 without auth body and 400 bad-request) are structurally identical — same setup, same assertions — and could be collapsed into a single it.each. Similarly, the 401-retry and 403-auth-failure-retry cases share the same flow. Consolidating them would make it easy to add new status codes or body shapes in the future without duplicating test structure.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/mobile/src/lib/api.test.ts
    Line: 858-898
    
    Comment:
    **Prefer parameterised tests for the "no-retry" and "retry" cases**
    
    The team's convention is to use parameterised tests wherever multiple cases exercise the same assertion. The two "does not retry" tests (403 without auth body and 400 bad-request) are structurally identical — same setup, same assertions — and could be collapsed into a single `it.each`. Similarly, the 401-retry and 403-auth-failure-retry cases share the same flow. Consolidating them would make it easy to add new status codes or body shapes in the future without duplicating test structure.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/mobile/src/lib/api.ts:1
`expo/build/winter/fetch/fetch.types` is an internal build-output path, not part of expo's public API. It can break silently on an expo version bump when the internal directory structure changes. The standard `RequestInit` type is typically a safe drop-in here, or the type can be inlined; if expo's extra properties are needed it's better to declare a local intersection or keep the import but document it explicitly so a breakage is obvious in upgrade PRs.

```suggestion
// NOTE: importing from an internal expo path; update if expo restructures its build output
import type { FetchRequestInit } from "expo/build/winter/fetch/fetch.types";
```

### Issue 2 of 2
apps/mobile/src/lib/api.test.ts:858-898
**Prefer parameterised tests for the "no-retry" and "retry" cases**

The team's convention is to use parameterised tests wherever multiple cases exercise the same assertion. The two "does not retry" tests (403 without auth body and 400 bad-request) are structurally identical — same setup, same assertions — and could be collapsed into a single `it.each`. Similarly, the 401-retry and 403-auth-failure-retry cases share the same flow. Consolidating them would make it easy to add new status codes or body shapes in the future without duplicating test structure.

Reviews (1): Last reviewed commit: "fix(mobile): auto-refresh on PostHog 401..." | Re-trigger Greptile

Comment thread apps/mobile/src/lib/api.ts Outdated
@@ -1,3 +1,4 @@
import type { FetchRequestInit } from "expo/build/winter/fetch/fetch.types";
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.

P2 expo/build/winter/fetch/fetch.types is an internal build-output path, not part of expo's public API. It can break silently on an expo version bump when the internal directory structure changes. The standard RequestInit type is typically a safe drop-in here, or the type can be inlined; if expo's extra properties are needed it's better to declare a local intersection or keep the import but document it explicitly so a breakage is obvious in upgrade PRs.

Suggested change
import type { FetchRequestInit } from "expo/build/winter/fetch/fetch.types";
// NOTE: importing from an internal expo path; update if expo restructures its build output
import type { FetchRequestInit } from "expo/build/winter/fetch/fetch.types";
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/mobile/src/lib/api.ts
Line: 1

Comment:
`expo/build/winter/fetch/fetch.types` is an internal build-output path, not part of expo's public API. It can break silently on an expo version bump when the internal directory structure changes. The standard `RequestInit` type is typically a safe drop-in here, or the type can be inlined; if expo's extra properties are needed it's better to declare a local intersection or keep the import but document it explicitly so a breakage is obvious in upgrade PRs.

```suggestion
// NOTE: importing from an internal expo path; update if expo restructures its build output
import type { FetchRequestInit } from "expo/build/winter/fetch/fetch.types";
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

- Drop the import from `expo/build/winter/fetch/fetch.types` (an
  internal build-output path) and derive the init type from the
  `fetch` signature itself, so an expo version bump can't silently
  break the import.
- Collapse the structurally identical retry and no-retry tests into
  two `it.each` tables so new status codes / body shapes can be
  added without copy-pasting the assertion block.

Generated-By: PostHog Code
Task-Id: bece83a6-7305-4a9d-b7c3-c5cb62ee3e49
@Gilbert09 Gilbert09 added the Stamphog This will request an autostamp by stamphog on small changes label May 28, 2026
Copy link
Copy Markdown

@stamphog stamphog Bot left a comment

Choose a reason for hiding this comment

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

Review agent failed after 3 attempts — needs human review.

@stamphog stamphog Bot removed the Stamphog This will request an autostamp by stamphog on small changes label May 28, 2026
@Gilbert09 Gilbert09 requested a review from a team May 28, 2026 15:09
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.

2 participants