Skip to content

tui: the MCP boot chip counts expired logins separately from failures - #5938

Merged
Hmbown merged 2 commits into
mainfrom
fix/mcp-chip-need-login-5926
Sep 6, 2026
Merged

tui: the MCP boot chip counts expired logins separately from failures#5938
Hmbown merged 2 commits into
mainfrom
fix/mcp-chip-need-login-5926

Conversation

@Hmbown

@Hmbown Hmbown commented Sep 6, 2026

Copy link
Copy Markdown
Owner

No-Issue: chip half of #5926; the issue stays open for the /mcp ordering and the refresh-parse detail.

Refs #5926 (chip half; the /mcp ordering with a one-key login and the refresh-parse detail stay open)

Receipt: the footer read MCP · 12 connected · 8 failed while codewhale mcp connect showed seven of those servers answering 401 with the engine already printing the remedy. The boot surface classifies them as NeedsLogin; the chip lumped them into failed.

Now: MCP · 12 connected · 7 ◆ auth required · 1 failed (narrow: MCP · 7 login · 1 failed), at attention level when nothing actually failed and failure level otherwise. The label is the shared mcp_auth_required_state_label() every other MCP surface prints.

Verified: RUST_MIN_STACK=16777216 cargo test -p codewhale-tui --lib -- tui::session_boot → 14 passed, 0 failed (two tests cover the single-state and mixed-state chips in both widths).


Note

Low Risk
Footer copy and severity for MCP boot only; no auth, connect, or data-path changes.

Overview
The session boot footer chip no longer treats NeedsLogin MCP servers as failures. Expired or missing logins are counted with the shared mcp_auth_required_state_label() (e.g. MCP · 12 connected · 7 ◆ auth required · 1 failed), while only true Failed states use the failed label.

When only auth is missing, the chip uses SessionBootActivityLevel::Attention instead of failure; mixed auth + failure still uses failure level. A compact line (MCP · N login · M failed) keeps both counts on narrow footer budgets. Tests cover needs-login-only and mixed login/failure chips at full and tight widths (#5926).

Reviewed by Cursor Bugbot for commit 236cda0. Bugbot is set up for automated code reviews on this repo. Configure here.

…#5926)

The founder's footer read 'MCP · 12 connected · 8 failed' when seven of
the eight only needed 'codewhale mcp login <server>'. The boot surface
already classifies those servers as NeedsLogin; the chip now says
'MCP · 12 connected · 7 ◆ auth required · 1 failed' (narrow form
'MCP · 7 login · 1 failed') and sits at attention level when nothing
actually failed. The /mcp ordering and the refresh-parse detail stay open
on #5926.

Tests: a needs-login server renders under the auth-required label at
attention level without the word failed; mixed states keep both counts in
the full and the narrow form.
cargo test -p codewhale-tui --lib -- tui::session_boot: 14 passed, 0 failed.

Signed-off-by: CodeWhale Bot <hmbown@gmail.com>
@Hmbown Hmbown added this to the v0.9.13 milestone Sep 6, 2026
Copilot AI lite review requested due to automatic review settings September 6, 2026 07:30
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-06T07:34:09.886186Z 0aad71a PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cursor

cursor Bot commented Sep 6, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_08f16cc4-b03f-4786-8ee3-779d153c988e)

@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 1m 47s —— View job


Review: MCP boot chip separates expired logins from failures

  • Gather context (diff, changed file, related helpers)
  • Review correctness of chip logic (need_login vs failed split)
  • Check mcp_auth_required_state_label() reuse / provider-safety relevance
  • Check test coverage
  • Post final review

Scope: crates/tui/src/tui/session_boot.rs only (+87/-23), all in SessionBootSurface::activity_notice and its test module.

Summary

The change is correct and well-scoped. McpServerBootState is a single-valued enum per row (Connecting | Connected | Failed | NeedsLogin | Disabled), so the need_login and failed filters at session_boot.rs:220-229 are mutually exclusive by construction — no double-counting risk. The level selection (Failure if any real failure exists, Attention otherwise, session_boot.rs:265-269) matches the PR description, and SessionBootActivityLevel::Attention is already a live variant wired to ChromeInk::Attention in phase_strip.rs:221, so no rendering-side changes were needed — good use of an existing surface rather than adding a new one.

mcp_auth_required_state_label() (session_boot.rs:371) is reused as-is, not reimplemented — consistent with the repo's rung-2 reuse rule and the PR's own claim that it's "the shared label every other MCP surface prints."

No provider/model routing code is touched; not applicable to the EPIC #2608 invariant.

Correctness check

  • Full/compact candidate ordering (connected → login → failed) matches both the wide and narrow examples in the PR description.
  • activity_notice_from_candidates (unchanged) still falls back from full to compact to None on budget, preserving prior truncation behavior.
  • row_from_snapshot (unchanged, session_boot.rs:318-356) is the sole classifier feeding state, so this PR only changes how already-classified rows are aggregated — it doesn't touch classification logic, keeping the diff minimal.

Minor, non-blocking observations

  • chip_separates_expired_logins_from_real_failures test (session_boot.rs:684-685) sets expired.auth_required = true on a row whose error text is "401 Unauthorized", which mcp_error_requires_login/error_text_looks_auth_required already classifies as auth-required via the "401"/"unauthorized" substrings (crates/tui/src/mcp/oauth.rs:75-76). The explicit flag is redundant here (harmless, but doesn't add coverage beyond what typed_auth_required_state_routes_to_login_without_error_text_sniffing already covers for the typed-flag path).
  • The auth-required label text itself isn't run through tr()/locale (pre-existing in mcp_auth_required_state_label, not introduced by this PR) — worth a follow-up if non-English locales are a near-term priority, but out of scope here.

Verification note

I was not able to execute cargo test in this sandboxed review environment (Bash tool calls require interactive approval not available here), so I could not independently re-run the tui::session_boot suite. Static review of the diff and full surrounding function found no logic errors, non-exhaustive matches, or panics on the changed paths; the PR's reported 14 passed, 0 failed is consistent with what the added/updated tests assert.

Copilot AI left a comment

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.

🟢 Approval recommended

The change is localized to boot-chip rendering logic, matches the stated behavior in #5926 for the “chip half,” and is covered by updated unit tests for both single- and mixed-state cases.

Pull request overview

Updates the TUI session-boot footer “MCP” activity chip to distinguish auth-required (expired/missing login) from true failures, aligning the boot surface with the engine’s existing NeedsLogin classification and the shared ◆ auth required label used elsewhere in the TUI.

Changes:

  • Count McpServerBootState::NeedsLogin separately from Failed, and render it under mcp_auth_required_state_label() in the chip text.
  • Map chip severity to Attention when only logins are required, and Failure when any server actually failed.
  • Expand tests to cover needs-login-only and mixed-state chips (including full vs compact rendering under tight budgets).
File summaries
File Description
crates/tui/src/tui/session_boot.rs Separates NeedsLogin vs Failed in the boot footer MCP chip, adjusts severity mapping, and adds tests for the new text/level behavior.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0aad71a9e6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +255 to +256
full.push_str(&format!("{ITEM_SEPARATOR}{need_login} {auth_label}"));
compact.push_str(&format!("{ITEM_SEPARATOR}{need_login} login"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Localize both authentication-chip variants

When app.ui_locale is non-English, this new branch emits English in both candidates: mcp_auth_required_state_label() returns ◆ auth required, while the compact form hardcodes login; only the surrounding connected/failed labels are translated. This produces mixed-language wide and narrow footers even though localized LaunchMcpNeedsSignInOne/LaunchMcpNeedsSignInMany messages already exist. Compose the authentication prose through tr(locale, MessageId::...), leaving only the glyph in code.

AGENTS.md reference: crates/tui/AGENTS.md:L25-L26

Useful? React with 👍 / 👎.

let mut compact = String::from("MCP");
if need_login > 0 {
full.push_str(&format!("{ITEM_SEPARATOR}{need_login} {auth_label}"));
compact.push_str(&format!("{ITEM_SEPARATOR}{need_login} login"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the compact state neutral to the authentication method

When a needs-auth server uses a manual bearer/environment header or is plugin-contributed, the compact candidate incorrectly labels it login. These servers still set McpServerSnapshot::auth_required after a 401, but auth_required_recovery_hint() explicitly routes them to correcting the credential and /mcp reload, while OAuth login is disabled. At narrow widths this therefore advertises the wrong recovery class; use a neutral compact label such as auth/auth required, or derive the wording from the server's supported recovery.

Useful? React with 👍 / 👎.

} else {
SessionBootActivityLevel::Attention
};
return activity_notice_from_candidates(level, vec![full, compact], budget);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep real failures visible at the 40-column budget

At the supported 40-column layout, notice_budget is 20, but with both counts nonzero the shortest candidate, MCP · 1 login · 1 failed, is 24 columns, so activity_notice_from_candidates() returns None and the footer hides the actual failure. Before this change the same state fell back to MCP · 2 failed, which fit in 14 columns. Add another fallback that sheds the auth clause and retains the higher-severity failed count instead of dropping the entire notice.

AGENTS.md reference: crates/tui/AGENTS.md:L22-L22

Useful? React with 👍 / 👎.

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

The PR splits NeedsLogin MCP servers from Failed in the boot footer chip and maps login-only cases to Attention severity. The logic is straightforward and the existing tests are updated to cover the full and compact mixed-state paths.

Findings

  • [WARNING] Compact auth-required label is not localized (crates/tui/src/tui/session_boot.rs:256)
    The new compact fallback hard-codes the string "login" when need_login > 0. The full candidate localizes connected/failed via tr(locale, ...), so non-English TUI users can see an English word in the narrow footer. If an existing MessageId for "login" exists, use it; otherwise a new localized message is needed for the compact form.
  • [INFO] Compact login-only path is not directly tested (crates/tui/src/tui/session_boot.rs:256)
    The updated single-state test uses a 100-column budget and asserts the full candidate. The new compact candidate for need_login > 0 && failed == 0 (MCP · N login) is not exercised by a test; the mixed-state test does cover the compact failed+login combination. A narrow-budget assertion for the login-only case would lock the intended fallback.

Assessment

The change is low-risk and functionally correct. The main gap is localization of the compact login label; adding a narrow login-only test would further strengthen coverage.


Advisory review by Codewhale (codewhale review --pr 5938 --post, head 0aad71a9e663e6239164723e11cf4506efe7f2ba). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

let mut compact = String::from("MCP");
if need_login > 0 {
full.push_str(&format!("{ITEM_SEPARATOR}{need_login} {auth_label}"));
compact.push_str(&format!("{ITEM_SEPARATOR}{need_login} login"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[WARNING] Compact auth-required label is not localized

The new compact fallback hard-codes the string "login" when need_login > 0. The full candidate localizes connected/failed via tr(locale, ...), so non-English TUI users can see an English word in the narrow footer. If an existing MessageId for "login" exists, use it; otherwise a new localized message is needed for the compact form.

let mut compact = String::from("MCP");
if need_login > 0 {
full.push_str(&format!("{ITEM_SEPARATOR}{need_login} {auth_label}"));
compact.push_str(&format!("{ITEM_SEPARATOR}{need_login} login"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[INFO] Compact login-only path is not directly tested

The updated single-state test uses a 100-column budget and asserts the full candidate. The new compact candidate for need_login > 0 && failed == 0 (MCP · N login) is not exercised by a test; the mixed-state test does cover the compact failed+login combination. A narrow-budget assertion for the login-only case would lock the intended fallback.

…en on this head)

Signed-off-by: CodeWhale Bot <hmbown@gmail.com>
@cursor

cursor Bot commented Sep 6, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_02cc73d7-3de5-4aed-b91a-301c954a5276)

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Devin Review found 1 potential issue.

Devin Review

Comment on lines +254 to +256
if need_login > 0 {
full.push_str(&format!("{ITEM_SEPARATOR}{need_login} {auth_label}"));
compact.push_str(&format!("{ITEM_SEPARATOR}{need_login} login"));

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.

🔍 Login status bypasses localization

The new login labels remain English while adjacent status labels use tr. Non-English footers therefore mix languages.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 3m 39s —— View job


Review: MCP boot chip separates expired logins from failures

Scope: crates/tui/src/tui/session_boot.rs only (+87/-23), all in SessionBootSurface::activity_notice and its test module. Verified by reading the code directly (test execution wasn't possible in this sandbox — cargo test requires interactive approval that isn't available here).

Correctness

McpServerBootState is single-valued per row (row_from_snapshot), so the need_login/failed filters at session_boot.rs:213-229 are disjoint by construction — no double counting. SessionBootActivityLevel::Attention is already wired to ChromeInk::Attention (phase_strip.rs:221), so no rendering gap.

Bug: at narrow footer widths, a real failure can be hidden entirely, not just abbreviated. When failed > 0 && need_login > 0, the only two candidates are full and compact (session_boot.rs:249-274). At the officially supported 40-column layout (notice_budget = width/2 = 20, per phase_strip.rs:1079 and the width matrix in crates/tui/AGENTS.md:53), the shortest candidate "MCP · 1 login · 1 failed" is 24 columns — over budget — so activity_notice_from_candidates returns None and the chip disappears. Before this PR, the same state fell back to "MCP · 2 failed" (14 columns), which always fit. This is a real regression: an actual failure that used to always surface at 40 columns can now be silently dropped.

The same file already has the right pattern for this exact problem — plugin_review_notice_uses_attention_and_sheds_whole_fields (session_boot.rs:496-529) sheds whole fields down through three tiers ("Plugins · Problems: 1 · /plugins""Plugins · Problems: 1""Plugins · 1"), always keeping the highest-severity count visible. crates/tui/AGENTS.md:22 codifies this: "Compact layouts remove chrome before content." The MCP chip should add a third fallback that drops the auth clause and keeps just "MCP · {failed} failed" when failed > 0, rather than falling through to None. Only the mixed-state test at budget 30 was added (session_boot.rs:711); nothing exercises the width-40 case where this breaks.

Fix this →

Provider/model routing (EPIC #2608)

Not applicable — no provider/model selection code is touched.

Reuse

Good rung-2 discipline: mcp_auth_required_state_label() is reused as-is rather than reimplemented, consistent with it being "the shared label every other MCP surface prints."

Localization gap (new in this PR)

session_boot.rs:256 hardcodes the English word "login" in the compact candidate, while every other string in this function routes through tr(locale, MessageId::...). crates/tui/AGENTS.md:25-26 requires user-visible prose to go through tr(); only commands, key names, and glyphs are composed directly in code. The existing LaunchMcpNeedsSignInOne/Many message IDs are full sentences ("1 needs sign-in") from a different surface (the launch screen) and aren't a drop-in fit for this compact single-word slot, so this likely needs a new short MessageId rather than reuse.

Related wording issue: the compact "N login" phrasing assumes OAuth is the remedy for every NeedsLogin row, but auth_required_recovery_hint() (crates/tui/src/mcp/oauth.rs:104-134) shows that plugin-contributed and manually-authorized servers are told to fix a credential and run /mcp reload, not to log in. The full-form label (◆ auth required) stays neutral; the new compact label doesn't.

Tests

  • The needs-login-only compact form ("MCP · N login", no failures) isn't directly exercised by a narrow-budget assertion — only the mixed compact case is tested.
  • Minor: chip_separates_expired_logins_from_real_failures sets expired.auth_required = true on a row whose error text is already "401 Unauthorized", which error_text_looks_auth_required classifies as auth-required via the "401" substring alone (crates/tui/src/mcp/oauth.rs:75). Harmless but redundant.

Security

No secret handling, shell/exec, or input-validation surface touched.
· branch

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

PR cleanly separates NeedsLogin MCP servers from Failed servers in the session boot chip, uses the shared auth-required label, downgrades login-only chips to Attention, and adds tests for the two-state and mixed-state paths. The logic matches the PR description and is low risk.

Findings

  • [INFO] Compact chip hardcodes English 'login' (crates/tui/src/tui/session_boot.rs:256)
    The narrow candidate uses compact.push_str(&format!("{ITEM_SEPARATOR}{need_login} login"));, which hardcodes English 'login'. The full candidate uses the shared mcp_auth_required_state_label(), and connected/failed are translated in the full form. If the TUI is localized, the compact footer will show an English word; consider using an existing localized message or abbreviation if one is available.
  • [INFO] Test comment says 'compact' but exercises full-width candidate (crates/tui/src/tui/session_boot.rs:664)
    The updated test comment says 'The compact activity chip names a needs-login server...' but the assertion calls activity_notice(Locale::En, 100) and verifies the full candidate including the connected count. The wording is misleading.
  • [INFO] Compact needs-login-only branch lacks explicit coverage (crates/tui/src/tui/session_boot.rs:679)
    The mixed-state test covers the compact branch when both login and failure counts are present, and the first needs-login test covers the full-width branch. The compact path for need_login > 0 && failed == 0 (MCP · N login) is not explicitly asserted, despite the PR description implying both widths are covered for the single-state case.

Suggestions

  • crates/tui/src/tui/session_boot.rs:664 — The test calls activity_notice with width 100 and asserts the full candidate, so the comment should say full-width rather than compact.

            // The full-width activity chip names a needs-login server under the
    

Assessment

No blocking correctness issues found in the diff. The separation of NeedsLogin from Failed is implemented correctly and the severity change is appropriate. Minor follow-ups are test clarity and a possible compact-localization inconsistency.


Advisory review by Codewhale (codewhale review --pr 5938 --post, head 236cda03fb2484d5129314887dcfedb9b3f5b8b9). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

let mut compact = String::from("MCP");
if need_login > 0 {
full.push_str(&format!("{ITEM_SEPARATOR}{need_login} {auth_label}"));
compact.push_str(&format!("{ITEM_SEPARATOR}{need_login} login"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[INFO] Compact chip hardcodes English 'login'

The narrow candidate uses compact.push_str(&format!("{ITEM_SEPARATOR}{need_login} login"));, which hardcodes English 'login'. The full candidate uses the shared mcp_auth_required_state_label(), and connected/failed are translated in the full form. If the TUI is localized, the compact footer will show an English word; consider using an existing localized message or abbreviation if one is available.

// The compact activity chip counts a needs-login server with the
// failure band (the receipt-line renderer this pinned moved to the
// chip in the Tideline boot-surface refactor).
// The compact activity chip names a needs-login server under the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[INFO] Test comment says 'compact' but exercises full-width candidate

The updated test comment says 'The compact activity chip names a needs-login server...' but the assertion calls activity_notice(Locale::En, 100) and verifies the full candidate including the connected count. The wording is misleading.

mcp_auth_required_state_label()
)
);
assert!(!chip.text.contains("failed"), "{}", chip.text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[INFO] Compact needs-login-only branch lacks explicit coverage

The mixed-state test covers the compact branch when both login and failure counts are present, and the first needs-login test covers the full-width branch. The compact path for need_login > 0 && failed == 0 (MCP · N login) is not explicitly asserted, despite the PR description implying both widths are covered for the single-state case.

// The compact activity chip counts a needs-login server with the
// failure band (the receipt-line renderer this pinned moved to the
// chip in the Tideline boot-surface refactor).
// The compact activity chip names a needs-login server under the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The test calls activity_notice with width 100 and asserts the full candidate, so the comment should say full-width rather than compact.

Suggested change
// The compact activity chip names a needs-login server under the
// The full-width activity chip names a needs-login server under the

@Hmbown
Hmbown merged commit e5a8bd7 into main Sep 6, 2026
34 checks passed
@Hmbown
Hmbown deleted the fix/mcp-chip-need-login-5926 branch September 6, 2026 11:15
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