Skip to content

Show "Pi isn't signed in" in native chat before the first send - #26743

Merged
brennanb2025 merged 5 commits into
mainfrom
brennanb2025/pi-signed-out-before-send
Oct 9, 2026
Merged

brennanb2025 merged 5 commits into
mainfrom
brennanb2025/pi-signed-out-before-send

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​205 $\color{#cf222e}{\Huge{\mathbf{−}}}$​4 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​201
Prod 12 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​131 $\color{#cf222e}{\Huge{\mathbf{−}}}$​30 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​101

ELI5

If Pi has no sign-in at all, a new Pi chat used to open as if everything was fine. You only found out when your first message failed. Now the chat tells you up front, in the same notice Codex chats already show: "Sign in to Pi by running pi and using /login on the computer running this chat." And once you sign in, your next message in that same chat goes through on the first try.

What Changed

What you see (Pi native chat, nothing signed in):

Before After
Opening the chat Normal empty chat, no hint. The sign-in notice above the composer, before you send anything. Send is not blocked.
Signing in, then sending in the same chat The first send after signing in still failed ("No API key found for the selected model"), because the Pi that was already running never picks up a new login. You had to send a second time. The send goes through: Orca starts a fresh Pi for it, which reads the new login.
Sending without signing in The sign-in line under the refused message. Same line, shown once (the notice above the composer steps aside while that line is on screen).

The other agents, before the first send: Claude already shows a sign-in line as the chat opens, Codex already shows this notice, and Grok already fails to start with its sign-in line (checked with real Grok 1.0.50 in a throwaway home: it refuses to open a session when signed out). None of that changes. OpenCode has nothing to show: without a login it keeps its free models usable, so a signed-out OpenCode can still chat (from its source). OMP stays quiet, see "Differences" below.

Mechanism. Every native chat starts its agent as soon as the chat opens. When a fully signed-out Pi starts, its own model list is empty and it keeps a placeholder model, so every prompt that Pi takes will fail as "not signed in". Orca already reads that list at start. Now:

  1. The Pi adapter answers a new optional read, startUnavailable(sessionId): "not signed in" when its running Pi started with no model, nothing once that Pi has exited or closed. It lives and dies with that one Pi process; nothing is stored.
  2. The host's model-list answer for the chat (agentSession.modelCatalog, which the chat already reads for its notice) carries it as the existing unavailable field when the catalog itself has no reason. Only for a read naming that chat and its own agent.
  3. The existing "replace a signed-out agent before the next send" check (from Show sign-in guidance for all native chat agents #26544) also counts a Pi that started signed out, so the next send gets a fresh Pi.
  4. The chat re-reads the answer whenever the host starts or stops its agent (the host's status feed already publishes that), so the notice goes away when that Pi is stopped, and a late answer from before the stop is dropped.
  5. The notice steps aside while the newest message's own "not sent" line already says the same reason (newest in the host's send order, and only while that line is on screen).

Why

The fact already exists inside the running chat: Pi's own start says it has no model. Reading it there costs nothing, uses the chat's real folder, environment and extensions (Pi reads project-level settings), and is the same process the next send would use. The alternatives were weaker:

  • A separate Pi model-list check (like Codex's): an extra Pi process per chat, run outside the chat's folder, and it still could not fix the "first send after signing in fails" problem, which needs the running Pi replaced.
  • Saving the verdict in the shared per-account model catalog: wrong lifetime. The verdict belongs to one running Pi and must end with it.

Unknown stays quiet: no running Pi, an older host, or an unreachable host shows nothing. The execution host decides (paired remote hosts run the same code); a client only displays it.

Differences from the common pattern

  • Send stays enabled for a new chat known to be signed out, where the common pattern disables it. Intended, as in Show sign-in guidance for all native chat agents #26544: the verdict can be wrong while a send would work, and the send is what replaces the agent.
  • The notice can outlast a sign-in until the next send (or until Pi is stopped), where the common pattern re-checks and clears it. Intended: the running Pi really cannot use a login made after it started; the next send replaces it and goes through. Re-checking would need a second Pi process.
  • No up-front notice for a reopened chat whose agent is not running yet (it starts on the first send). Intended: there is no evidence to show until a Pi has started; the first send then shows the sign-in line as before.
  • OMP stays quiet before the first send. Intended: at start, a fully signed-out OMP looks exactly like one whose enabledModels allow-list excludes every model it has a login for (OMP 17.0.5 source and a throwaway-home run: no model option either way). An up-front "Sign in to OMP." would be wrong for the second case, and OMP's first-send failure already shows OMP's own words.

Linked Issue

Follow-up to #26544, which listed "no sign-in check before the first send" as a temporary difference. Requested by @BrennanKB5.

Visual Proof

Hidden-window QA on macOS with the real Pi 1.1.0 in throwaway home folders and a local fake model server (no real account; "signing in" = writing that server's key into the throwaway Pi folder). Real config files unchanged (hashed before and after).

What Before (base 820277c2077) After (this PR)
New Pi chat, nothing signed in, nothing sent yet before-01 No hint. after-01 "Sign in to Pi by running pi and using /login on the computer running this chat."
Sign in, then send "Say hello" in the same chat before-02 The first send still fails ("No API key found for the selected model"); before-03 only a second send works. after-02 The first send gets the reply; the notice is gone.
Send without signing in (unchanged: the same line under the message) after-05 One sign-in line, under the refused message; the notice above the composer steps aside (checked: one occurrence on the page, still one 20 s later).

The model picker reads unknown/unknown for a signed-out Pi in both builds: that is Pi's own placeholder model name, unchanged here. The empty-composer Send arrow is disabled only because the composer is empty, as for any chat.

Testing

  • I manually tested these changes locally (macOS, hidden-window QA above)
  • Automated tests added/updated

Automated, on macOS, under Node (node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts): 395 test files / 4155 tests passed at the final commit: all of src/main/pi, all of src/renderer/src/components/native-chat, and the host tests for the signed-out restart, the adapter router, the idle sweep, the model-list RPC handler and Pi client capability. New or changed tests:

  • src/main/pi/rpc-session-adapter.test.ts: a Pi that started with an empty model list (the exact shape the captured Pi 1.0.4 signed-out session returns) reports "not signed in"; nothing once its process exited (even while its output still drains) or it closed.
  • src/main/runtime/rpc/methods/structured-agent-session-options-read.test.ts: the model-list answer carries a Pi chat's start verdict; the catalog's own reason wins; another agent's read never gets it; quiet when the start said nothing.
  • src/main/native-chat/agent-session-wire/structured-agent-session-signed-out-child.test.ts: on the real host test rig, the next send after a signed-out start goes to a new agent; only a "not signed in" start counts, never a starting child.
  • src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.unavailable.test.tsx: the chat re-reads when its agent stops or starts, and drops a signed-out answer read before the stop.
  • src/renderer/src/components/native-chat/use-native-chat-availability-notice.test.tsx: the notice steps aside only while the newest send's own line is on screen and loaded, by the host's send order; it comes back when that line is gone or a later send exists.

Ablations: reverting each production piece fails exactly its new test (the Pi start read, the exited-process check, the RPC merge, the restart check, the re-read on agent start/stop, the "line on screen" and "row loaded" checks, the send order).

pnpm tc:node, pnpm tc:web and orca-ci-checks (23/23, including typecheck) passed.

Live, macOS, hidden windows (driven over CDP; no desktop input), real Pi 1.1.0 in throwaway home folders with a local fake model server, no real account: the scenarios in Visual Proof passed on the final commit, plus a signed-in-from-the-start chat showing no notice and a normal reply (earlier commit of this PR). The same checks on the base build produced the "before" column. Not run: Windows, Linux, paired remote hosts, mobile.

Review

A read-only reviewer ran five rounds on this branch, judging the approach as well as the diff. Every finding was fixed with a regression test, or shown unreachable:

  1. Round 1: the notice could outlive the Pi it came from (when the idle sweep stops it); a Pi whose process had exited could still report. Fixed: the chat re-reads when its agent starts or stops; exited processes report nothing.
  2. Round 2: an answer read just before Pi stopped could put the notice back. Fixed: every start/stop re-reads and drops the earlier answer.
  3. QA found the same sign-in sentence twice after a refused send. Fixed: the notice steps aside while the refused send's own line says it.
  4. Rounds 3 and 4: that step-aside could hide the only guidance (a returned card deleted, the row trimmed) or pick the wrong send when timestamps go backwards. Fixed: it requires the line to be on screen and its row loaded, and uses the host's send order.
  5. Round 4 also raised a shared "wait for the model list" read not restarting on an agent stop. Not reachable: only agents with a model-list check (Codex, Claude) ever wait on one, and their verdict does not come from the running agent. Round 5 confirmed this from the code and found nothing significant remaining.

The reviewer also judged the design: the verdict belongs to one running Pi, so keeping it there (not a separate check, not the shared per-account model catalog) gives it the right lifetime, and the restart is needed either way.

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

No new wire fields or opcodes: unavailable is an existing optional field of the model-list answer, which older clients already read (they will show the same notice for Pi). Older hosts never set it for Pi, so nothing changes there. No new UI surface, strings or settings. Windows, Linux and paired remote hosts use the same code paths but were not run.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred): lint/typecheck via orca-ci-checks, targeted tests above; full suite and build left to CI

A Pi chat whose agent started with no model to use now says so in the
existing availability notice, and the next send replaces that agent so
a sign-in made since takes effect.
…ted Pi

A Pi's start verdict ends with that Pi: the notice now re-reads when the
host starts or stops the chat's agent, and a Pi whose process already
exited reports nothing.
…art or stop

The notice steps aside while the newest send was refused for the same
reason, and the chat re-reads its verdict whenever its agent starts or
stops, dropping an answer read before.
…reen

Uses the host's send order and the chat's rendered delivery lines, so a
deleted returned card or a trimmed row brings the sign-in notice back.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review summary

Verdict: ready from review. Five read-only review rounds on this branch (approach and diff), plus three hidden-window QA runs with the real Pi 1.1.0.

What was checked

  • Whether reading the verdict from the running Pi is the right design versus a separate model-list check or the shared per-account model catalog. Conclusion: the verdict belongs to one running Pi (a running Pi never picks up a later login), so it must live and die with that Pi, and the restart before the next send is needed either way.
  • Every other agent's state before the first send, verified first-hand: Grok 1.0.50 refuses to open a session when signed out (live, throwaway home); Pi 1.0.x–1.1.0 never says "No model selected" (its model is a placeholder, so it says "No API key found for the selected model", live on 1.1.0); OMP 17.0.5 looks the same signed out and with an allow-list that excludes every signed-in model (source + live), so it stays quiet; OpenCode keeps free models usable without a login (source).
  • Races and lifetimes: Pi stopped by the idle sweep, Pi exited while its output drains, a late answer read before the stop, a shared wait for a model list, the refused-send line versus the notice (deleted returned card, trimmed row, host send order), older hosts and clients.

Findings and outcomes

Round Finding Outcome
1 Notice could outlive its Pi after the idle sweep stops it Fixed: re-read when the chat's agent starts or stops (+ test)
1 An exited Pi whose output still drains could report Fixed (+ test)
2 A late answer from before the stop could restore the notice Fixed: each start/stop re-reads and drops the earlier answer (+ test)
QA Same sign-in sentence twice after a refused send Fixed: notice steps aside while that send's line says it (+ test, live recheck: one occurrence)
3 Step-aside could hide the only guidance (deleted card) / wrong send under clock skew Fixed: needs the line on screen, host send order (+ tests)
4 Refused row trimmed but still suppressing Fixed: also needs the row loaded (+ test)
4 Shared model-list wait not restarted on stop Not reachable: only Codex/Claude ever wait on a listing, and their verdict is not tied to the running agent; confirmed from code in round 5
5 — No significant findings remaining

Each fix was ablated (reverting it fails exactly its new test). Not verified: Windows, Linux, paired remote hosts, mobile.

@coderabbitai

coderabbitai Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 19d90385-090f-4127-a29f-f9db0f3352ef
📥 Commits

Reviewing files that changed from the base of the PR and between b9a7830 and b99cb63.

📒 Files selected for processing (17)
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter-router.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-host-delivery.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-rest-test-rig.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-signed-out-child.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-signed-out-child.ts
  • src/main/pi/rpc-session-adapter.test.ts
  • src/main/pi/rpc-session-adapter.ts
  • src/main/runtime/rpc/methods/structured-agent-session-options-read.test.ts
  • src/main/runtime/rpc/methods/structured-agent-session-options-read.ts
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
  • src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.ts
  • src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.unavailable.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-availability-notice.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-availability-notice.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-options.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

Pi sessions now expose startup unavailability through the session adapter and catalog response. The renderer rereads the catalog when provider-running state changes. Availability notices also account for the newest rejected send and its journal line.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to b99cb

No actionable issue remains that needs to be resolved before merging.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 17 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main user-facing change: showing the Pi sign-in notice before the first send in native chat.
Description check Passed The description is comprehensive and follows the repository template. It explains the user impact, implementation, rationale, linked issue, visual proof, testing, review findings, compatibility consid…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@brennanb2025
brennanb2025 merged commit 4f126bc into main Oct 9, 2026
35 checks passed
brennanb2025 added a commit that referenced this pull request Oct 9, 2026
…ain)

Rebuilds #26561 on main now that #26416 is squash-merged. Same change as
before, re-applied onto what main has since:

- Every adapter answers acquire at spawn and reports `started` on protocol
  readiness with its real handle (Claude initialize, Codex thread, ACP
  session new/load plus restore, Pi session file plus restore). No bridge,
  no per-adapter phase or Stop opt-outs.
- Picks made while starting are intents applied model then effort before
  the start is accepted; rewind, goal, rewind recovery and compact wait for
  a proven start outside the session queue.
- One Stop rule: Stop ends the session for every agent (Codex: interrupt,
  up to 2 s for the turn to end, then close).
- Orchestration worker starts share one deadline; a send's answer copies
  its row; a re-attach during start keeps the start wait.

Integrated with main:
- ACP: the subagent-stop probe, child-work delivery and new/loaded origin
  move into the handshake; the sign-in rule (#26544) classifies a failed
  handshake; acpAgentName stays in acp-structured-agent-definitions.
- Codex: the session journal translator and account kind (#26407) reach the
  start step.
- Pi: the start choice (#26821) and the signed-out verdict (#26743) are read
  in Pi's start, so both exist when `started` is reported; commands follow.
- openWithAgent keeps main's pre-agent check and replay rule.
- A report with no model drops the saved one, as #26587 decided.
- The renderer re-reads a start's sign-in verdict once the start is proven.
brennanb2025 added a commit that referenced this pull request Oct 9, 2026
The gap it closed exists only for a desktop app built between #26743 and
this change; no released app shows the Pi notice yet, and the first send
still says not signed in. Not worth a held read on every agent's start.
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.

1 participant