From d2f3b42debc38737268640cf95f9175c01c12262 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Tue, 6 Jan 2026 20:57:42 +0100 Subject: [PATCH] fix: OAuth token refresh failure returns undefined instead of throwing When OAuth refresh fails during model discovery, getApiKey() now returns undefined instead of throwing. This allows the app to start and fall back to other providers, so the user can /login to re-authenticate. fixes #498 --- .pi/prompts/is.md | 21 +++++++++++ .pi/prompts/pr.md | 35 +++++++++++++++++++ packages/coding-agent/CHANGELOG.md | 1 + .../coding-agent/src/core/auth-storage.ts | 12 +++---- packages/coding-agent/src/main.ts | 25 ++++++++++++- .../components/session-selector.ts | 6 +++- pi-test.sh | 4 +++ 7 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 .pi/prompts/is.md create mode 100644 .pi/prompts/pr.md create mode 100755 pi-test.sh diff --git a/.pi/prompts/is.md b/.pi/prompts/is.md new file mode 100644 index 00000000..f57561f6 --- /dev/null +++ b/.pi/prompts/is.md @@ -0,0 +1,21 @@ +--- +description: Analyze GitHub issues (bugs or feature requests) +--- +Analyze GitHub issue(s): $ARGUMENTS + +For each issue: + +1. Read the issue in full, including all comments and linked issues/PRs. + +2. **For bugs**: + - Ignore any root cause analysis in the issue (likely wrong) + - Read all related code files in full (no truncation) + - Trace the code path and identify the actual root cause + - Propose a fix + +3. **For feature requests**: + - Read all related code files in full (no truncation) + - Propose the most concise implementation approach + - List affected files and changes needed + +Do NOT implement unless explicitly asked. Analyze and propose only. diff --git a/.pi/prompts/pr.md b/.pi/prompts/pr.md new file mode 100644 index 00000000..e5c41884 --- /dev/null +++ b/.pi/prompts/pr.md @@ -0,0 +1,35 @@ +--- +description: Review PRs from URLs with structured issue and code analysis +--- +You are given one or more GitHub PR URLs: $@ + +For each PR URL, do the following in order: +1. Read the PR page in full. Include description, all comments, all commits, and all changed files. +2. Identify any linked issues referenced in the PR body, comments, commit messages, or cross links. Read each issue in full, including all comments. +3. Analyze the PR diff. Read all relevant code files in full with no truncation. Include related code paths that are not in the diff but are required to validate behavior. +4. Check for a changelog entry in the relevant `packages/*/CHANGELOG.md` files. Report whether an entry exists. If missing, state that a changelog entry is required before merge and that you will add it if the user decides to merge. Follow the changelog format rules in AGENTS.md. +5. Provide a structured review with these sections: + - Good: solid choices or improvements + - Bad: concrete issues, regressions, missing tests, or risks + - Ugly: subtle or high impact problems +6. Add Questions or Assumptions if anything is unclear. +7. Add Change summary and Tests. + +Output format per PR: +PR: +Changelog: +- ... +Good: +- ... +Bad: +- ... +Ugly: +- ... +Questions or Assumptions: +- ... +Change summary: +- ... +Tests: +- ... + +If no issues are found, say so under Bad and Ugly. \ No newline at end of file diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 7dea01b7..c49128e3 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Queued steering/follow-up messages no longer wipe unsent editor input ([#503](https://github.com/badlogic/pi-mono/pull/503) by [@tmustier](https://github.com/tmustier)) +- OAuth token refresh failure no longer crashes app at startup, allowing user to `/login` to re-authenticate ([#498](https://github.com/badlogic/pi-mono/issues/498)) ## [0.37.3] - 2026-01-06 diff --git a/packages/coding-agent/src/core/auth-storage.ts b/packages/coding-agent/src/core/auth-storage.ts index 8ac63dd7..09c0ac02 100644 --- a/packages/coding-agent/src/core/auth-storage.ts +++ b/packages/coding-agent/src/core/auth-storage.ts @@ -326,7 +326,7 @@ export class AuthStorage { if (result) { return result.apiKey; } - } catch (err) { + } catch { // Refresh failed - re-read file to check if another instance succeeded this.reload(); const updatedCred = this.data[provider]; @@ -339,13 +339,9 @@ export class AuthStorage { : updatedCred.access; } - // Refresh truly failed - DO NOT remove credentials - // User can retry or re-authenticate manually - const errorMessage = err instanceof Error ? err.message : String(err); - throw new Error( - `OAuth token refresh failed for ${provider}: ${errorMessage}. ` + - `Please try again or re-authenticate with /login.`, - ); + // Refresh truly failed - return undefined so model discovery skips this provider + // User can /login to re-authenticate (credentials preserved for retry) + return undefined; } } else { // Token not expired, use current access token diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index eaa164cd..92754958 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -171,12 +171,35 @@ function getChangelogForDisplay(parsed: Args, settingsManager: SettingsManager): return undefined; } +/** + * Resolve a session argument to a file path. + * If it looks like a path, use as-is. Otherwise try to match as session ID prefix. + */ +function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): string { + // If it looks like a file path, use as-is + if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) { + return sessionArg; + } + + // Try to match as session ID (full or partial UUID) + const sessions = SessionManager.list(cwd, sessionDir); + const matches = sessions.filter((s) => s.id.startsWith(sessionArg)); + + if (matches.length >= 1) { + return matches[0].path; // Already sorted by modified time (most recent first) + } + + // No match - return original (will create new session) + return sessionArg; +} + function createSessionManager(parsed: Args, cwd: string): SessionManager | undefined { if (parsed.noSession) { return SessionManager.inMemory(); } if (parsed.session) { - return SessionManager.open(parsed.session, parsed.sessionDir); + const resolvedPath = resolveSessionPath(parsed.session, cwd, parsed.sessionDir); + return SessionManager.open(resolvedPath, parsed.sessionDir); } if (parsed.continue) { return SessionManager.continueRecent(cwd, parsed.sessionDir); diff --git a/packages/coding-agent/src/modes/interactive/components/session-selector.ts b/packages/coding-agent/src/modes/interactive/components/session-selector.ts index 317c72a5..8bb7fe9d 100644 --- a/packages/coding-agent/src/modes/interactive/components/session-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/session-selector.ts @@ -42,7 +42,11 @@ class SessionList implements Component { } private filterSessions(query: string): void { - this.filteredSessions = fuzzyFilter(this.allSessions, query, (session) => session.allMessagesText); + this.filteredSessions = fuzzyFilter( + this.allSessions, + query, + (session) => `${session.id} ${session.allMessagesText}`, + ); this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1)); } diff --git a/pi-test.sh b/pi-test.sh new file mode 100755 index 00000000..fa78e54f --- /dev/null +++ b/pi-test.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +npx tsx packages/coding-agent/src/cli.ts "$@"