Skip to content

feat(mine): graceful Ctrl-C handling — clean message + exit, no traceback #1182

@igorls

Description

@igorls

Problem

mempalace mine <dir> on a large directory is slow (thousands of files × embedding + upsert). Users commonly Ctrl-C if they realize they pointed at the wrong dir, or just want to stop and come back later. Today this produces a bare KeyboardInterrupt traceback through multiple stack frames — something like:

File ".../mempalace/cli.py", line 168, in cmd_mine
    mine(project_dir=args.dir, ...)
File ".../mempalace/miner.py", line 898, in mine
    drawers, room = process_file(...)
File ".../mempalace/miner.py", line 730, in process_file
    added = add_drawer(...)
File ".../mempalace/miner.py", line 662, in add_drawer
    collection.upsert(...)
KeyboardInterrupt

This is ugly, and the user has no signal about:

  • How many files were processed before the interrupt
  • Whether partial progress is safe (it is — mine is idempotent on re-run via deterministic drawer IDs)
  • Whether anything needs cleanup

Proposal

Wrap the main mine() loop in a try / except KeyboardInterrupt block and emit a clean summary:


  Mine interrupted.
    files_processed: N/M
    drawers_filed:   K
    last_file:       <filename>

  Re-run `mempalace mine <dir>` to resume — already-filed drawers are
  upserted idempotently and will not duplicate.

Exit with code 130 (standard for SIGINT) so shell scripts can distinguish interrupt from error.

Edge cases

  • Mid-upsert interrupt. ChromaDB upsert() calls are per-batch atomic; a half-processed file is fine because re-running produces the same drawer IDs.
  • Mid-closet-build interrupt. Closets are rebuilt per file rather than accumulated mid-file — interrupting between files is clean; interrupting during closet LLM/regex pass for a given file means that file's closets will be recomputed on re-mine (acceptable).
  • PID file guard. The existing per-source-directory PID file (fix(hooks): PID file guard prevents stacking mine processes #1023) should be cleaned up in a finally so a future mine doesn't see a stale PID.
  • Second Ctrl-C. If the user hits Ctrl-C again during the summary print, fall through to the default handler. Don't try to handle everything.

Tests

  • Unit: mine() raises KeyboardInterrupt internally → caller sees exit code 130 and cleanup message
  • PID file is removed on interrupt
  • Re-running after interrupt does not double-file drawers (already covered by idempotency tests; add one that specifically interrupts-then-resumes)

Out of scope

  • Checkpoint-to-disk so re-mine skips work already done (current path already avoids re-embedding via dedup, sufficient for now)
  • Making individual file processing interruptible at sub-file granularity

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions