Skip to content

post-commit/post-checkout hooks block git for the duration of full rebuild — should detach #650

Description

@frankdinhgotit

Problem

The post-commit and post-checkout hooks installed by graphify hook install run the rebuild synchronouslygit commit (and git checkout) does not return until the full repo rebuild completes.

On a moderately large repo (~9k files), the rebuild can take several hours. While it runs:

  • git commit blocks the developer's shell for the entire duration
  • Any tooling that triggers a commit (Claude Code, IDE hooks, automation) stalls or holds open file descriptors
  • A subsequent commit before the prior rebuild finishes silently queues another rebuild on the same files

Reproduction (frank-ai-brain, ~9k files):

$ time git commit -m "..."
[graphify hook] N file(s) changed - rebuilding graph...
  AST extraction: 1/8903 files (0%)
  ...
[blocks for 4+ hours]

ps aux showed the rebuild process accumulating 256+ CPU minutes while the parent git commit was still waiting on it.

Why this matters

A post-commit hook should not block the developer's workflow. The rebuild is producing observability output (graphify-out/), not state needed by the next git command. Stalling the developer to update an out-of-band artifact violates the principle that side effects belong out of band.

Suggested fix

Detach the rebuild so the hook returns immediately and the rebuild orphans into the background. Standard portable shell idiom: nohup ... > log 2>&1 < /dev/null & followed by disown.

Patch against graphify/hooks.py _HOOK_SCRIPT:

 export GRAPHIFY_CHANGED="$CHANGED"
-$GRAPHIFY_PYTHON -c "
+
+# Run rebuild detached so the commit returns immediately. Full repo rebuild
+# can take hours; blocking the post-commit hook stalls every commit.
+LOG_FILE="$HOME/.cache/graphify-rebuild.log"
+mkdir -p "$(dirname "$LOG_FILE")"
+echo "[graphify hook] launching background rebuild (log: $LOG_FILE)"
+nohup $GRAPHIFY_PYTHON -c "
 import os, sys
 from pathlib import Path

 changed_raw = os.environ.get('GRAPHIFY_CHANGED', '')
 changed = [Path(f.strip()) for f in changed_raw.strip().splitlines() if f.strip()]

 if not changed:
     sys.exit(0)

 print(f'[graphify hook] {len(changed)} file(s) changed - rebuilding graph...')

 try:
     from graphify.watch import _rebuild_code
     _rebuild_code(Path('.'))
 except Exception as exc:
     print(f'[graphify hook] Rebuild failed: {exc}')
     sys.exit(1)
-"
+" > "$LOG_FILE" 2>&1 < /dev/null &
+disown 2>/dev/null || true
 # graphify-hook-end

Same change for _CHECKOUT_SCRIPT. The rebuild's stdout/stderr go to ~/.cache/graphify-rebuild.log (truncated each run) so users can tail -f it for status.

Result of applying this patch locally

$ time git commit -m "..."
[graphify hook] launching background rebuild (log: /Users/.../.cache/graphify-rebuild.log)
[branch hash] commit message
real    0m0.137s

Down from 4+ hours to 137 ms. Background rebuild continues independently and writes the graph as before.

Optional follow-ups

  1. Lock file~/.cache/graphify-rebuild.lock to prevent two rebuilds from racing if commits land back-to-back.
  2. Incremental rebuild — if _rebuild_code already supports incremental updates, the changed-file list (GRAPHIFY_CHANGED) could feed it directly instead of re-walking the whole tree. Would obsolete most of the runtime issue.
  3. Opt-in flaggraphify hook install --detached (default) vs --blocking for users who want CI-style commit gating.

Happy to open a PR if the patch shape works for you.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions