Skip to content

fix(lock,update): an interrupted write exits 130, releases the lock, names the backup (PHARN-10) - #204

Merged
PrzemekGalarowicz merged 2 commits into
mainfrom
claude/bold-archimedes-5czyyn
Sep 24, 2026
Merged

PrzemekGalarowicz merged 2 commits into
mainfrom
claude/bold-archimedes-5czyyn

Conversation

@PrzemekGalarowicz

Copy link
Copy Markdown
Contributor

What this changes

While a spinner is on screen, the prompt library (@clack/core) handles Ctrl-C by calling process.exit(0), and finally blocks do not run on process.exit.

Reproduced with update --yes --force in a terminal, pressing Ctrl-C while the records file was being written:

  • the CLI printed "Canceled" and exited 0;
  • .pharn.lock was left behind;
  • .pharn-backup/<ts>/ had been created but was never named;
  • pharn/CONSTITUTION.md had already been overwritten.

The fix:

  • src/lib/project-lock.ts: while withProjectLock holds the lock, it registers a process exit handler and removes it again when the locked work finishes. If the process exits in that window, the handler releases the lock immediately. If the exit code is 0, it changes it to 130 and prints one line to stderr: "pharn was interrupted while writing to this project — it may be partially updated. Re-run pharn <command>." The code comment states the assumption this relies on: no prompt inside a locked section exits 0 on a normal cancel. Today the confirm prompts run before the lock is taken, and the add picker exits after it is released.
  • src/commands/update.ts: prints where the backup went as soon as .pharn-backup/<ts>/ is created, as add already does and as docs/commands/update.md already promises. It is printed once on a successful run. An aborted run still repeats it on stderr, alongside the "stopped part-way" warning.

Built with /pharn-dev-ship; stage artifacts are in .dev/features/interrupt-exit-code/. Results:

  • validate: exit 0
  • regress: no-regressions
  • verify: PASS

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/project-lock | commands/update

Checklist

  • Read the existing file(s) before editing; followed the ESM .js-extension import convention.
  • New tests:
    • a real child process that calls process.exit(0) while holding the lock exits 130, prints the line, and leaves no .pharn.lock (fails on the old code);
    • the exit handler is removed after a normal release;
    • update prints the backup location when the backup is created (fails on the old code) and exactly once on success.
  • No docs change needed: docs/commands/update.md already describes the corrected behavior.
  • Preserved the security invariants.

Quality gates

  • npm run check passes locally (1349/1349; non-root user, node 22).
  • npm run build / npm run test:coverage (left to CI).

🤖 Generated with Claude Code

https://claude.ai/code/session_01TvcuVhk8hTeDskp5pAJhnc


Generated by Claude Code

…names the backup (PHARN-10)

While a clack spinner is up, @clack/core turns Ctrl-C into process.exit(0),
which runs no `finally`. Reproduced: `update --force` interrupted during its
records write printed "Canceled", exited 0, left .pharn.lock behind, and
never named the .pharn-backup/<ts>/ it had already created — with originals
already overwritten.

- withProjectLock registers an `exit` listener while the lock is held: it
  releases the lock synchronously and turns an exit code of 0 into 130 with
  one stderr line saying the run was interrupted while writing.
- update prints the backup pointer the moment the backup is created (as
  `add` does and docs/commands/update.md promises), not only at the end.

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

coderabbitai Bot commented Sep 24, 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: 079ac9ce-9c55-401c-8113-f220734fd765


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.

…re the 97% gate

CI's coverage gate failed at 96.79% statements: the exit listener only ran in
a child process (invisible to coverage), on top of uncovered hook-wiring error
branches. Adds in-process listener tests (0 -> 130, non-zero untouched) and
two hook-wiring error-branch tests: 97.10%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TvcuVhk8hTeDskp5pAJhnc
@PrzemekGalarowicz
PrzemekGalarowicz merged commit 770adad into main Sep 24, 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