Coding tools · 01

Claude Code

Use Claude Code’s Stop hook to convert the end of a main Agent response into a bluebell completed event. Test locally first, then enable remote delivery.

Current official lifecycle

Why use Stop?

Claude Code defines Stop as a hook that runs when the main Agent finishes responding. It has no matcher. Callback JSON arrives on stdin and includes the session, working directory, and transcript path.

Step 1

Create a local adapter script

Save the following script at a stable path of your own, such as ~/.local/bin/claude-bluebell.py. It does not read transcript contents; it uses the file size and session ID to derive a stable ID for the turn.

claude-bluebell.py
#!/usr/bin/env python3
import hashlib, json, os, subprocess, sys

CLI = "/Applications/bluebellMac.app/Contents/Helpers/bluebellctl"

try:
    payload = json.load(sys.stdin)
except Exception:
    sys.exit(0)

if payload.get("hook_event_name") != "Stop" or payload.get("stop_hook_active"):
    sys.exit(0)

session_id = str(payload.get("session_id") or "")
transcript = str(payload.get("transcript_path") or "")
cwd = str(payload.get("cwd") or "")
if not session_id or not transcript:
    sys.exit(0)

try:
    transcript_size = os.path.getsize(transcript)
except OSError:
    sys.exit(0)

signature = f"{session_id}\0{transcript}\0{transcript_size}"
turn_id = hashlib.sha256(signature.encode()).hexdigest()[:24]
workspace_name = os.path.basename(cwd) or "Claude Code"

subprocess.run([
    CLI, "emit",
    "--source", "Claude Code",
    "--workspace-name", workspace_name,
    "--session-id", session_id,
    "--turn-id", turn_id,
    "--dedupe-key", f"claude-code:{session_id}:{turn_id}",
    "--local",
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=3, check=False)

The example keeps --local for local testing. It deliberately discards bluebell output and prevents notification failures from blocking Claude Code.

Step 2

Merge into Claude Code settings

User-level settings are usually at ~/.claude/settings.json. Merge the object below into the existing hooks.Stop; preserve other hooks and replace the example absolute paths.

settings.json fragment
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 /absolute/path/claude-bluebell.py"
          }
        ]
      }
    ]
  }
}

Step 3

Confirm one visible completion produces one record

  1. Keep bluebell Mac running and complete a short Claude Code task.
  2. After about 2 seconds, check “Recent notifications” for one record with the expected source and project name.
  3. Test continued execution and a workflow with subtasks. Confirm that neither produces unwanted extra completions.
  4. Once the behavior is stable, remove --local from the script, enable remote delivery on Mac, and test with real devices.

If your Claude Code version does not provide transcript_path, do not use this example for production deduplication. Use a stable turn ID based on that version’s official payload instead.

Sources

Claude Code official references