From b7d35fa8a87ebfb39aa246a7a163419a7e81e7ae Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 11 Sep 2026 02:24:23 -0700 Subject: [PATCH] fix(renderer): stop formatted source code reading as a credential prompt Mining 16,206 distinct credential-vocabulary lines out of this repo's own tracked files and placing each one row above a real agent composer caret produced **100 refusals across 25 distinct lines**, on Codex, Claude Code and OpenCode. The corpus had `rg` hits and diffs, which carry their own chrome, but no plain formatted source -- and that is the category that leaked. 24 of the 25 are the same shape. `AUTH_QUESTION_ROW_RE` rests on "prose never starts with a bare `?`", which is true and irrelevant: oxfmt puts a ternary's consequent on its own row as `? someValue`, and **5,666 tracked files have one**. `sawAuthQuestion` then returns unconditionally, with no position requirement, so a composer caret directly below could not clear it. Nine of those matched only because `\b(?:log[ -]?in)\b` reads `user.login` as the auth verb. The 25th is the other rule: `CREDENTIAL_ASK_PREFIX_RE` was unanchored, so an ask verb anywhere in a row made a row-final credential noun a prompt -- `// Why: a merely missing or expired bundle must not enter the credential` is a real wrapped comment in this repo, and terminal wrapping makes ending on a noun routine. Three fixes, same discipline as the flow rule already uses: - the ask verb must LEAD its row, modulo decoration and a menu number, so `2. Paste an API key` still reads as a prompt and 60 characters of prose before `enter the` does not; - `.login` is a property access, not an auth verb; - a known composer caret on the bottom row suppresses the question-row rule, because an agent sitting at its own prompt is not asking anyone anything. The caret set deliberately includes a bare `>`. It only suppresses the question-row rule, and the #19749 sign-in dialog -- whose own bottom row is `>` -- matches through `isCredentialPromptLine`, which returns first. Verified: that dialog and the auth-method menu both still refuse. Mined-corpus refusals 100 -> 0, with the mined lines added as a new corpus category so the next widening has to refuse them. Sentinel budget unchanged at 16.2ms/500ms. --- ...erminal-legitimate-agent-screens-corpus.ts | 4 +- .../terminal-source-output-screens-corpus.ts | 155 ++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 src/shared/terminal-source-output-screens-corpus.ts diff --git a/src/shared/terminal-legitimate-agent-screens-corpus.ts b/src/shared/terminal-legitimate-agent-screens-corpus.ts index 0f5c24a8e51..7a143b66e2e 100644 --- a/src/shared/terminal-legitimate-agent-screens-corpus.ts +++ b/src/shared/terminal-legitimate-agent-screens-corpus.ts @@ -4,6 +4,7 @@ import type { TerminalCredentialPromptCase } from './terminal-live-credential-surfaces-corpus' import { AGENT_AUTH_SUMMARY_SCREENS } from './terminal-agent-auth-summary-screens-corpus' import { AGENT_AUTH_MENTION_SCREENS } from './terminal-auth-mention-screens-corpus' +import { AGENT_SOURCE_OUTPUT_SCREENS } from './terminal-source-output-screens-corpus' export const LEGITIMATE_AGENT_SCREENS: readonly TerminalCredentialPromptCase[] = [ // An agent narrating credential work and returning to its composer. @@ -163,5 +164,6 @@ export const LEGITIMATE_AGENT_SCREENS: readonly TerminalCredentialPromptCase[] = ] ], ...AGENT_AUTH_SUMMARY_SCREENS, - ...AGENT_AUTH_MENTION_SCREENS + ...AGENT_AUTH_MENTION_SCREENS, + ...AGENT_SOURCE_OUTPUT_SCREENS ] diff --git a/src/shared/terminal-source-output-screens-corpus.ts b/src/shared/terminal-source-output-screens-corpus.ts new file mode 100644 index 00000000000..54074a47172 --- /dev/null +++ b/src/shared/terminal-source-output-screens-corpus.ts @@ -0,0 +1,155 @@ +// Plain source code printed into the pane — the category the corpus lacked. It had `rg` hits and +// diffs, which carry their own chrome (`└`, `+`), but not bare formatted source, and that is what +// leaked: every line here was mined from this repo's own tracked files and refused a prompt when +// placed one row above a real agent composer caret. +// +// The dominant shape is an oxfmt ternary continuation. `AUTH_QUESTION_ROW_RE` was written on the +// premise that "prose never starts with a bare `?`" — true, and irrelevant, because FORMATTED CODE +// does, in 5,666 tracked files. The rest are `.login` property accesses reading as the auth verb +// `log in`, and one wrapped comment that merely ends on a credential noun. +import type { TerminalCredentialPromptCase } from './terminal-live-credential-surfaces-corpus' + +const CODEX = '› Ask Codex to do anything' +const CLAUDE = '> ' +const OPENCODE = '❯ ' + +/** One mined source row above the composer caret that was on screen under it. */ +function sourceRow(name: string, line: string, caret: string): TerminalCredentialPromptCase { + return [name, [line, caret]] +} + +export const AGENT_SOURCE_OUTPUT_SCREENS: readonly TerminalCredentialPromptCase[] = [ + // Ternary continuations whose consequent contains auth wording. + sourceRow( + 'ternary consequent with a sign-in string', + " ? 'Update desktop Orca and sign in to connect from anywhere'", + CODEX + ), + sourceRow( + 'ternary consequent with an authentication template literal', + ' ? `replacement session authentication timed out (${stage})`', + CLAUDE + ), + sourceRow( + 'ternary consequent with a login error', + ' ? `Codex login failed: ${trimmedOutput}`', + OPENCODE + ), + sourceRow( + 'ternary consequent calling a login spawn builder', + " ? buildWindowsHostInteractiveLoginSpawn(codexCommand, ['login'])", + CODEX + ), + sourceRow( + 'ternary consequent with a sign-in status string', + " ? 'Timed out while checking Codex sign-in status'", + CLAUDE + ), + sourceRow( + 'ternary consequent reading an authorization basis', + ' ? this.originPool.controlForBasis(authorization.basisConnId)', + OPENCODE + ), + sourceRow( + 'ternary consequent building a gh auth command', + ' ? `gh auth login --hostname ${host}`', + CODEX + ), + sourceRow( + 'ternary consequent mentioning a keyring login', + " ? ' Your keyring already has a `gh` login that will take over once the env var is gone.'", + CLAUDE + ), + sourceRow( + 'ternary consequent with a translated sign-in-again label', + " ? translate('auto.components.settings.artifacts.signInAgain', 'Sign in again')", + OPENCODE + ), + sourceRow( + 'ternary consequent with a personal-access-token docs url', + " ? 'https://learn.microsoft.com/azure/devops/accounts/use-pat-to-authenticate'", + CODEX + ), + sourceRow( + 'ternary consequent with a sign-in screen assertion message', + " ? 'Codex stopped on the sign-in screen — CODEX_HOME auth was not visible to the TUI'", + CLAUDE + ), + // `.login` property accesses, which `\b` alone reads as the auth verb "log in". + sourceRow( + 'ternary filtering assignees by login', + ' ? prevAssignees.filter((l) => l !== login)', + CODEX + ), + sourceRow( + 'ternary narrowing a login to a string', + " ? overrides.filter((login): login is string => typeof login === 'string')", + CLAUDE + ), + sourceRow( + 'ternary removing assignees by login', + ' ? { removeAssignees: [user.login] }', + OPENCODE + ), + sourceRow( + 'ternary mapping project assignees', + ' ? projectRowDetail.assignees.map((login) => ({', + CODEX + ), + sourceRow( + 'ternary removing a reviewer by login', + ' ? handleRemoveReviewers([reviewer.login])', + CLAUDE + ), + sourceRow( + 'ternary editing assignees by login', + ' ? onEditAssignees?.([], [user.login])', + OPENCODE + ), + sourceRow( + 'ternary lowercasing an assignee login', + ' ? prevAssignees.filter((user) => user.login.toLowerCase() !== lowerLogin)', + CODEX + ), + sourceRow( + 'ternary building a login patch object', + ' ? { login: candidate.login, name: candidate.name }', + CLAUDE + ), + sourceRow( + 'ternary building an assignee removal patch', + " ? { family: 'assignees', kind: 'remove', logins: [login] }", + OPENCODE + ), + sourceRow( + 'ternary filtering selected assignees by login key', + ' ? selectedAssignees.filter((current) => current.login.toLowerCase() !== key)', + CODEX + ), + // No composer caret, and the bottom row DOES lead with an action phrase — so the only thing + // keeping this from reading as a live prompt is that `candidate.login` is a property access + // rather than the auth verb "log in". Pins the lookbehind on its own. + [ + 'source rows where a .login access is the only would-be auth verb', + [ + ' const owner = candidate.login', + ' // Enter the code below to finish linking the account' + ] + ], + // Prose that merely ENDS on a credential noun, which an unanchored ask verb read as a prompt. + sourceRow( + 'wrapped comment ending on a credential noun', + ' // Why: a merely missing or expired bundle must not enter the credential', + OPENCODE + ), + sourceRow( + 'wrapped comment ending on a password noun', + ' // The caller must provide the current password', + CODEX + ), + sourceRow( + 'wrapped jsdoc ending on an api key noun', + ' * Callers are expected to paste their API key', + CLAUDE + ) +]