Skip to content

review: fix captureDisabled module-global cache poisoning#43

Open
kridaydave wants to merge 8 commits into
Shreyan1:mainfrom
kridaydave:review/fix-capture-disabled-cache
Open

review: fix captureDisabled module-global cache poisoning#43
kridaydave wants to merge 8 commits into
Shreyan1:mainfrom
kridaydave:review/fix-capture-disabled-cache

Conversation

@kridaydave

Copy link
Copy Markdown
Contributor

Split out of PR #39 (kridaydave:fix/good-first-issues) to address review concern #3.

The async refactor introduced a module-global cachedIgnore that checkIgnoreAsync() wrote and captureDisabled() read. Reproduced bug: a test that sets CC_HABITS_DISABLE and calls checkIgnoreAsync() caches true; a later test that clears the env and calls captureDisabled() expecting false gets true — the stale cached value. In the shared vitest worker this silently poisons captureDisabled() for any later test depending on order, which is exactly the intermittent flakiness the description already flagged.

Remove the module-global entirely. Both captureDisabled() and checkIgnoreAsync() now compute the ignore decision fresh on every call (env var + .cc-habits-ignore check), so nothing leaks across invocations. The env var and cwd are read fresh each time, which is free and correct for the short-lived hook process in production. captureDisabled(cached?) optionally accepts a single-invocation value, and the hook handlers thread the value from checkIgnoreAsync() through so there is still no redundant filesystem hit within one invocation.

The other three concerns (drop Antigravity #1, bounded concurrency #2, config-parse dedup #4) land in their own PRs, also split from #39.

kridaydave and others added 8 commits July 15, 2026 10:36
Part of the review split of PR Shreyan1#39 (kridaydave:fix/good-first-issues). This
PR handles concern Shreyan1#3 only.

The async refactor introduced a module-global `cachedIgnore` that
checkIgnoreAsync() wrote and captureDisabled() read. Reproduced bug: a test
that sets CC_HABITS_DISABLE and calls checkIgnoreAsync() caches true; a later
test that clears the env and calls captureDisabled() expecting false gets
true — the stale cached value. In the shared vitest worker this silently
poisons captureDisabled() for any later test depending on order, which is
exactly the intermittent flakiness the description already flagged.

Remove the module-global entirely. Both captureDisabled() and
checkIgnoreAsync() now compute the ignore decision fresh on every call (env
var + .cc-habits-ignore check), so nothing leaks across invocations. The env
var and cwd are read fresh each time, which is free and correct for the
short-lived hook process in production. captureDisabled(cached?) optionally
accepts a single-invocation value, and the hook handlers thread the value
from checkIgnoreAsync() through so there is still no redundant filesystem
hit within one invocation.

Split from Shreyan1#39; the other three concerns (drop Antigravity, bounded
concurrency, config-parse dedup) land in their own PRs.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Comment thread src/hook.ts

export function handleUserPromptSubmit(): void {
export async function handleUserPromptSubmit(): Promise<void> {
const ignored = await checkIgnoreAsync();

@Shreyan1 Shreyan1 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The fix is correct, Kriday. The module-global is gone, both captureDisabled() and checkIgnoreAsync() compute fresh, and the single-invocation value is threaded through so there is no redundant fs hit in the hot path. That is the right resolution.

The one thing this PR must have and does not: a regression test for the bug it fixes (inline). This is the exact poisoning I reproduced, and without a test locking it, it silently comes back the next time someone reaches for a cache.

Same two structural blockers as #42: it still carries antigravity (rebase on #41 to clear the parity gate) and it needs a version bump. The CodeQL HIGH is from the carried #39 base and clears on rebase.

Comment thread src/hook.ts
}
}

export function captureDisabled(cached?: boolean | null): boolean {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This is the correct fix, computing fresh each call and letting the caller thread a single-invocation value is exactly right. What is missing is the test. Please add the regression I reproduced: set CC_HABITS_DISABLE, call checkIgnoreAsync() (this is what used to cache true), clear the env, then assert captureDisabled() === false. That one test is the whole point of this PR, it is what stops the poisoning from coming back.

Comment thread src/supported.ts

// Adapter ids that have a dedicated hook adapter (used to assert parity in tests).
export const HOOK_ADAPTERS = ['claude-code', 'gemini', 'codex', 'cline', 'kimi'];
export const HOOK_ADAPTERS = ['claude-code', 'gemini', 'codex', 'cline', 'kimi', 'antigravity'];

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Same as #42, still lists antigravity, so the parity gate is red. Rebase on #41 and it clears.

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.

3 participants