Skip to content

fix: high-severity bug batch from comprehensive review#1646

Open
gfargo wants to merge 22 commits into
mainfrom
claude/code-review-github-issues-t1wry1
Open

fix: high-severity bug batch from comprehensive review#1646
gfargo wants to merge 22 commits into
mainfrom
claude/code-review-github-issues-t1wry1

Conversation

@gfargo

@gfargo gfargo commented Jul 12, 2026

Copy link
Copy Markdown
Owner

What

Six atomic fixes for the high-severity findings from a comprehensive codebase review (issues filed as #1577#1644), plus the 16 medium-severity fixes and the resolution of this PR's own review feedback. Each is a standalone commit; normally these would be separate PRs, but they were fixed together as a batch since they were triaged together.

Closes #1578
Closes #1584
Closes #1592
Closes #1580
Closes #1577
Closes #1579
Closes #1583
Closes #1586
Closes #1590
Closes #1595
Closes #1599
Closes #1604
Closes #1608
Closes #1616
Closes #1623
Closes #1597
Closes #1603
Closes #1609
Closes #1629
Closes #1582
Closes #1588
Closes #1593

How

Review feedback addressed

  • prCreate/config.ts now matches the cache <subcommand> pattern (required positional + native choices) instead of a hand-rolled .check().
  • tokenizer.ts's fallback no longer tries to positively match "newest" OpenAI ids by regex; defaults to o200k_base and only falls back further for ids that look pre-o200k.
  • worktreeDiffRefreshToken is now bumped centrally inside refreshWorktreeContext, not at each individual call site.
  • env.ts's three hand-synced places for provider-scoped API-key env vars collapsed into one lookup table.

Validation

  • npm test — full jest suite passes locally
  • New/updated behavior has test coverage throughout
  • schema.json regenerated — not needed, no config schema changes
  • Docs updated — not needed, no user-facing doc changes
  • Commits follow Conventional Commits (one per fix)

Notes

Remaining low-severity findings from the same review are tracked as separate open issues and will follow in subsequent PRs.

@gfargo gfargo left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Ran a high-effort review (8 finder angles: line-by-line scan, removed-behavior audit, cross-file trace, reuse/duplication, simplification, efficiency, altitude, conventions) against this diff, plus manual tracing of the more consequential candidates.

No blocking issues. Full test suite (334 suites / 4809 tests), tsc --noEmit, and eslint were all re-verified clean on this branch. The six fixes are each correct, well-scoped, and covered by new tests; cross-file tracing confirmed no call site is broken by any of the six changes.

Six non-blocking findings, all polish/robustness — left as inline comments:

  1. worktreeDiffRefreshToken is bumped ad hoc at 4 sites instead of centrally inside refreshWorktreeContext (unlike the sibling prDiffRefreshToken, which is centralized). Traced every other caller — none currently reproduce #1579, but the safety is implicit, not enforced.
  2. toggleSelectedFileStage's exemption from the token bump relies on the same unenforced invariant, localized to this file.
  3. prCreate's 'pr [action]' + hand-rolled .check() diverges from the sibling cache/config.ts pattern (required positional + native choices) for the same underlying yargs footgun.
  4. tokenizer.ts's fallback regex doesn't actually cover its own motivating cases (Azure custom deployment names, gpt-4.1/gpt-4.5) — still strictly better than crashing, just an approximation gap worth a follow-up.
  5. env.ts's env-key handling is now duplicated across 3 places that must stay in sync by hand; this PR extended that pattern rather than collapsing it.
  6. Minor dep-array inconsistency in useWorktreeStageActions.ts — cosmetic only (setState setters are referentially stable), not a functional bug.

None of these block merging; happy to see them addressed here or as quick follow-ups.

Comment thread src/commands/prCreate/config.ts
Comment thread src/lib/utils/tokenizer.ts
Comment thread src/workstation/runtime/hooks/useWorktreeStageActions.ts Outdated
Comment thread src/workstation/runtime/hooks/useWorktreeStageActions.ts
Comment thread src/lib/config/services/env.ts Outdated
gfargo pushed a commit that referenced this pull request Jul 12, 2026
Resolves all five non-blocking review comments from the PR #1646 code
review so the batch can merge cleanly:

- prCreate/config.ts: switch `pr [action]` + a hand-rolled `.check()` to
  a required `pr <action>` positional with native yargs `choices`,
  matching the `cache <subcommand>` pattern already used elsewhere for
  the same "verb + required-choice positional" shape.
- tokenizer.ts: the unrecognized-model fallback no longer tries to
  positively match "newest" OpenAI ids by regex (which missed Azure
  custom deployment aliases and ids newer than the pinned tiktoken
  release, e.g. gpt-4.1/gpt-4.5). Default to o200k_base — the more
  common recent encoding — and only fall back further to cl100k_base
  for ids that look like pre-o200k OpenAI models.
- useWorktreeStageActions.ts / useContextRefresh.ts: move the
  `worktreeDiffRefreshToken` bump out of four individual stage/revert
  call sites and into `refreshWorktreeContext` itself, mirroring how
  `prDiffRefreshToken` is already bumped centrally inside
  `refreshContext`. Every current and future `refreshWorktreeContext`
  caller now gets the reload signal automatically instead of each call
  site having to remember it.
- env.ts: collapse the three hand-synced places for provider-scoped
  API-key env vars (the `envKeys` array, a boolean OR-chain, and the
  `handleServiceEnvVar` switch) into one `PROVIDER_API_KEY_ENV_VARS`
  lookup table used for both membership-testing and provider routing.
gfargo added 22 commits July 12, 2026 18:57
The panel copy was never updated when #1352 rebound mark-good from g to
y (g stays free as the chord prefix). Fixes #1578.
…onment

The env loader had no handling for OPENAI_API_KEY and no Anthropic key
variable at all, while init, doctor, the missing-key screen, and the
error formatter all instruct users to set exactly those variables.
OPEN_AI_KEY is kept as a deprecated alias; OPENAI_API_KEY wins when both
are set. Fixes #1584.
encoding_for_model() throws for any model id outside tiktoken's
compiled-in map, which killed every LLM command before any work
happened for custom baseURL models, Azure deployment names, and OpenAI
ids newer than the pinned tiktoken release. Falls back to o200k_base or
cl100k_base by model-id shape instead. Fixes #1592.
'pr create' registered 'create' as an unconstrained required positional,
not a literal subcommand, so 'coco pr close'/'list'/'view' (or any typo)
silently ran PR creation with no confirmation. 'pr [action]' now
validates against choices: ['create'] and a bare 'pr' names the valid
action instead of yargs' generic arity error. Fixes #1580.
In non-interactive mode the plan was printed via the muted logger, then
an inquirer confirm asked to apply commits the user never saw — hanging
forever on an open, silent stdin. The default path now degrades to a
plan preview (no prompt) when non-interactive, and writes the plan
directly to stdout in the interactive path so it's never hidden behind
an independently-muted logger. Fixes #1577.
…tation

Hunk-level stage/revert cleared the cached diff/hunks expecting
rehydration, but the loaders only re-fire on the file's own
indexStatus/worktreeStatus — unchanged when reverting one of several
unstaged hunks, or staging the 2nd+ hunk of an already-MM file. The
pane went permanently blank with every further action reporting "no
hunk selected". Adds worktreeDiffRefreshToken, bumped by the four
hunk/line-level handlers and threaded into both diff-hydration effects,
mirroring the prDiffRefreshToken pattern (OSS-452). Fixes #1579.
commandExit(0) on an empty LLM response masked hard failures as success
in CI. Also routes the failure message through logger.error so it's
visible even in quiet/non-interactive mode. Fixes #1583.
…ircuit on a clean repo

noResult() threw NO_CHANGES_DETECTED, making the handler's commandExit(0)
unreachable and escaping to the generic error formatter with exit 1.
Removed the throw so the handler's own exit runs, matching changelog's
"no commits" behavior (#1586).

Separately, the default 'current' timeframe factory always returned
non-empty template-prefixed strings even when every section was empty,
so a completely clean repo never reached noResult and burned an LLM
call on fabricated content. Added the same empty-change-set guard
review already has (#1608).
…exclusivity check

--range only matched coco's <from>:<to> form; a bare HEAD~5..HEAD was
silently dropped and fell through to current-branch mode with no
warning. Now accepts both : and ../... separators, and rejects
unparseable values instead of ignoring them. Also added --range to the
mutual-exclusion check so combining it with --branch/--tag errors
consistently instead of silently favoring range. Fixes #1590.
The --noDiff factory mapped every git-status entry, including unstaged
and untracked files, into the "Staged files" summary fed to the model
— describing changes that wouldn't be part of the commit. Reuses
getChanges().staged (the same source the regular diff path uses),
which also restores ignoredFiles/ignoredExtensions filtering that the
raw git-status mapping bypassed. Fixes #1595.
… silently disabling the CI gate

yargs coerces an unparseable --severity value to NaN, and
typeof NaN === 'number' — so a typo (or any non-integer/out-of-range
value) passed the old guard and every severity comparison silently
evaluated false, disabling the CI gate with no signal. The builder now
validates severity is an integer between 1 and 10 up front; the
handler's own gate additionally uses Number.isFinite as a backstop.
Fixes #1599.
…ntinel text

When the current branch has no commits ahead of base, changelog's clean
-exit sentinel (CommandExitError(0), default message "Command exited
with code 0") was relayed verbatim as the PR-body generation failure,
so users saw a red error that literally described the exit code
instead of an explanation. runPullRequestBodyWorkflow now special-cases
a zero-code CommandExitError and returns the existing curated message.
Fixes #1604.
loadProjectJsonConfig tested bare relative filenames, resolving against
process.cwd() — while coco init writes .coco.json at the repo root.
Running any command from a subdirectory silently dropped the entire
project config with no warning.

Extracted the git-toplevel resolution (with cwd fallback) that
diffSummaryCache.ts already used for the same subdirectory-invocation
problem (#1463) into a shared resolveGitRepoRoot utility, and reuse it
here so project config loading finds the same file coco init wrote
regardless of invocation directory. Fixes #1616.
Choosing ".env" at coco init produced a file coco could never read
back: the writer emitted un-prefixed flattened names while the env
loader only reads COCO_*-prefixed names, and nothing loaded a .env
file into process.env in the first place. Init reported success while
every chosen setting silently never applied.

Removed the option (and the now-dead appendToEnvFile/flattenObject
writer) rather than building out a new .env-loading + trust-boundary
code path — .coco.json already works and is the recommended choice.
Fixes #1623.
git status --porcelain (v1, without -z) C-quotes "unusual" paths under
the default core.quotePath=true — any non-ASCII filename, or one
containing a quote/tab, round-tripped its literal quotes and octal
escapes into the subsequent git add/restore/diff pathspec, which then
failed to match. -z disables quoting entirely and reports rename/copy
origin paths as a separate trailing NUL-terminated field instead of an
in-line " -> " separator, which also removes the ambiguity of a
filename containing that literal substring.

Applied the same fix to operationData.ts's conflict-file parser, which
duplicated the identical v1 text-parsing logic (and the same bug) for
merge-conflict detection. Fixes #1597.
getDiff's default-path git.diff() calls passed the file path as a bare
argument, without the -- separator used everywhere else in the
codebase. A tracked file literally named like a ref (a branch, HEAD, a
tag) was resolved as a revision instead of a path, silently diffing
the wrong thing and feeding it to the commit-message/review LLM with
no error surfaced. Fixes #1603.
…lt-branch lookups

Two halves of the same host-scoping gap: getDefaultBranch queried
gh's default host (github.com) with a bare owner/name slug regardless
of which host the remote actually lives on, and the PR/issue list
surfaces gated on a github.com-only remote parser — rejecting a
GitHub Enterprise remote outright even though the provider layer
already classifies it correctly and probes auth against the right
host.

getDefaultBranch now uses the full host-qualified URL when the
repository isn't on github.com. issuesListData/pullRequestListData/
pullRequestData now resolve the repository via the same host-aware
provider detection getProviderOverview already uses (heuristic +
forgeHosts overrides), and pass the resolved host into getGhStatus
instead of defaulting to github.com. Fixes #1609.
The OpenAI provider's config object omitted maxConcurrency, so
service.maxConcurrent was silently ignored and the summarize fan-out ran
unthrottled while every other provider honored the setting.

Fixes #1629.
…es against repo-frame races

force-delete-branch, force-push-current-branch/force-push-selected-branch,
and the delete-branch/drop-stash setMarks freeze were the only post-await
dispatch sites in runWorkflowAction missing the frameChanged guard #1429
established for recovery prompts. A repo-frame push/pop that races one of
these slow workflows could re-target the follow-up confirm or mark freeze
at the wrong repo frame.

Fixes #1582.
selectedWorktreeFile resolved by raw index into the flattened status
list with no clamp, and nothing rectifies the index when a refresh
(revert, external stage) shrinks the visible list — the cursor and
row highlight vanished and file actions no-op'd even with files still
on screen. Clamp at every resolution site (buildStatusSurfaceData's
pure core and memo, the status renderer's cursor row, and
useInputHandler's worktreeSelectedPath) so highlight, input target, and
action target keep agreeing, matching the clamp pattern every other
list selector already uses.

Fixes #1588.
…g them

startChangelogView, startCommitSplit, applyCommitSplit,
startCreatePullRequest, and startConflictResolution awaited their
workflow with no catch (or, for applyCommitSplit, no try at all) around
the awaited call. Any unexpected throw — as opposed to the workflow's
own `{ ok: false }` result — would escape as an unhandled promise
rejection and permanently strand the surface's loading state, with no
way to retry short of quitting. useAiCommitDraftActions already carries
this defensive catch for itself; mirror it in the four sibling hooks so
an escaped throw surfaces an error and clears loading instead.

Fixes #1593.
Resolves all five non-blocking review comments from the PR #1646 code
review so the batch can merge cleanly:

- prCreate/config.ts: switch `pr [action]` + a hand-rolled `.check()` to
  a required `pr <action>` positional with native yargs `choices`,
  matching the `cache <subcommand>` pattern already used elsewhere for
  the same "verb + required-choice positional" shape.
- tokenizer.ts: the unrecognized-model fallback no longer tries to
  positively match "newest" OpenAI ids by regex (which missed Azure
  custom deployment aliases and ids newer than the pinned tiktoken
  release, e.g. gpt-4.1/gpt-4.5). Default to o200k_base — the more
  common recent encoding — and only fall back further to cl100k_base
  for ids that look like pre-o200k OpenAI models.
- useWorktreeStageActions.ts / useContextRefresh.ts: move the
  `worktreeDiffRefreshToken` bump out of four individual stage/revert
  call sites and into `refreshWorktreeContext` itself, mirroring how
  `prDiffRefreshToken` is already bumped centrally inside
  `refreshContext`. Every current and future `refreshWorktreeContext`
  caller now gets the reload signal automatically instead of each call
  site having to remember it.
- env.ts: collapse the three hand-synced places for provider-scoped
  API-key env vars (the `envKeys` array, a boolean OR-chain, and the
  `handleServiceEnvVar` switch) into one `PROVIDER_API_KEY_ENV_VARS`
  lookup table used for both membership-testing and provider routing.
@gfargo gfargo force-pushed the claude/code-review-github-issues-t1wry1 branch from f00a3ae to 587c49d Compare July 12, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant