Skip to content

fix: shell default profile name type guard#687

Open
daewoongoh wants to merge 5 commits into
Zoo-Code-Org:mainfrom
daewoongoh:fix/shell-defaultProfileName-type-guard
Open

fix: shell default profile name type guard#687
daewoongoh wants to merge 5 commits into
Zoo-Code-Org:mainfrom
daewoongoh:fix/shell-defaultProfileName-type-guard

Conversation

@daewoongoh

@daewoongoh daewoongoh commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Related GitHub Issue

Closes: #686

Description

config.get<string>() in src/utils/shell.ts provides only a compile-time type hint — if a user sets a non-string value (number, boolean, array, object) in settings.json, it is returned as-is at runtime. This causes a TypeError when .toLowerCase() is subsequently called on defaultProfileName.

  • Add a typeof === "string" runtime type guard to coerce non-string values to null
  • Consolidate the duplicated getWindowsTerminalConfig, getMacTerminalConfig, and getLinuxTerminalConfig into a single generic getTerminalConfig<T>(platformKey)
  • Add test cases for non-string input handling (8 cases) and getTerminalConfig behavior (7 cases)

Test Procedure

  • Run vitest run src/utils/__tests__/shell.spec.ts — all 53 tests pass
  • Verified that non-string defaultProfileName scenarios (number, boolean, array, object) return the platform fallback shell without throwing TypeError

Pre-Submission Checklist

  • Issue Linked: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
  • Scope: My changes are focused on the linked issue (one major feature/fix per PR).
  • Self-Review: I have performed a thorough self-review of my code.
  • Testing: New and/or updated tests have been added to cover my changes (if applicable).
  • Documentation Impact: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Screenshots / Videos

N/A

Documentation Updates

  • [X ] Yes, documentation updates are required. (Please describe what needs to be updated or link to a PR in the docs repository).

Additional Notes

config.get<T>() does not guarantee type safety at runtime due to VS Code API behavior — runtime validation at the system boundary is necessary.

Get in Touch

hehegwk_23849

Summary by CodeRabbit

  • Tests

    • Expanded test coverage for terminal profile selection on Windows, macOS, and Linux, including cases where configuration values are missing, null, or of invalid types, plus fallback behavior when configuration access fails.
  • Refactor

    • Streamlined terminal configuration retrieval into a single shared flow that selects the correct platform profile data and applies consistent safe fallbacks on errors.

…oLowerCase()

config.get<string>() has no runtime type check, so non-string values
(number, boolean, array, object) from settings.json pass through and
cause a TypeError. Add typeof guard in each platform's terminal config
helper and add corresponding test cases.
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f8f6187-0920-4e5b-b47c-bac226670849

📥 Commits

Reviewing files that changed from the base of the PR and between ab8f32b and 9fbc9e1.

📒 Files selected for processing (2)
  • src/utils/__tests__/shell.spec.ts
  • src/utils/shell.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils/shell.ts
  • src/utils/tests/shell.spec.ts

📝 Walkthrough

Walkthrough

Replaces three platform-specific VS Code terminal config helpers with one typed getTerminalConfig function and adds test coverage for config access failures and invalid defaultProfileName values.

Changes

Terminal Config Refactor and Robustness

Layer / File(s) Summary
Generic getTerminalConfig helper and platform wiring
src/utils/shell.ts
Introduces PlatformProfilesMap and a generic getTerminalConfig that reads platform-specific default profile and profiles settings, returns safe fallbacks on VS Code API errors, and updates Windows, macOS, and Linux shell resolution paths to use it.
Shell config error and type-safety tests
src/utils/__tests__/shell.spec.ts
Adds getShell coverage for per-platform terminal profile resolution, undefined and null config values, thrown configuration access errors, and non-string defaultProfileName values across Windows, macOS, and Linux.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Zoo-Code-Org/Zoo-Code#239: Modifies the same src/utils/shell.ts and src/utils/__tests__/shell.spec.ts files around VS Code terminal defaultProfile and missing-profile behavior on Windows.

Suggested reviewers

  • taltas
  • JamesRobert20
  • navedmerchant

Poem

🐇 One helper now hops across every land,
With typed config paths held safely in hand.
When profiles go odd, the rabbit won’t fall—
Safe defaults stay ready for Windows, Mac, and all.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately highlights the runtime type-guard fix for shell default profile handling.
Description check ✅ Passed The PR description includes the linked issue, change summary, testing steps, checklist, and notes, so it matches the template well.
Linked Issues check ✅ Passed The changes directly address #686 by guarding non-string default profile values and adding coverage for invalid settings.
Out of Scope Changes check ✅ Passed The diff stays focused on shell config robustness and related tests, with no obvious unrelated code changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed due to a network error.


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.

@daewoongoh daewoongoh changed the title Fix/shell default profile name type guard fix: shell default profile name type guard Jun 22, 2026
@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread src/utils/shell.ts Outdated

function getLinuxTerminalConfig() {
function getTerminalConfig<T extends TerminalProfiles>(
platformKey: string,

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.

Could this be narrowed to "windows" | "osx" | "linux"? Passing "darwin" instead of "osx" would silently return empty config today. Terminal.getPlatformProfileKey() already returns this union.

Taking it a step further, a mapped-type approach would also eliminate the explicit <T> at each call site and prevent mismatches like getTerminalConfig<MacTerminalProfiles>("windows"):

Suggested change
platformKey: string,
platformKey: "windows" | "osx" | "linux",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I replaced the TerminalProfiles union with a PlatformProfilesMap, so getTerminalConfig() now accepts only "windows" | "osx" | "linux" and infers the matching profile map type from the platform key.
This removes the explicit generic type argument at each call site and makes mismatches like getTerminalConfig("windows") a compile-time error.

Comment thread src/utils/shell.ts Outdated
const profiles = config.get<LinuxTerminalProfiles>("profiles.linux") || {}
const rawProfileName = config.get<string>(`defaultProfile.${platformKey}`)
const defaultProfileName = typeof rawProfileName === "string" ? rawProfileName : null
const profiles = config.get<T>(`profiles.${platformKey}`) || ({} as T)

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.

Should this be ?? instead of ||? The rest of the codebase is moving toward nullish coalescing for config reads (e.g. Task.ts:3696, extension.ts:155), and ?? is more precise — only fires on null/undefined.

Suggested change
const profiles = config.get<T>(`profiles.${platformKey}`) || ({} as T)
const profiles = config.get<T>(`profiles.${platformKey}`) ?? ({} as T)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. I changed the profiles fallback from || to ?? so we only fall back to an empty profile map for null/undefined, which is more precise for config reads.

Comment on lines +510 to +536
it("macOS: handles numeric defaultProfileName without TypeError", () => {
Object.defineProperty(process, "platform", { value: "darwin" })
const mockConfig = {
get: vi.fn((key: string) => {
if (key === "defaultProfile.osx") return 1
if (key === "profiles.osx") return {}
return undefined
}),
}
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue(mockConfig as any)

expect(getShell()).toBe("/bin/zsh")
})

it("macOS: handles object defaultProfileName without TypeError", () => {
Object.defineProperty(process, "platform", { value: "darwin" })
const mockConfig = {
get: vi.fn((key: string) => {
if (key === "defaultProfile.osx") return {}
if (key === "profiles.osx") return {}
return undefined
}),
}
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue(mockConfig as any)

expect(getShell()).toBe("/bin/zsh")
})

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.

Two things about the macOS/Linux non-string tests:

  1. Asymmetric coverage — Windows tests all 4 non-string types (numeric, boolean, array, object) but macOS and Linux only test 2. The PR description commits to covering all four.

  2. Mutation resistance — these tests won't actually catch removal of the typeof guard. getMacShellFromVSCode never calls .toLowerCase() on defaultProfileName — it only uses it as an object key. So a non-string value silently misses the key lookup → undefinednormalizeShellPath returns null → falls back to /bin/zsh, which is exactly what the test asserts. Could you add a case where the non-string value, if left unguarded, would match a profile key (making the guarded vs. unguarded paths observably different)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added coverage for this in 9fbc9e1.

Changes made:

  • Added the missing boolean and array defaultProfileName cases for macOS and Linux so they now match the Windows non-string coverage.
  • Added mutation-resistant macOS/Linux tests where a numeric non-string value would otherwise match a real "1" profile key if the typeof guard were removed.

@github-actions github-actions Bot added the awaiting-author PR is waiting for the author to address requested changes label Jun 25, 2026
- Replace TerminalProfiles union with PlatformProfilesMap mapped type so
  the return type is inferred from the platform key, eliminating explicit
  type parameters at each call site and making mismatches a compile error
- Change || to ?? when falling back to empty profiles object
- Add JSDoc explaining the key constraint and inferred return type
- Add missing boolean/array non-string defaultProfileName test cases for
  macOS and Linux to match Windows coverage
- Add mutation-resistant tests for macOS and Linux that verify the typeof
  guard is load-bearing (numeric key matching a real profile entry)
@daewoongoh daewoongoh requested a review from edelauna June 25, 2026 09:30
@github-actions github-actions Bot added awaiting-review PR changes are ready and waiting for maintainer re-review and removed awaiting-author PR is waiting for the author to address requested changes labels Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-review PR changes are ready and waiting for maintainer re-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Shell detection crashes when terminal default profile setting is not a string

2 participants