Skip to content

fix: no pharn-owned project file can hang a command or be read without bound - #225

Merged
PrzemekGalarowicz merged 1 commit into
mainfrom
claude/optimistic-heisenberg-8rw8ke
Sep 25, 2026
Merged

PrzemekGalarowicz merged 1 commit into
mainfrom
claude/optimistic-heisenberg-8rw8ke

Conversation

@PrzemekGalarowicz

Copy link
Copy Markdown
Contributor

What this changes

pharn.config.json, pharn.records.json and .pharn.lock were read with a plain readFileSync. That caused two problems:

  • A FIFO at any of them blocked in open(2) forever. For pharn update, and for a re-run pharn init, that happened while holding the project lock, so every other pharn command in the project was refused too.
  • A symlink to /dev/zero was read until memory ran out.

A new src/lib/bounded-read.ts (readBoundedFile) reads through one descriptor opened with O_NONBLOCK. It checks with fstat that the file is a regular file of at most 16 MiB, reads from that same descriptor, and never throws. It is the same approach hook-wiring.ts and detect-archetype.ts already use.

These six readers now go through it: readRecords, readPharnConfig, configFingerprint, init's two tolerant config reads, and the lock's readRawAt. Each keeps its existing "unreadable" outcome:

File Outcome when unreadable
pharn.records.json a named invalid
pharn.config.json null / unreadable:<code>
.pharn.lock presumed live, then broken

Type of change

  • feat — new stack option, wizard step, or command capability
  • fix — bug fix
  • docs — docs-only change
  • chore / refactor — tooling or internal restructure, no behavior change

Area(s) touched

lib/bounded-read (new) | lib/install-records | lib/pharn-config | lib/project-lock | steps/overwrite-check | steps/install-archetype | docs

Checklist

  • Read the existing file(s) before editing; followed the ESM .js-extension import convention.
  • Updated the matching tests/*.test.ts. I ran the FIFO cases for records, config, and a young and an old lock against the unchanged code first: each hung in open(2) until its 15 s child timeout killed it.
  • Updated the relevant docs/ page (docs/reference/pharn-records.md, docs/troubleshooting.md).
  • Preserved the security invariants.

Quality gates

  • npm run check passes locally (1664 tests). I ran it as root with the DAC-override capabilities dropped (CI-equivalent).
  • npm run build succeeds.
  • npm run test:coverage passes.

Notes for the reviewer

  • Existing values are unchanged. A directory at the config path still fingerprints as unreadable:EISDIR. On Windows, where opening a directory throws, the reason is normalized so both platforms say "is not a regular file".
  • One message is still imprecise: a FIFO at pharn.config.json still gets the existing "No pharn.config.json found. Run pharn init first." answer. That was already the wording for a directory or a permissions problem; making it more precise would change every command's documented contract, so it's left for its own change.

🤖 Generated with Claude Code

https://claude.ai/code/session_0199owRmYfskqYQVQrVP679o


Generated by Claude Code

…t bound

pharn.config.json, pharn.records.json and .pharn.lock were read with a
plain readFileSync. A FIFO at any of them blocked in open(2) forever — for
`update` and a re-run `init` while holding the project lock, so every other
pharn command in the project was refused too — and a symlink to /dev/zero
was read until memory ran out.

- New lib/bounded-read.ts (readBoundedFile): one descriptor opened
  O_NONBLOCK, fstat-checked as a regular file of at most 16 MiB, read from
  that same descriptor; never throws. Fixed reasons ("is not a regular
  file", "is larger than 16 MiB", "could not be read (<errno>)").
- readRecords, readPharnConfig, configFingerprint, init's two tolerant
  config reads and the lock's readRawAt read through it. Each keeps its
  existing unreadable outcome: records -> a named `invalid`, config ->
  null / `unreadable:<code>`, lock -> presumed live, then broken.
- FIFO cases for records, config and a young/old lock run in a child with
  a hard timeout (they hung on the base).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0199owRmYfskqYQVQrVP679o
@coderabbitai

coderabbitai Bot commented Sep 25, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 84e6d77a-ec28-4efe-a25e-1516236559f1


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.

@PrzemekGalarowicz
PrzemekGalarowicz merged commit 33861f1 into main Sep 25, 2026
14 checks passed
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.

2 participants