Skip to content

fix: tldr-read-enforcer hook ignored when Claude uses a relative file path#152

Open
marcuspuchalla wants to merge 1 commit intoparcadei:mainfrom
marcuspuchalla:fix/relative-path-read-enforcer
Open

fix: tldr-read-enforcer hook ignored when Claude uses a relative file path#152
marcuspuchalla wants to merge 1 commit intoparcadei:mainfrom
marcuspuchalla:fix/relative-path-read-enforcer

Conversation

@marcuspuchalla
Copy link
Copy Markdown

@marcuspuchalla marcuspuchalla commented Feb 22, 2026

Problem

The hook checks the file size with statSync(filePath) to decide whether to intercept a Read. But statSync needs an absolute path — if Claude passes a relative path like src/api/foo.ts, the hook's process has a different working directory than the project, so statSync can't find the file.

It throws, the catch {} block silently returns {}, and the raw file is read normally — TLDR never kicks in for any relative path.

Fix

Convert relative paths to absolute using input.cwd (the project directory Claude Code provides in the hook payload):

const rawFilePath = input.tool_input.file_path || '';
const filePath = isAbsolute(rawFilePath) ? rawFilePath : join(input.cwd, rawFilePath);

Also fixes pre-existing TypeScript compile errors

  • daemon-client import missing .js extension — required by NodeNext module resolution
  • DaemonResponse interface missing imports/importers fields — used in importsDaemon() at line 906 but not declared, causing TS2339
  • tsconfig.json now excludes src/__tests__ — test files need jest/vitest types not present in the main tsconfig, causing spurious build errors

Testing

Verified that after this fix, reading a file via relative path (e.g. src/api/estimateSwap.ts) correctly triggers the TLDR context instead of falling through to a raw read.

Summary by CodeRabbit

  • Bug Fixes

    • Improved file path resolution and normalization to ensure input files are located and processed reliably.
  • Chores

    • Updated build configuration to exclude test sources from compilation.
    • Expanded response structure to include additional optional metadata fields.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Feb 22, 2026

PR author is not in the allowed authors list.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 22, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Added optional import-related fields to DaemonResponse, made file path resolution use path.resolve with a project-cwd fallback and updated the daemon-client import to a .js extension, and excluded src/__tests__ from TypeScript compilation.

Changes

Cohort / File(s) Summary
Interface Enhancement
/.claude/hooks/src/daemon-client.ts
Added optional fields imports?: any[] and importers?: any[] to the DaemonResponse interface.
Path resolution & import update
/.claude/hooks/src/tldr-read-enforcer.ts
Imported resolve from path, introduced rawFilePath and projectCwd, compute filePath with resolve(projectCwd, rawFilePath) (falling back to process.cwd()), and updated daemon-client import to use a .js extension.
TypeScript config
/.claude/hooks/tsconfig.json
Added src/__tests__ to the exclude array to omit test sources from compilation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: resolving an issue where the tldr-read-enforcer hook was being ignored when Claude provided relative file paths instead of absolute paths.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

const filePath = input.tool_input.file_path || '';
const rawFilePath = input.tool_input.file_path || '';
// Resolve relative paths — statSync fails silently on relative paths, bypassing the hook
const filePath = isAbsolute(rawFilePath) ? rawFilePath : join(input.cwd, rawFilePath);

This comment was marked as outdated.

Copy link
Copy Markdown

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

🧹 Nitpick comments (1)
.claude/hooks/src/tldr-read-enforcer.ts (1)

374-376: Core fix is correct; consider resolve over join for stronger absoluteness guarantee.

path.join(input.cwd, rawFilePath) produces a relative result if input.cwd is itself relative — the original bug silently re-emerges. path.resolve always returns an absolute path, regardless of whether input.cwd is absolute or relative.

While Claude's hook system should always supply an absolute cwd, resolve makes the contract explicit and eliminates the edge-case regression path.

🛡️ Suggested defensive change
-import { basename, extname, join, isAbsolute } from 'path';
+import { basename, extname, resolve, isAbsolute } from 'path';
-const rawFilePath = input.tool_input.file_path || '';
-// Resolve relative paths — statSync fails silently on relative paths, bypassing the hook
-const filePath = isAbsolute(rawFilePath) ? rawFilePath : join(input.cwd, rawFilePath);
+const rawFilePath = input.tool_input.file_path || '';
+// Resolve relative paths — statSync fails silently on relative paths, bypassing the hook
+const filePath = isAbsolute(rawFilePath) ? rawFilePath : resolve(input.cwd, rawFilePath);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/hooks/src/tldr-read-enforcer.ts around lines 374 - 376, Replace the
path construction that uses join so relative input.cwd can still yield a
relative result: change the logic that sets filePath (currently using
isAbsolute(rawFilePath) ? rawFilePath : join(input.cwd, rawFilePath)) to use
path.resolve for the fallback (i.e., isAbsolute(rawFilePath) ? rawFilePath :
resolve(input.cwd, rawFilePath)); ensure the code imports resolve from 'path'
alongside isAbsolute and join (or replace join usage entirely) so filePath is
always an absolute path even when input.cwd is relative.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.claude/hooks/src/tldr-read-enforcer.ts:
- Around line 374-376: Replace the path construction that uses join so relative
input.cwd can still yield a relative result: change the logic that sets filePath
(currently using isAbsolute(rawFilePath) ? rawFilePath : join(input.cwd,
rawFilePath)) to use path.resolve for the fallback (i.e.,
isAbsolute(rawFilePath) ? rawFilePath : resolve(input.cwd, rawFilePath)); ensure
the code imports resolve from 'path' alongside isAbsolute and join (or replace
join usage entirely) so filePath is always an absolute path even when input.cwd
is relative.

@marcuspuchalla marcuspuchalla force-pushed the fix/relative-path-read-enforcer branch from 6ce9590 to b0f65ce Compare February 22, 2026 14:29
Copy link
Copy Markdown
Author

@marcuspuchalla marcuspuchalla left a comment

Choose a reason for hiding this comment

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

Good catch — valid defensive concern. While Claude Code's hook spec guarantees cwd is always present in PreToolUse payloads, there's no runtime guard. If input.cwd were somehow absent, join(undefined, rawFilePath) would throw a TypeError outside the try/catch block below, crashing the hook.

Fixed in the latest commit with a process.cwd() fallback:

const projectCwd = input.cwd || process.cwd();
const filePath = isAbsolute(rawFilePath) ? rawFilePath : join(projectCwd, rawFilePath);

This is purely defensive — in normal operation input.cwd is always a string — but graceful degradation is better than an unhandled crash.

… path

The hook checks the file size before deciding whether to intercept a Read.
It does this with statSync(filePath). But statSync needs an absolute path —
if Claude passes a relative path like 'src/api/foo.ts', statSync can't find
the file (the hook process has a different working directory than the project).

It throws, the catch block silently returns {}, and the hook does nothing —
the raw file is read as normal, bypassing the TLDR token savings entirely.

Fix: convert relative paths to absolute using input.cwd (the project directory
that Claude Code provides in the hook payload):

  const filePath = isAbsolute(rawFilePath)
    ? rawFilePath
    : join(input.cwd, rawFilePath);

Also fixes pre-existing TypeScript compile errors:
- daemon-client import missing .js extension (required by NodeNext module resolution)
- DaemonResponse interface missing imports/importers fields (used in importsDaemon())
- tsconfig excludes src/__tests__ to stop test files breaking the build
@marcuspuchalla marcuspuchalla force-pushed the fix/relative-path-read-enforcer branch from b0f65ce to 99ea9aa Compare February 22, 2026 14:35
Copy link
Copy Markdown
Author

@marcuspuchalla marcuspuchalla left a comment

Choose a reason for hiding this comment

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

Good call on . Fixed in the latest commit — replaced with :

const filePath = resolve(projectCwd, rawFilePath);

resolve() handles both cases natively: returns an absolute path as-is, resolves a relative path against projectCwd. The isAbsolute guard and join import are no longer needed.

@marcuspuchalla
Copy link
Copy Markdown
Author

Re the Sentry inline comment on input.cwd: already addressed in the latest commit — we now use resolve(projectCwd, rawFilePath) where const projectCwd = input.cwd || process.cwd(). Both concerns (undefined cwd + join producing relative path if cwd is relative) are handled.

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