Skip to content

fix: surface silent update-check parse failures#384

Closed
ndycode wants to merge 1 commit intomainfrom
fix/surface-silent-catch-blocks
Closed

fix: surface silent update-check parse failures#384
ndycode wants to merge 1 commit intomainfrom
fix/surface-silent-catch-blocks

Conversation

@ndycode
Copy link
Copy Markdown
Owner

@ndycode ndycode commented Apr 6, 2026

Problem

The update checker silently swallowed invalid local package metadata and unreadable cached JSON.

The fallback behavior was safe, but those failures were invisible during diagnosis.

Fix

Keep the same fallback behavior, but log those silent parse/read failures at debug level.

Changes

  • lib/auto-update-checker.ts
    • logs debug details when reading local package.json fails
    • logs debug details when loading cached update JSON fails
  • test/auto-update-checker.test.ts
    • added coverage for package metadata parse failures being logged
    • added coverage for unreadable cache JSON being logged and bypassed

Validation

npx vitest run test/auto-update-checker.test.ts
Test Files  1 passed (1)
Tests      36 passed (36)

npx vitest run
Test Files  222 passed (222)
Tests      3315 passed (3315)

note: greptile review for oc-chatgpt-multi-auth. cite files like lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.

Greptile Summary

this pr adds log.debug calls in the two previously-silent catch {} blocks inside getCurrentVersion() and loadCache(), surfacing parse and read failures for diagnosis without changing the existing fallback behavior ("0.0.0" / null). tests are added for both paths using a vi.doMock logger stub that correctly intercepts the module-level createLogger("update-checker") call before the module is dynamically imported.

Confidence Score: 5/5

safe to merge — no behavior change, only adds debug observability to two existing catch blocks

minimal diff, fallback values are identical to before, both new catch paths are exercised by dedicated tests, and the pattern matches the existing log.debug call in fetchLatestVersion()

no files require special attention

Important Files Changed

Filename Overview
lib/auto-update-checker.ts adds debug-level logging in two previously-silent catch blocks for read/parse failures; fallback behavior unchanged
test/auto-update-checker.test.ts adds logger mock via vi.doMock and two new tests covering the debug log paths in getCurrentVersion and loadCache

Sequence Diagram

sequenceDiagram
    participant C as checkForUpdates()
    participant GV as getCurrentVersion()
    participant LC as loadCache()
    participant FS as node:fs
    participant LOG as log (debug)
    participant NPM as npm registry

    C->>GV: call
    GV->>FS: readFileSync(package.json)
    alt read/parse succeeds
        FS-->>GV: version string
        GV-->>C: "4.12.0"
    else read/parse fails (new)
        FS-->>GV: throws
        GV->>LOG: debug("Failed to read current package version", {error})
        GV-->>C: "0.0.0"
    end

    C->>LC: call
    LC->>FS: existsSync(cache)
    alt cache absent
        LC-->>C: null
    else cache present
        LC->>FS: readFileSync(cache)
        alt parse succeeds
            FS-->>LC: UpdateCheckCache
            LC-->>C: cache object
        else parse fails (new)
            FS-->>LC: throws
            LC->>LOG: debug("Failed to load update cache", {error})
            LC-->>C: null
        end
    end

    alt cache valid and not expired
        C-->>C: return cached result
    else cache missing/expired/forced
        C->>NPM: fetchLatestVersion()
        NPM-->>C: latestVersion
        C->>FS: saveCache(...)
        C-->>C: return fresh result
    end
Loading

Reviews (1): Last reviewed commit: "fix: surface silent update-check parse f..." | Re-trigger Greptile

The update checker silently swallowed invalid package metadata and cached
JSON parse failures. Keep the same fallback behavior, but log those cases
at debug level so malformed local state is observable during diagnosis.
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 6, 2026

📝 Walkthrough

This is a minor severity observability improvement that adds debug-level logging to the update checker's error handling paths while preserving all existing fallback behavior. The fix surfaces previously-silent parse failures in local package metadata and cache JSON reading, enabling visibility into malformed local state during diagnosis without changing any functional behavior or exported APIs. No security or data-loss risks are introduced; regression tests were added to verify the debug logging behavior (59 new test lines, full suite passes 3315 tests), and the architectural approach is straightforward—capturing exceptions in existing catch blocks and logging them before returning fallback values.

Walkthrough

enhanced error handling in auto-update-checker by adding debug logging to two catch blocks. when version reading or cache loading fails, errors are now logged before returning fallback values. tests added to verify logging behavior on parse/read errors.

Changes

Cohort / File(s) Summary
error logging in auto-update-checker
lib/auto-update-checker.ts
getCurrentVersion() and loadCache() now emit log.debug() entries with stringified error messages in their catch blocks before returning fallback values ("0.0.0" and null respectively).
test coverage for error logging
test/auto-update-checker.test.ts
mocked logger globally via ../lib/logger.js and added test cases asserting logger.debug() is called with expected messages when package.json parsing fails and when cache file cannot be read.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

bug


review notes:

  • lib/auto-update-checker.ts: confirm the error stringification logic handles circular references safely—String(error) on non-Error objects could produce verbose output. verify this doesn't generate noise in production logs.

  • test/auto-update-checker.test.ts: the new tests cover error scenarios for getCurrentVersion() and loadCache() (cache unreadable while present). missing regression test coverage: what happens if the cache file exists but is truncated/corrupted? current loadCache() may throw during JSON.parse()—is that tested? also, what happens if fetch() itself is called on a corrupted cache path—does the function still attempt upstream checks?

  • no windows-specific edge cases flagged: if package.json or cache paths contain non-ascii characters or long paths (>260 chars), does error logging handle them without throwing? test on windows paths.

  • concurrency risk: if loadCache() fails mid-read (file being written concurrently), does repeated logging create log spam? no backoff or rate-limiting visible.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed title follows conventional commits format with 'fix' type, lowercase imperative summary, and is well under the 72 character limit at 47 characters.
Description check ✅ Passed description covers the problem, fix, and changes clearly with validation results provided, though the formal template sections (Risk and Rollback, Docs checklist) are not explicitly filled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/surface-silent-catch-blocks
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/surface-silent-catch-blocks

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/auto-update-checker.test.ts`:
- Around line 242-261: In the "logs debug details when package metadata cannot
be parsed" test, add an assertion that the update fetch still runs by checking
globalThis.fetch was called; update the test around the checkForUpdates(true)
invocation (the test function and its mocks for fs.readFileSync and
globalThis.fetch) to include expect(globalThis.fetch).toHaveBeenCalled() after
the existing assertions so the fallback update path (checkForUpdates) is
verified to continue even when package parsing fails.
- Around line 287-310: Add a new vitest case in test/auto-update-checker.test.ts
that simulates Windows-style readFileSync failures for the update cache: mock
fs.existsSync to true and mock fs.readFileSync to throw a NodeJS.ErrnoException
(set the name/message/code to "EBUSY" or "EPERM") when the path includes
"update-check-cache.json" while still returning package.json for that path; call
checkForUpdates() and assert that logger.debug was invoked with a message like
"Failed to load update cache" containing an error object that includes the
errno/code string, and also assert that globalThis.fetch was called to verify
fallback behavior in the auto-update-checker checkForUpdates path.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 69b037a0-4f30-4858-938f-c02e77b60d6e

📥 Commits

Reviewing files that changed from the base of the PR and between 478f44c and a2203db.

📒 Files selected for processing (2)
  • lib/auto-update-checker.ts
  • test/auto-update-checker.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (2)
lib/**

⚙️ CodeRabbit configuration file

focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.

Files:

  • lib/auto-update-checker.ts
test/**

⚙️ CodeRabbit configuration file

tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.

Files:

  • test/auto-update-checker.test.ts
🔇 Additional comments (3)
lib/auto-update-checker.ts (2)

44-47: good observability on package version fallback path.

this keeps behavior stable while making failures diagnosable in getCurrentVersion (lib/auto-update-checker.ts:44-47), and it is covered by vitest at test/auto-update-checker.test.ts:242-261. no new concurrency risk introduced here.


57-60: cache parse/read failures are now visible without breaking fallback.

the debug log in loadCache (lib/auto-update-checker.ts:57-60) preserves the null fallback and is validated by test/auto-update-checker.test.ts:287-310. this also improves windows fs diagnostics without exposing tokens/emails.

test/auto-update-checker.test.ts (1)

15-19: logger mock wiring is solid and deterministic.

the vi.doMock setup and per-test logger reset in test/auto-update-checker.test.ts:15-19 and test/auto-update-checker.test.ts:28-35 are clean and stable under vitest fake timers.

Also applies to: 28-35

Comment on lines +242 to +261
it("logs debug details when package metadata cannot be parsed", async () => {
vi.mocked(fs.readFileSync).mockImplementation((path: unknown) => {
if (String(path).includes("package.json")) {
return "{";
}
throw new Error("File not found");
});
vi.mocked(globalThis.fetch).mockResolvedValue({
ok: true,
json: async () => ({ version: "5.0.0" }),
} as Response);

const result = await checkForUpdates(true);

expect(result.currentVersion).toBe("0.0.0");
expect(logger.debug).toHaveBeenCalledWith(
"Failed to read current package version",
expect.objectContaining({ error: expect.any(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.

⚠️ Potential issue | 🟡 Minor

add an assertion that update fetch still runs after package parse failure.

this regression currently checks fallback version and debug log, but it does not assert continuation of the update path in test/auto-update-checker.test.ts:242-261. add expect(globalThis.fetch).toHaveBeenCalled() so the fallback behavior is fully locked in.

proposed test hardening
 		const result = await checkForUpdates(true);

 		expect(result.currentVersion).toBe("0.0.0");
+		expect(globalThis.fetch).toHaveBeenCalled();
 		expect(logger.debug).toHaveBeenCalledWith(
 			"Failed to read current package version",
 			expect.objectContaining({ error: expect.any(String) }),
 		);

As per coding guidelines, test/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior.

📝 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.

Suggested change
it("logs debug details when package metadata cannot be parsed", async () => {
vi.mocked(fs.readFileSync).mockImplementation((path: unknown) => {
if (String(path).includes("package.json")) {
return "{";
}
throw new Error("File not found");
});
vi.mocked(globalThis.fetch).mockResolvedValue({
ok: true,
json: async () => ({ version: "5.0.0" }),
} as Response);
const result = await checkForUpdates(true);
expect(result.currentVersion).toBe("0.0.0");
expect(logger.debug).toHaveBeenCalledWith(
"Failed to read current package version",
expect.objectContaining({ error: expect.any(String) }),
);
});
it("logs debug details when package metadata cannot be parsed", async () => {
vi.mocked(fs.readFileSync).mockImplementation((path: unknown) => {
if (String(path).includes("package.json")) {
return "{";
}
throw new Error("File not found");
});
vi.mocked(globalThis.fetch).mockResolvedValue({
ok: true,
json: async () => ({ version: "5.0.0" }),
} as Response);
const result = await checkForUpdates(true);
expect(result.currentVersion).toBe("0.0.0");
expect(globalThis.fetch).toHaveBeenCalled();
expect(logger.debug).toHaveBeenCalledWith(
"Failed to read current package version",
expect.objectContaining({ error: expect.any(String) }),
);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/auto-update-checker.test.ts` around lines 242 - 261, In the "logs debug
details when package metadata cannot be parsed" test, add an assertion that the
update fetch still runs by checking globalThis.fetch was called; update the test
around the checkForUpdates(true) invocation (the test function and its mocks for
fs.readFileSync and globalThis.fetch) to include
expect(globalThis.fetch).toHaveBeenCalled() after the existing assertions so the
fallback update path (checkForUpdates) is verified to continue even when package
parsing fails.

Comment on lines +287 to +310
it("logs debug details when cached update JSON is unreadable", async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockImplementation((path: unknown) => {
if (String(path).includes("package.json")) {
return JSON.stringify(mockPackageJson);
}
if (String(path).includes("update-check-cache.json")) {
return "{";
}
throw new Error("File not found");
});
vi.mocked(globalThis.fetch).mockResolvedValue({
ok: true,
json: async () => ({ version: "5.0.0" }),
} as Response);

await checkForUpdates();

expect(logger.debug).toHaveBeenCalledWith(
"Failed to load update cache",
expect.objectContaining({ error: expect.any(String) }),
);
expect(globalThis.fetch).toHaveBeenCalled();
});
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.

⚠️ Potential issue | 🟠 Major

missing windows read-error regression for cache load path.

test/auto-update-checker.test.ts:287-310 covers malformed cache json, but not windows-style fs read failures (like EBUSY/EPERM) on readFileSync. add a case that throws NodeJS.ErrnoException with those codes and assert debug logging plus fetch fallback. this is the windows edge case for the exact path changed in lib/auto-update-checker.ts:57-60. no new concurrency bug is visible, but this platform regression is still missing.

proposed regression case
+		it.each(["EBUSY", "EPERM"] as const)(
+			"logs debug details when cache read fails on windows-style lock (%s)",
+			async (code) => {
+				vi.mocked(fs.existsSync).mockReturnValue(true);
+				vi.mocked(fs.readFileSync).mockImplementation((path: unknown) => {
+					if (String(path).includes("package.json")) {
+						return JSON.stringify(mockPackageJson);
+					}
+					if (String(path).includes("update-check-cache.json")) {
+						const error = new Error("locked") as NodeJS.ErrnoException;
+						error.code = code;
+						throw error;
+					}
+					throw new Error("File not found");
+				});
+				vi.mocked(globalThis.fetch).mockResolvedValue({
+					ok: true,
+					json: async () => ({ version: "5.0.0" }),
+				} as Response);
+
+				await checkForUpdates();
+
+				expect(logger.debug).toHaveBeenCalledWith(
+					"Failed to load update cache",
+					expect.objectContaining({ error: expect.any(String) }),
+				);
+				expect(globalThis.fetch).toHaveBeenCalled();
+			},
+		);

As per coding guidelines, test/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/auto-update-checker.test.ts` around lines 287 - 310, Add a new vitest
case in test/auto-update-checker.test.ts that simulates Windows-style
readFileSync failures for the update cache: mock fs.existsSync to true and mock
fs.readFileSync to throw a NodeJS.ErrnoException (set the name/message/code to
"EBUSY" or "EPERM") when the path includes "update-check-cache.json" while still
returning package.json for that path; call checkForUpdates() and assert that
logger.debug was invoked with a message like "Failed to load update cache"
containing an error object that includes the errno/code string, and also assert
that globalThis.fetch was called to verify fallback behavior in the
auto-update-checker checkForUpdates path.

@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Apr 6, 2026

Superseded by #387, which rebuilds the full open PR stack onto one reviewed integration branch.

@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Apr 6, 2026

Closing in favor of #387.

@ndycode ndycode closed this Apr 6, 2026
@ndycode ndycode deleted the fix/surface-silent-catch-blocks branch April 12, 2026 06:00
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.

1 participant