Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ PROMPT

# Start background TTS streamer that reads from DEBUGLOG
start_tts_streamer() {
# Kill any prior streamer before truncating the shared log. A streamer left
# running from an overlapping request keeps its read cursor at the old
# offset; truncating the log strands that cursor past EOF and the streamer
# then tails the file forever — hanging the crab stop that waits on it.
pkill -f "$LIB_DIR/tts-streamer" 2>/dev/null
: > "$DEBUGLOG"
DESKCRAB_DEBUGLOG="$DEBUGLOG" DESKCRAB_PIPER_VOICE="$PIPER_VOICE" \
DESKCRAB_PIPER_LENGTH_SCALE="${PIPER_LENGTH_SCALE:-}" \
Expand Down Expand Up @@ -203,6 +208,14 @@ run_claude_and_respond() {
--append-system-prompt "$SYSTEM_PROMPT" \
"$TEXT" > "$DEBUGLOG" 2>&1

# Guarantee the TTS streamer always receives a stop signal. claude normally
# ends its stream with a {"type":"result"} line, but if it crashed, was
# killed, or got rate-limited mid-stream it may not — and without a result
# event the streamer tails the log forever and the wait below never returns.
# extract-response ignores a result line that has no "result" field, so this
# terminator is harmless on the success path.
printf '{"type":"result"}\n' >> "$DEBUGLOG"

# Dismiss thinking notification
notify-send -t 1 -h string:x-dunst-stack-tag:deskcrab "$NOTIFY_NAME" "" 2>/dev/null

Expand Down
11 changes: 11 additions & 0 deletions lib/tts-streamer
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,24 @@ def speak(text):
pass


# Remember who launched us (the crab process). If it dies we get reparented —
# to PID 1 or, under a systemd user manager, to a subreaper — either way our
# ppid changes. That's our cue to stop, since an orphaned crab can no longer
# append the terminator and we must not tail the log forever.
ORIG_PPID = os.getppid()

while not os.path.exists(LOG):
time.sleep(0.1)

with open(LOG) as f:
while True:
line = f.readline()
if not line:
# Backstop: if our launching crab process is gone we've been
# reparented (ppid changes) — it can no longer append a terminator,
# so stop instead of tailing the log forever.
if os.getppid() != ORIG_PPID:
break
time.sleep(0.05)
continue
line = line.strip()
Expand Down