fix: v0.2.0 — autoplan ship-blockers, beginner mode, 258/258 tests#9
fix: v0.2.0 — autoplan ship-blockers, beginner mode, 258/258 tests#9
Conversation
- Scoring: use typed dimension keys instead of fragile display-name
string matching (fixes 9 previously-failing tests)
- Error handling: wrap each scanner in tryScan() so one failure
doesn't crash the whole tool
- Error handling: top-level try/catch in CLI with exit code 1
- Remove hardcoded settings_validation: { valid: true } — now
explicitly marks schema validation as not yet implemented
- Cap efficiency scanner to 20 most recent session files to
prevent OOM on power users
- Remove /tmp fallback for HOME env var across 11 call sites in
6 files — now throws instead of silently using shared /tmp
- Replace python3 with node -e in generated hooks (node is
guaranteed present, python3 is not)
- Add secret redaction regex to audit log hooks before writing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Show "GETTING STARTED" panel when score < 20 or fewer than 2 dimensions scored, with 3 actionable steps for new users - Show "Scored: N/4 dimensions" prominently after overall score, listing which dimensions were skipped and why (no data vs scanner failed) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace generic "linters vs X-Ray" table with specific comparison against /refine, cclint, and generic linters. Shows X-Ray's differentiators: safety-first weighting, source-grounded checks, score history, and auto-fix with rollback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tils DRY fix: readJson was duplicated 4x across scanner files, getHome and getSettingsLocations were duplicated 2-3x each. Now imported from src/scan/utils.ts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add scan-fix-rescan integration test that validates the viral thesis: scan a bad setup, apply fixes, rescan, assert improvement - Fix deepMerge tests: arrays replace (not dedupe), matching impl - Fix archetype tests: fallback is 'unknown' not 'ts-lib' - Fix dispatch test: async cost is 1.26 (3 roles x 0.42) - Fix capability test: scanner now has 7 checks not 4 258 tests, 258 pass, 0 fail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bump version to 0.2.0 (ship-blocker fixes, beginner mode, score comparability, competitive positioning, shared utils, 258/258 tests passing) - .npmignore: exclude 15 orchestrator modules not used by Phase 1 (keeps detect-archetype + config which scan/ depends on) - Exclude dist/evaluator/, dist/genotype/, dist/promoter/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR updates the package version to 0.2.0, introduces shared utility functions for settings and home directory resolution across scan modules, adds error handling to XRay scanning via a Changes
Sequence Diagram(s)The changes in this PR do not meet the criteria for sequence diagram visualization. While the PR introduces error handling and modular refactoring, the core interactions remain straightforward: local utility imports, error wrapping around existing scanners, and standard test/fix workflows without significantly altered control flow across multiple distinct components. Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
README.md (1)
88-95: Add verifiability guardrails for competitor claims in this table.This comparison is clear, but several competitor-specific claims are time-sensitive. Please add a short “as of YYYY-MM-DD” note and link/source criteria (or methodology) for
/refineandcclintcolumns so the table doesn’t drift into stale or disputable marketing copy.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 88 - 95, Add a verifiability guardrail for the comparison table by appending a short “as of YYYY-MM-DD” timestamp and a brief source/methodology note for the /refine and cclint columns; update the table caption or the final row under the table (referencing the table header labels "X-Ray", "/refine", and "cclint") to include the date and a parenthetical link or citation token for each competitor that explains the criteria used (e.g., feature check, documentation page, or test method) so readers can trace and update the claims.src/scan/cli.ts (1)
141-145: Error handling loses stack trace for debugging.The catch block extracts only
err.message, which loses the stack trace. For production CLI use this is acceptable, but consider adding a--verboseorDEBUGenv flag that logs the full error stack to aid debugging:💡 Optional enhancement
} catch (err) { const msg = err instanceof Error ? err.message : String(err); console.error(`\n[xray] Error: ${msg}`); + if (process.env.DEBUG || flags.has("--verbose")) { + console.error(err); + } process.exit(1); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/scan/cli.ts` around lines 141 - 145, The catch block in src/scan/cli.ts currently prints only err.message and loses the error stack; update the handler that catches "err" to print the full stack when verbose/debugging is enabled (e.g., via a new CLI flag like --verbose or an environment flag DEBUG), falling back to the concise message when not enabled; specifically, augment the catch around the CLI main flow so that it checks the flag and logs err.stack (or the error object) to console.error when set, otherwise keep the existing short message, and still call process.exit(1).src/fix/safety-fixer.ts (1)
154-157: LGTM, but note code duplication.The fail-fast check is necessary here since
homeis used at line 165 for theallowWritepath. However,resolveSettingsTargetis now duplicated acrosssafety-fixer.ts(line 20) andhook-generator.ts(line 18) with identical logic. Given the PR already extractedreadJson,getHome, andgetSettingsLocationstosrc/scan/utils.ts, consider consolidating this function there as well in a follow-up.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/fix/safety-fixer.ts` around lines 154 - 157, There is a duplicated resolveSettingsTarget implementation in safety-fixer.ts and hook-generator.ts; extract and consolidate it into the existing utils module that already contains readJson, getHome and getSettingsLocations, export it (preserving its current signature), then update safety-fixer.ts and hook-generator.ts to import and use the shared resolveSettingsTarget from utils; ensure behavior for the failing HOME/USERPROFILE check stays the same and update any imports/tests that reference the old local implementations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.npmignore:
- Around line 40-48: The package publish rules are inconsistent: package.json
explicitly lists dist/orchestrator/adopt.js and dist/orchestrator/history.js but
.npmignore contains patterns dist/orchestrator/adopt.* and
dist/orchestrator/history.* which exclude them from the tarball; fix by choosing
one of two actions—either remove the adopt.js and history.js entries from the
"files" array in package.json if they should not be published, or delete/adjust
the dist/orchestrator/adopt.* and dist/orchestrator/history.* lines from
.npmignore so those files are included; after making the change, verify with npm
pack --dry-run to confirm the tarball contains (or excludes) the intended files.
In `@src/fix/hook-generator.ts`:
- Line 64: The shell pipeline that sets CMD in hook-generator.ts (the line
starting with CMD=$(echo "$INPUT" | node -e ...)) silently falls back to an
empty command if node is not installed because of the "|| true" and redirection;
update the hook so it first guards for a Node.js runtime (e.g., check
availability of `node` or its version) and fail non‑zero with an explicit error
if not found, or restore a fallback parser (e.g., `python3`) only after
validating its presence; ensure the guard is adjacent to the CMD assignment so
the PreToolUse check never proceeds with an empty/unknown command when Node is
missing.
In `@src/scan/__tests__/integration.test.ts`:
- Line 15: The test currently reuses a single TMP directory (const TMP) for both
the fake user HOME and the repoRoot which causes getSettingsLocations() to
resolve user and project settings to the same file; change the setup to create
two distinct temp paths (e.g., TMP_HOME and TMP_REPO) and use TMP_HOME as
process.env.HOME and TMP_REPO as the repoRoot used in the tests (and update any
test helpers, cleanup, and assertions that reference TMP to use the appropriate
TMP_HOME or TMP_REPO); ensure references in this file to getSettingsLocations(),
any setup/teardown code, and all places that previously used TMP (lines noted in
the review) are adjusted so user-level files live under TMP_HOME/.claude and
project-level files under TMP_REPO/.claude.
In `@src/scan/efficiency.ts`:
- Around line 92-102: The function listSessionFiles currently fails the whole
mtime pass if any single statSync throws; change it to perform per-file statSync
inside a try/catch (or use a safe helper) so unreadable/missing .jsonl files are
skipped rather than causing an empty result, then sort the successfully-statted
entries by mtime, slice to 20 and return their paths; update the logic around
readdirSync/map/statSync (referencing listSessionFiles, readdirSync, statSync)
to skip errors (optionally logging them) and ensure the 20-most-recent cap is
applied after filtering.
In `@src/scan/index.ts`:
- Around line 78-87: The current code hardcodes settings_validation to { valid:
false, errors: [...] } causing runXRay() to always report a validation error;
change runXRay() (or the code that constructs settings_validation) to avoid
fabricating failures—either omit settings_validation when no validator ran or
set an explicit sentinel (e.g., settings_validation: { implemented: false } or
settings_validation: { status: "not_implemented", errors: [] }) so downstream
consumers can distinguish "not implemented" from a real validation failure;
update any consumers/tests that expect a boolean valid to handle the explicit
not-implemented representation.
In `@src/scan/utils.ts`:
- Around line 11-17: readJson currently returns any JSON value; update it so
after parsing it validates the top-level value is a plain object (non-null,
typeof === "object", and not an Array via Array.isArray) and return that
Record<string, unknown> only when valid, otherwise return null; keep the
try/catch around JSON.parse and treat parse errors the same, and reference the
readJson function to add the type checks and null-returns for non-object JSON
values.
- Around line 20-25: getHome currently reads only HOME/USERPROFILE env vars
which can fail even when the system can resolve a home directory; update getHome
to call Node's os.homedir() (importing os) and return that value, falling back
to throwing the same error if os.homedir() returns falsy. Locate the getHome
function and replace the manual env lookup with a call to os.homedir(), keep the
function signature and error behavior (throw "HOME or USERPROFILE environment
variable is required" if no home is found) so callers (settings resolution,
project detection, automation checks) continue to receive a string or an error.
---
Nitpick comments:
In `@README.md`:
- Around line 88-95: Add a verifiability guardrail for the comparison table by
appending a short “as of YYYY-MM-DD” timestamp and a brief source/methodology
note for the /refine and cclint columns; update the table caption or the final
row under the table (referencing the table header labels "X-Ray", "/refine", and
"cclint") to include the date and a parenthetical link or citation token for
each competitor that explains the criteria used (e.g., feature check,
documentation page, or test method) so readers can trace and update the claims.
In `@src/fix/safety-fixer.ts`:
- Around line 154-157: There is a duplicated resolveSettingsTarget
implementation in safety-fixer.ts and hook-generator.ts; extract and consolidate
it into the existing utils module that already contains readJson, getHome and
getSettingsLocations, export it (preserving its current signature), then update
safety-fixer.ts and hook-generator.ts to import and use the shared
resolveSettingsTarget from utils; ensure behavior for the failing
HOME/USERPROFILE check stays the same and update any imports/tests that
reference the old local implementations.
In `@src/scan/cli.ts`:
- Around line 141-145: The catch block in src/scan/cli.ts currently prints only
err.message and loses the error stack; update the handler that catches "err" to
print the full stack when verbose/debugging is enabled (e.g., via a new CLI flag
like --verbose or an environment flag DEBUG), falling back to the concise
message when not enabled; specifically, augment the catch around the CLI main
flow so that it checks the flag and logs err.stack (or the error object) to
console.error when set, otherwise keep the existing short message, and still
call process.exit(1).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0f65dd81-ea7a-4a68-9a0a-a530380980ef
📒 Files selected for processing (21)
.npmignoreREADME.mdpackage.jsonsrc/fix/__tests__/index.test.tssrc/fix/hook-generator.tssrc/fix/safety-fixer.tssrc/orchestrator/__tests__/config.test.tssrc/orchestrator/__tests__/detect-archetype.test.tssrc/orchestrator/__tests__/dispatch.test.tssrc/scan/__tests__/capability.test.tssrc/scan/__tests__/integration.test.tssrc/scan/automation.tssrc/scan/capability.tssrc/scan/cli.tssrc/scan/efficiency.tssrc/scan/index.tssrc/scan/render.tssrc/scan/safety.tssrc/scan/scoring.tssrc/scan/types.tssrc/scan/utils.ts
| dist/orchestrator/adopt.* | ||
| dist/orchestrator/cli.* | ||
| dist/orchestrator/complexity.* | ||
| dist/orchestrator/conventions.* | ||
| dist/orchestrator/coverage.* | ||
| dist/orchestrator/crew-pipeline.* | ||
| dist/orchestrator/dispatch.* | ||
| dist/orchestrator/doc-coverage.* | ||
| dist/orchestrator/history.* |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify what will actually be published
npm pack --dry-run --json > /tmp/pack.json
echo "== Target orchestrator files in packed tarball =="
jq -r '.[0].files[].path' /tmp/pack.json | rg -n '^package/dist/orchestrator/(adopt|history|detect-archetype)\.js$' || true
echo
echo "== Any ignored phase-2 folders still included? =="
jq -r '.[0].files[].path' /tmp/pack.json | rg -n '^package/dist/(evaluator|genotype|promoter)/' || trueRepository: ArshyaAI/claude-code-xray
Length of output: 169
🏁 Script executed:
# Check package.json files list
echo "=== package.json lines 33-43 ==="
sed -n '33,43p' package.json
echo
echo "=== .npmignore lines 40-48 ==="
sed -n '40,48p' .npmignore
echo
echo "=== Check if dist/orchestrator files exist ==="
ls -la dist/orchestrator/ 2>/dev/null | head -20 || echo "dist/orchestrator/ does not exist or is empty"Repository: ArshyaAI/claude-code-xray
Length of output: 717
Reconcile publish manifest rules—adopted entry points are excluded from tarball.
Your package.json files list (lines 38–40) explicitly includes dist/orchestrator/adopt.js and dist/orchestrator/history.js, but .npmignore (lines 40 and 48) blocks both via dist/orchestrator/adopt.* and dist/orchestrator/history.*. This mismatch prevents those files from being packaged: neither file appears in the tarball contents when using npm pack --dry-run.
Decide: either remove the files from package.json if they should not be published, or remove the corresponding .npmignore rules if they should be included.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.npmignore around lines 40 - 48, The package publish rules are inconsistent:
package.json explicitly lists dist/orchestrator/adopt.js and
dist/orchestrator/history.js but .npmignore contains patterns
dist/orchestrator/adopt.* and dist/orchestrator/history.* which exclude them
from the tarball; fix by choosing one of two actions—either remove the adopt.js
and history.js entries from the "files" array in package.json if they should not
be published, or delete/adjust the dist/orchestrator/adopt.* and
dist/orchestrator/history.* lines from .npmignore so those files are included;
after making the change, verify with npm pack --dry-run to confirm the tarball
contains (or excludes) the intended files.
| set -euo pipefail | ||
| INPUT=$(cat) | ||
| CMD=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('command',''))" 2>/dev/null || true) | ||
| CMD=$(echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);console.log((j.tool_input||{}).command||'')}catch{console.log('')}})" 2>/dev/null || true) |
There was a problem hiding this comment.
Node dependency assumed; failures are silent.
The switch from python3 to node for JSON parsing assumes Node.js is available in the user's PATH when the hook runs. If node isn't found, the || true fallback silently proceeds with an empty command, which allows destructive commands through the PreToolUse check. Consider adding a guard that exits non-zero when node is unavailable, or documenting this runtime requirement.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/fix/hook-generator.ts` at line 64, The shell pipeline that sets CMD in
hook-generator.ts (the line starting with CMD=$(echo "$INPUT" | node -e ...))
silently falls back to an empty command if node is not installed because of the
"|| true" and redirection; update the hook so it first guards for a Node.js
runtime (e.g., check availability of `node` or its version) and fail non‑zero
with an explicit error if not found, or restore a fallback parser (e.g.,
`python3`) only after validating its presence; ensure the guard is adjacent to
the CMD assignment so the PreToolUse check never proceeds with an empty/unknown
command when Node is missing.
| import { runXRay } from "../index.js"; | ||
| import { generateFixes, applyFix } from "../../fix/index.js"; | ||
|
|
||
| const TMP = join(__dirname, ".tmp-integration-test"); |
There was a problem hiding this comment.
Separate the temp HOME from the temp repo.
This test sets HOME and repoRoot to the same path, so getSettingsLocations() resolves the user and project-shared settings files to the same TMP/.claude/settings.json. That can hide scope bugs by letting project-level checks and fixes pass after only mutating the user-level file.
💡 Proposed fix
-const TMP = join(__dirname, ".tmp-integration-test");
+const TMP_ROOT = join(__dirname, ".tmp-integration-test");
+const TMP_HOME = join(TMP_ROOT, "home");
+const TMP_REPO = join(TMP_ROOT, "repo");
...
- process.env.HOME = TMP;
+ process.env.HOME = TMP_HOME;
...
- mkdirSync(join(TMP, ".claude"), { recursive: true });
+ mkdirSync(join(TMP_HOME, ".claude"), { recursive: true });
+ mkdirSync(TMP_REPO, { recursive: true });
...
- join(TMP, ".claude", "settings.json"),
+ join(TMP_HOME, ".claude", "settings.json"),
...
- rmSync(TMP, { recursive: true, force: true });
+ rmSync(TMP_ROOT, { recursive: true, force: true });
...
- const beforeResult = runXRay(TMP);
+ const beforeResult = runXRay(TMP_REPO);
...
- const fixes = generateFixes(beforeResult, TMP);
+ const fixes = generateFixes(beforeResult, TMP_REPO);
...
- const afterResult = runXRay(TMP);
+ const afterResult = runXRay(TMP_REPO);Also applies to: 19-25, 31-33, 46-55, 58-69, 82-82
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/scan/__tests__/integration.test.ts` at line 15, The test currently reuses
a single TMP directory (const TMP) for both the fake user HOME and the repoRoot
which causes getSettingsLocations() to resolve user and project settings to the
same file; change the setup to create two distinct temp paths (e.g., TMP_HOME
and TMP_REPO) and use TMP_HOME as process.env.HOME and TMP_REPO as the repoRoot
used in the tests (and update any test helpers, cleanup, and assertions that
reference TMP to use the appropriate TMP_HOME or TMP_REPO); ensure references in
this file to getSettingsLocations(), any setup/teardown code, and all places
that previously used TMP (lines noted in the review) are adjusted so user-level
files live under TMP_HOME/.claude and project-level files under
TMP_REPO/.claude.
| function listSessionFiles(projectDir: string): string[] { | ||
| try { | ||
| return readdirSync(projectDir) | ||
| const files = readdirSync(projectDir) | ||
| .filter((f) => f.endsWith(".jsonl")) | ||
| .map((f) => join(projectDir, f)); | ||
| // Cap at 20 most recent files to avoid OOM on power users | ||
| return files | ||
| .map((f) => ({ path: f, mtime: statSync(f).mtimeMs })) | ||
| .sort((a, b) => b.mtime - a.mtime) | ||
| .slice(0, 20) | ||
| .map((f) => f.path); |
There was a problem hiding this comment.
Handle per-file statSync() failures instead of dropping the whole set.
listSessionFiles() now returns [] if any single .jsonl disappears or becomes unreadable during the mtime pass. That turns one transient file race into "no sessions" and a 0 efficiency score for the repo.
💡 Proposed fix
function listSessionFiles(projectDir: string): string[] {
try {
const files = readdirSync(projectDir)
.filter((f) => f.endsWith(".jsonl"))
- .map((f) => join(projectDir, f));
+ .map((f) => join(projectDir, f))
+ .flatMap((f) => {
+ try {
+ return [{ path: f, mtime: statSync(f).mtimeMs }];
+ } catch {
+ return [];
+ }
+ });
// Cap at 20 most recent files to avoid OOM on power users
return files
- .map((f) => ({ path: f, mtime: statSync(f).mtimeMs }))
.sort((a, b) => b.mtime - a.mtime)
.slice(0, 20)
.map((f) => f.path);
} catch {
return [];
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/scan/efficiency.ts` around lines 92 - 102, The function listSessionFiles
currently fails the whole mtime pass if any single statSync throws; change it to
perform per-file statSync inside a try/catch (or use a safe helper) so
unreadable/missing .jsonl files are skipped rather than causing an empty result,
then sort the successfully-statted entries by mtime, slice to 20 and return
their paths; update the logic around readdirSync/map/statSync (referencing
listSessionFiles, readdirSync, statSync) to skip errors (optionally logging
them) and ensure the 20-most-recent cap is applied after filtering.
| settings_validation: { | ||
| valid: false, | ||
| errors: [ | ||
| { | ||
| path: "", | ||
| message: "Schema validation not yet implemented", | ||
| scope: "n/a", | ||
| }, | ||
| ], | ||
| }, |
There was a problem hiding this comment.
Don't hardcode settings_validation to fail.
Every runXRay() result now reports a validation error even though no schema validator ran. That makes the JSON contract lie to downstream consumers and turns healthy repos into false negatives.
If validation is not implemented yet, represent that state explicitly instead of fabricating an error payload.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/scan/index.ts` around lines 78 - 87, The current code hardcodes
settings_validation to { valid: false, errors: [...] } causing runXRay() to
always report a validation error; change runXRay() (or the code that constructs
settings_validation) to avoid fabricating failures—either omit
settings_validation when no validator ran or set an explicit sentinel (e.g.,
settings_validation: { implemented: false } or settings_validation: { status:
"not_implemented", errors: [] }) so downstream consumers can distinguish "not
implemented" from a real validation failure; update any consumers/tests that
expect a boolean valid to handle the explicit not-implemented representation.
| export function readJson(filePath: string): Record<string, unknown> | null { | ||
| if (!existsSync(filePath)) return null; | ||
| try { | ||
| return JSON.parse(readFileSync(filePath, "utf-8")); | ||
| } catch { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Validate the parsed top-level shape before returning it.
readJson() promises an object but returns any successful JSON value. Arrays and scalars can then flow through every scanner as if they were config objects and produce misleading results.
💡 Proposed fix
export function readJson(filePath: string): Record<string, unknown> | null {
if (!existsSync(filePath)) return null;
try {
- return JSON.parse(readFileSync(filePath, "utf-8"));
+ const parsed: unknown = JSON.parse(readFileSync(filePath, "utf-8"));
+ return parsed !== null &&
+ typeof parsed === "object" &&
+ !Array.isArray(parsed)
+ ? (parsed as Record<string, unknown>)
+ : null;
} catch {
return null;
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function readJson(filePath: string): Record<string, unknown> | null { | |
| if (!existsSync(filePath)) return null; | |
| try { | |
| return JSON.parse(readFileSync(filePath, "utf-8")); | |
| } catch { | |
| return null; | |
| } | |
| export function readJson(filePath: string): Record<string, unknown> | null { | |
| if (!existsSync(filePath)) return null; | |
| try { | |
| const parsed: unknown = JSON.parse(readFileSync(filePath, "utf-8")); | |
| return parsed !== null && | |
| typeof parsed === "object" && | |
| !Array.isArray(parsed) | |
| ? (parsed as Record<string, unknown>) | |
| : null; | |
| } catch { | |
| return null; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/scan/utils.ts` around lines 11 - 17, readJson currently returns any JSON
value; update it so after parsing it validates the top-level value is a plain
object (non-null, typeof === "object", and not an Array via Array.isArray) and
return that Record<string, unknown> only when valid, otherwise return null; keep
the try/catch around JSON.parse and treat parse errors the same, and reference
the readJson function to add the type checks and null-returns for non-object
JSON values.
| export function getHome(): string { | ||
| const home = process.env["HOME"] ?? process.env["USERPROFILE"]; | ||
| if (!home) { | ||
| throw new Error("HOME or USERPROFILE environment variable is required"); | ||
| } | ||
| return home; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In current Node.js, how does os.homedir()resolve the home directory on POSIX and Windows, and what fallbacks does it support beyondHOME/USERPROFILE?
💡 Result:
In current Node.js (as of v25.8.2), os.homedir calls libuv's uv_os_homedir to resolve the current user's home directory path. On POSIX systems: - First checks the HOME environment variable using uv_os_getenv("HOME"). If set and valid, returns it. - Otherwise, uses the effective UID (from geteuid) to query the system's password database (via getpwuid_r, typically reading /etc/passwd or equivalent like NSS) to retrieve the pw_dir field. On Windows: - First checks the USERPROFILE environment variable using uv_os_getenv("USERPROFILE"). If set and valid, returns it. - Otherwise, calls GetUserProfileDirectory with the current user's token to get the profile directory path. No further fallbacks beyond these are supported; if both methods fail, uv_os_homedir returns an error (UV_ENOENT or similar), which os.homedir propagates as a thrown Error. This behavior prioritizes environment variables (which can override system defaults, e.g., in sudo scenarios where HOME may not match effective UID) but falls back to authentic system user data. Note that os.userInfo.homedir differs by always using the system lookup without env check.
Citations:
- 1: https://nodejs.org/api/os.html
- 2: https://nodejs.cn/api/os/os_homedir.html
- 3: doc: improve os.homedir() docs nodejs/node#28401
- 4: https://github.com/libuv/libuv/blob/271d173b/src/unix/core.c
- 5: unix: refactor uv_os_homedir to use uv_os_getenv libuv/libuv#1760
- 6: uv_os_homedir() in src/unix/core.c returns ENOENT for "fake" /etc/passwd file libuv/libuv#810
🏁 Script executed:
# Check if the file exists and read the relevant section
head -30 src/scan/utils.tsRepository: ArshyaAI/claude-code-xray
Length of output: 830
🏁 Script executed:
# Search for all usages of getHome to understand the scope of impact
rg "getHome()" --type ts --type tsx -A 2 -B 2Repository: ArshyaAI/claude-code-xray
Length of output: 96
🏁 Script executed:
# Search for getHome() usages with correct syntax
rg "getHome\(\)" -A 2 -B 2Repository: ArshyaAI/claude-code-xray
Length of output: 2525
Replace env-only home directory lookup with os.homedir() for broader fallback support.
The current implementation only checks HOME and USERPROFILE environment variables with no system fallback. Node's os.homedir() first attempts the same env var checks, then falls back to the system password database on POSIX (via getpwuid_r) or GetUserProfileDirectory on Windows, providing more robust resolution. Since getHome() is called throughout the scanner modules (in settings resolution, project detection, and automation checks), a failure here cascades to whole-scan failures even when the runtime can resolve a valid home directory.
Suggested fix
import { existsSync, readFileSync } from "node:fs";
+import { homedir } from "node:os";
import { join } from "node:path";
...
export function getHome(): string {
- const home = process.env["HOME"] ?? process.env["USERPROFILE"];
+ const home = homedir();
if (!home) {
- throw new Error("HOME or USERPROFILE environment variable is required");
+ throw new Error("Could not resolve the user's home directory");
}
return home;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/scan/utils.ts` around lines 20 - 25, getHome currently reads only
HOME/USERPROFILE env vars which can fail even when the system can resolve a home
directory; update getHome to call Node's os.homedir() (importing os) and return
that value, falling back to throwing the same error if os.homedir() returns
falsy. Locate the getHome function and replace the manual env lookup with a call
to os.homedir(), keep the function signature and error behavior (throw "HOME or
USERPROFILE environment variable is required" if no home is found) so callers
(settings resolution, project detection, automation checks) continue to receive
a string or an error.
Summary
Ship-blockers resolved
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
Chores