theslopmachine 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -134,18 +134,20 @@ Metadata for a Claude lane should include:
134
134
 
135
135
  - Rate-limit handling must be uninterrupted and script-managed. Use the live helper scripts only; never type manually into the tmux pane, send an ad hoc shell command to Claude, switch lanes, or ask the user whether to wait.
136
136
  - The live helpers read the tmux pane when a rate-limit/usage-limit prompt appears.
137
- - If Claude shows a wait/continue prompt, the helper presses Enter to select the wait/continue path.
137
+ - If Claude shows a wait/continue prompt such as `Stop and wait for limit to reset`, the helper presses Enter to select the wait/continue path.
138
138
  - The helper records blocked and dismissed pane snapshots under the lane runtime directory.
139
- - The helper calculates the reset time from structured metadata or screen/error text, falls back to the default quota reset window when needed, records the timer fields in `state.json`, waits, then continues the same turn in the same Claude lane.
139
+ - The helper calculates the reset time from structured metadata, hook payload text, `last_assistant_message`, and tmux pane text, falls back to the default quota reset window only when those sources do not contain a usable reset time, records the timer fields in `state.json`, waits, then continues the same turn in the same Claude lane.
140
+ - If a shell command is interrupted during a wait, rerun the same turn/status helper. It must reuse the blocked turn state and continue waiting/recovering from the recorded reset time instead of starting a duplicate prompt.
140
141
  - The continuation prompt must tell Claude to continue from the interruption point, not restart from scratch.
141
142
  - Shell timeouts during a rate-limit wait do not mean the turn failed. Run `claude_live_status.mjs`, let it reconcile or keep waiting on the same lane, then continue the same workflow step until a terminal result exists.
142
143
  - Do not report the workflow as blocked or finished because of a rate limit unless the helper scripts themselves hit an unrecoverable technical failure that cannot be repaired without user action.
143
144
 
144
- ## Development Channel Recovery
145
+ ## Tmux Prompt Injection Recovery
145
146
 
146
- - Launch must verify both `SessionStart` and channel readiness before a lane is usable.
147
- - If development channels are disabled, missing, or never become ready during launch, the helper kills only the proven-owned tmux session and relaunches the same lane before any prompt is sent.
148
- - If an existing lane has a live tmux session but the channel is unusable, relaunch with `--resume`/existing `sid` continuity when available.
147
+ - Launch must verify `SessionStart` and a captured `sid` before a lane is usable.
148
+ - Turns are sent by writing the prompt file, loading it into a tmux paste buffer, pasting it into Claude's interactive input, and pressing Enter. Hook events remain the response/completion source of truth.
149
+ - Before pasting, the helper must inspect the pane and handle known startup, confirmation, busy, and rate-limit popups heuristically. Do not paste into a pane while a popup or busy state is detected.
150
+ - If an existing lane has a live tmux session but no usable `SessionStart`/`sid`, relaunch with `--resume`/existing `sid` continuity when available.
149
151
  - If ownership cannot be proven, stop and ask the user rather than killing or replacing the session.
150
152
 
151
153
  ## Session Continuity Guarantees
@@ -22,7 +22,7 @@ The current type-check gate covers the Claude/session tooling family because the
22
22
 
23
23
  ### `claude_live_launch.mjs`
24
24
 
25
- Launches a live Claude Code session in tmux with the slopmachine channel bridge.
25
+ Launches a live Claude Code session in tmux with hook-based result capture.
26
26
 
27
27
  Required:
28
28
  - `--task-root <task-root>`
@@ -40,7 +40,7 @@ Output is JSON. Success includes `ok: true`, `sid`, `state_file`, and `result_fi
40
40
 
41
41
  ### `claude_live_turn.mjs`
42
42
 
43
- Sends one prompt into an existing live Claude lane through the channel bridge and waits for completion.
43
+ Sends one prompt into an existing live Claude lane by loading the prompt file into a tmux paste buffer, pasting it into Claude's interactive input, pressing Enter, and waiting for hook completion.
44
44
 
45
45
  Required:
46
46
  - `--runtime-dir <dir>`
@@ -58,7 +58,7 @@ Reads the live-lane runtime state.
58
58
  Required:
59
59
  - `--runtime-dir <dir>`
60
60
 
61
- Output is JSON with lane status, cwd, sid, runtime files, transcript path, channel state, and last error.
61
+ Output is JSON with lane status, cwd, sid, runtime files, transcript path, tmux state, and last error.
62
62
 
63
63
  ### `claude_live_stop.mjs`
64
64
 
@@ -69,16 +69,6 @@ Required:
69
69
 
70
70
  The helper only kills tmux when bridge state proves ownership. If ownership is ambiguous, it leaves tmux alone and reports the reason.
71
71
 
72
- ### `claude_live_channel.mjs`
73
-
74
- Internal JSON-RPC/HTTP bridge used by live lanes. It accepts Claude initialization over stdio and POSTs from `claude_live_turn.mjs`, then emits `notifications/claude/channel` to Claude.
75
-
76
- Required:
77
- - `--port <port>`
78
- - `--token <secret>`
79
- - `--state-file <file>`
80
- - `--log-file <file>`
81
-
82
72
  ### `claude_live_hook.py`
83
73
 
84
74
  Claude Code hook used by live lanes to append hook events under the runtime directory.
@@ -123,14 +113,12 @@ Packages the Claude project directory associated with a task root.
123
113
 
124
114
  Required:
125
115
  - `--task-root <task-root>`
126
- - `--output <zip-path>`
127
116
 
128
117
  Important options:
129
- - `--session-ids <comma-separated-session-ids>` narrows the expected tracked sessions
130
- - `--metadata-file <path>` overrides the default `<task-root>/metadata.json`
118
+ - `--output <zip-path>` writes the zip to a custom path; default is `<task-root>/claude-sessions.zip`
131
119
  - `--label <text>` adds reporting context
132
120
 
133
- The helper normalizes top-level JSONL transcripts, preserves the raw project directory structure, and writes a single zip.
121
+ The helper copies the Claude project/session directory as-is, removes `.DS_Store` files and top-level `.jsonl` transcripts under 25KB from the staged copy, zips the folder contents directly, and writes a single zip. It does not normalize or rewrite transcript files.
134
122
 
135
123
  ### `analyze_claude_project_dir.mjs`
136
124
 
@@ -194,10 +182,6 @@ Common usage:
194
182
  - `python3 convert_ai_session.py -i session.jsonl -o converted.json`
195
183
  - `python3 convert_ai_session.py -i session.jsonl --format claude`
196
184
 
197
- ### `normalize_claude_session.py`
198
-
199
- Normalizes Claude JSONL transcript structure for packaging and review.
200
-
201
185
  ### `strip_session_parent.py`
202
186
 
203
187
  Removes parent/session ancestry fields from exported session artifacts when needed for sanitized review.
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from 'node:fs/promises'
4
- import http from 'node:http'
5
4
  import net from 'node:net'
6
5
  import path from 'node:path'
7
6
  import crypto from 'node:crypto'
@@ -14,7 +13,6 @@ export { emitFailure, emitSuccess, parseArgs, printUsageAndExit, readPrompt, rea
14
13
 
15
14
  export const DEFAULT_LAUNCH_TIMEOUT_MS = 3600000
16
15
  export const DEFAULT_TURN_TIMEOUT_MS = 3600000
17
- export const DEFAULT_CHANNEL_POST_TIMEOUT_MS = 10000
18
16
  export const DEFAULT_POLL_INTERVAL_MS = 500
19
17
  export const ROOT_PHASE_TITLES = [
20
18
  'P1 Clarification',
@@ -35,9 +33,6 @@ export function buildRuntimePaths(runtimeDir) {
35
33
  stateFile: path.join(absoluteRuntimeDir, 'state.json'),
36
34
  resultFile: path.join(absoluteRuntimeDir, 'result.json'),
37
35
  settingsFile: path.join(absoluteRuntimeDir, 'settings.json'),
38
- mcpConfigFile: path.join(absoluteRuntimeDir, 'mcp.json'),
39
- channelStateFile: path.join(absoluteRuntimeDir, 'channel-state.json'),
40
- channelLogFile: path.join(absoluteRuntimeDir, 'channel.log'),
41
36
  hookEventsFile: path.join(absoluteRuntimeDir, 'hook-events.jsonl'),
42
37
  turnsDir: path.join(absoluteRuntimeDir, 'turns'),
43
38
  }
@@ -81,8 +76,6 @@ export async function writeState(runtimeDir, patch) {
81
76
 
82
77
  export async function clearRuntimeArtifacts(paths) {
83
78
  await writeFileIfNeeded(paths.hookEventsFile, '')
84
- await writeFileIfNeeded(paths.channelLogFile, '')
85
- await fs.rm(paths.channelStateFile, { force: true })
86
79
  await fs.rm(paths.resultFile, { force: true })
87
80
  }
88
81
 
@@ -324,21 +317,23 @@ export async function captureTmuxPaneArtifact(sessionName, filePath) {
324
317
  return pane
325
318
  }
326
319
 
327
- export function detectClaudeDevelopmentChannelDisabled(pane) {
328
- if (!pane) {
329
- return false
330
- }
320
+ export async function tmuxSendEnter(sessionName) {
321
+ return runCommand('tmux', ['send-keys', '-t', sessionName, 'Enter'])
322
+ }
331
323
 
332
- const normalized = pane.toLowerCase()
333
- if (normalized.includes('listening for channel messages from:')) {
334
- return false
324
+ export async function tmuxPasteFileAndEnter(sessionName, filePath, bufferName) {
325
+ const loadResult = await runCommand('tmux', ['load-buffer', '-b', bufferName, filePath])
326
+ if (loadResult.code !== 0) {
327
+ return loadResult
335
328
  }
336
329
 
337
- return /development channels? (?:are )?(?:disabled|not enabled|unavailable)|channels? (?:are )?disabled|failed to (?:load|start).*development channels?|unknown option.*dangerously-load-development-channels|mcp .*?(?:disabled|failed|unavailable)/i.test(normalized)
338
- }
330
+ const pasteResult = await runCommand('tmux', ['paste-buffer', '-t', sessionName, '-b', bufferName])
331
+ await runCommand('tmux', ['delete-buffer', '-b', bufferName])
332
+ if (pasteResult.code !== 0) {
333
+ return pasteResult
334
+ }
339
335
 
340
- export async function tmuxSendEnter(sessionName) {
341
- return runCommand('tmux', ['send-keys', '-t', sessionName, 'Enter'])
336
+ return tmuxSendEnter(sessionName)
342
337
  }
343
338
 
344
339
  function detectClaudeRateLimitPrompt(pane) {
@@ -348,17 +343,20 @@ function detectClaudeRateLimitPrompt(pane) {
348
343
 
349
344
  const normalized = pane.toLowerCase()
350
345
  const hasRateLimitSignal = /hit your limit|usage limit|rate limit|capacity|overloaded|try again/i.test(normalized)
346
+ const hasResetTimeSignal = /\breset(?:s| time)?\s+(?:at\s+)?\d{1,2}(?::\d{2})?\s*(?:am|pm)?(?:\s*\([^)]+\))?/i.test(pane)
351
347
  const hasContinueSignal = /press\s+(enter|return)|hit\s+(enter|return)|enter\s+to\s+continue|return\s+to\s+continue|enter\s+to\s+confirm|return\s+to\s+confirm/i.test(normalized)
348
+ const hasSelectedWaitOption = /(?:❯|>|➜|→)?\s*\d+\.\s*stop and wait for limit to reset/i.test(pane)
349
+ const hasPlainWaitOption = /stop and wait for limit to reset/i.test(normalized)
352
350
  const hasWaitMenuSignal = normalized.includes('what do you want to do?')
353
351
  && normalized.includes('stop and wait for limit to reset')
354
352
  const hasWaitMenuOptions = normalized.includes('request more')
355
353
  && /enter\s+to\s+confirm|return\s+to\s+confirm/.test(normalized)
356
354
 
357
- if (hasWaitMenuSignal || hasWaitMenuOptions) {
355
+ if (hasSelectedWaitOption || hasWaitMenuSignal || hasWaitMenuOptions) {
358
356
  return true
359
357
  }
360
358
 
361
- return hasRateLimitSignal && (hasContinueSignal || hasWaitMenuSignal)
359
+ return hasRateLimitSignal && (hasContinueSignal || hasPlainWaitOption || hasResetTimeSignal)
362
360
  }
363
361
 
364
362
  export async function maybeDismissClaudeRateLimitPrompt(sessionName, timeoutMs = 15000) {
@@ -440,13 +438,6 @@ function detectClaudeStartupPrompt(pane) {
440
438
  return 'workspace-trust'
441
439
  }
442
440
 
443
- if (
444
- pane.includes('WARNING: Loading development channels')
445
- || pane.includes('I am using this for local development')
446
- ) {
447
- return 'development-channels'
448
- }
449
-
450
441
  return null
451
442
  }
452
443
 
@@ -457,10 +448,6 @@ export async function maybeAcceptClaudeStartupPrompts(sessionName, timeoutMs = 3
457
448
 
458
449
  while (Date.now() < deadline) {
459
450
  const pane = await tmuxCapturePane(sessionName)
460
- if (pane.includes('Listening for channel messages from:')) {
461
- return false
462
- }
463
-
464
451
  const promptKind = detectClaudeStartupPrompt(pane)
465
452
  if (promptKind) {
466
453
  const now = Date.now()
@@ -482,17 +469,64 @@ export async function maybeAcceptClaudeStartupPrompts(sessionName, timeoutMs = 3
482
469
  return false
483
470
  }
484
471
 
485
- export async function waitForChannelReady(paths, timeoutMs) {
486
- return waitFor(async () => {
487
- const channelState = await readJsonIfExists(paths.channelStateFile)
488
- if (channelState?.ready_for_notifications) {
489
- return channelState
490
- }
472
+ export function detectClaudePopupOrBlockedPane(pane) {
473
+ if (!pane) {
491
474
  return null
492
- }, {
493
- timeoutMs,
494
- errorMessage: 'Timed out waiting for Claude channel readiness',
495
- })
475
+ }
476
+
477
+ const startupPrompt = detectClaudeStartupPrompt(pane)
478
+ if (startupPrompt) {
479
+ return startupPrompt
480
+ }
481
+
482
+ if (detectClaudeRateLimitPrompt(pane)) {
483
+ return 'rate-limit'
484
+ }
485
+
486
+ const normalized = pane.toLowerCase()
487
+ if (/press\s+(enter|return)|hit\s+(enter|return)|enter\s+to\s+continue|return\s+to\s+continue|enter\s+to\s+confirm|return\s+to\s+confirm/.test(normalized)) {
488
+ return 'enter-confirmation'
489
+ }
490
+
491
+ if (/\b(yes|no)\b.*\b(continue|proceed|trust|allow)|\b(continue|proceed|trust|allow)\b.*\b(yes|no)\b/.test(normalized)) {
492
+ return 'confirmation-menu'
493
+ }
494
+
495
+ if (/esc to interrupt|ctrl-c to cancel|interrupt/i.test(pane)) {
496
+ return 'busy'
497
+ }
498
+
499
+ return null
500
+ }
501
+
502
+ export async function prepareClaudePaneForPrompt(sessionName, { statePath = null, timeoutMs = 30000 } = {}) {
503
+ const deadline = Date.now() + timeoutMs
504
+ let lastPane = ''
505
+ let lastBlocker = null
506
+
507
+ while (Date.now() < deadline) {
508
+ await maybeAcceptClaudeStartupPrompts(sessionName, 1000)
509
+ const pane = await tmuxCapturePane(sessionName)
510
+ lastPane = pane
511
+ lastBlocker = detectClaudePopupOrBlockedPane(pane)
512
+
513
+ if (lastBlocker === 'rate-limit') {
514
+ const popupDismissed = await maybeDismissClaudeRateLimitPrompt(sessionName, 5000)
515
+ const waitInfo = await waitForRateLimitReset({
516
+ message: pane,
517
+ statePath,
518
+ })
519
+ return { ready: false, blocker: 'rate-limit', popup_dismissed: popupDismissed, wait_info: waitInfo }
520
+ }
521
+
522
+ if (!lastBlocker) {
523
+ return { ready: true, blocker: null, pane }
524
+ }
525
+
526
+ await sleep(DEFAULT_POLL_INTERVAL_MS)
527
+ }
528
+
529
+ return { ready: false, blocker: lastBlocker || 'unknown', pane: lastPane }
496
530
  }
497
531
 
498
532
  export async function readJsonl(filePath) {
@@ -550,32 +584,7 @@ export function buildHookSettings({ runtimeDir, utilsDir, agentName = 'developer
550
584
  return settings
551
585
  }
552
586
 
553
- export function buildMcpConfig({ paths, utilsDir, channelName, lane, port, token }) {
554
- return {
555
- mcpServers: {
556
- [channelName]: {
557
- command: 'node',
558
- args: [
559
- path.join(utilsDir, 'claude_live_channel.mjs'),
560
- '--port',
561
- String(port),
562
- '--token',
563
- token,
564
- '--state-file',
565
- paths.channelStateFile,
566
- '--log-file',
567
- paths.channelLogFile,
568
- '--channel-name',
569
- channelName,
570
- '--lane',
571
- lane,
572
- ],
573
- },
574
- },
575
- }
576
- }
577
-
578
- export function buildClaudeLaunchCommand({ claudeCommand, agentName, displayName, settingsFile, mcpConfigFile, channelName, model, effort = null, subagentModel = 'sonnet', supportsSubagentModel = false, resumeSessionId = null }) {
587
+ export function buildClaudeLaunchCommand({ claudeCommand, agentName, settingsFile, model, effort = null, subagentModel = 'sonnet', supportsSubagentModel = false, resumeSessionId = null }) {
579
588
  const parts = []
580
589
 
581
590
  if (subagentModel) {
@@ -586,15 +595,9 @@ export function buildClaudeLaunchCommand({ claudeCommand, agentName, displayName
586
595
  shellQuote(claudeCommand),
587
596
  '--agent',
588
597
  shellQuote(agentName),
589
- '-n',
590
- shellQuote(displayName),
591
598
  '--settings',
592
599
  shellQuote(settingsFile),
593
- '--mcp-config',
594
- shellQuote(mcpConfigFile),
595
600
  '--dangerously-skip-permissions',
596
- '--dangerously-load-development-channels',
597
- shellQuote(`server:${channelName}`),
598
601
  )
599
602
 
600
603
  if (resumeSessionId) {
@@ -616,43 +619,6 @@ export function buildClaudeLaunchCommand({ claudeCommand, agentName, displayName
616
619
  return parts.join(' ')
617
620
  }
618
621
 
619
- export async function postPromptToChannel({ state, prompt, turnId, timeoutMs = DEFAULT_CHANNEL_POST_TIMEOUT_MS }) {
620
- return new Promise((resolve, reject) => {
621
- const request = http.request({
622
- hostname: '127.0.0.1',
623
- port: state.channel_port,
624
- path: '/',
625
- method: 'POST',
626
- headers: {
627
- 'content-type': 'text/plain; charset=utf-8',
628
- 'content-length': Buffer.byteLength(prompt, 'utf8'),
629
- 'x-bridge-token': state.channel_token,
630
- 'x-lane': state.lane,
631
- 'x-turn-id': turnId,
632
- },
633
- timeout: timeoutMs,
634
- }, (response) => {
635
- let body = ''
636
- response.on('data', (chunk) => {
637
- body += chunk.toString()
638
- })
639
- response.on('end', () => {
640
- resolve({
641
- statusCode: response.statusCode || 0,
642
- body,
643
- })
644
- })
645
- })
646
-
647
- request.on('error', reject)
648
- request.on('timeout', () => {
649
- request.destroy(new Error('channel_post_timeout'))
650
- })
651
- request.write(prompt)
652
- request.end()
653
- })
654
- }
655
-
656
622
  export function buildTurnId(lastTurnNumber) {
657
623
  return String(lastTurnNumber + 1).padStart(4, '0')
658
624
  }
@@ -673,6 +639,7 @@ export function extractFailureMessage(payload) {
673
639
  if (!payload) return ''
674
640
 
675
641
  const candidates = [
642
+ payload.last_assistant_message,
676
643
  payload.message,
677
644
  payload.error,
678
645
  payload.failure_reason,
@@ -698,7 +665,7 @@ export function classifyStopFailure(event, fallbackSid = null) {
698
665
  const message = extractFailureMessage(payload) || 'claude_stop_failure'
699
666
  const rateLimit = extractRateLimitMetadata(payload)
700
667
 
701
- if (/hit your limit|usage limit|capacity|overloaded/i.test(message)) {
668
+ if (/hit your limit|usage limit|rate[_ -]?limit|capacity|overloaded/i.test(message)) {
702
669
  return {
703
670
  result: { ok: false, code: 'claude_usage_limit', msg: 'usage_limit', detail: message, rate_limit: rateLimit, sid },
704
671
  nextStatus: 'blocked',
@@ -4,21 +4,17 @@ import path from 'node:path'
4
4
 
5
5
  import {
6
6
  DEFAULT_LAUNCH_TIMEOUT_MS,
7
- allocatePort,
8
7
  assessOwnedTmuxSession,
9
8
  buildClaudeLaunchCommand,
10
9
  buildHookSettings,
11
- buildMcpConfig,
12
10
  buildRuntimePaths,
13
11
  captureTmuxPaneArtifact,
14
12
  clearRuntimeArtifacts,
15
- detectClaudeDevelopmentChannelDisabled,
16
13
  detectClaudeCliCapabilities,
17
14
  emitFailure,
18
15
  emitSuccess,
19
16
  ensureRuntimeDirs,
20
17
  makeSuffix,
21
- makeToken,
22
18
  maybeAcceptClaudeStartupPrompts,
23
19
  maybeDismissClaudeRateLimitPrompt,
24
20
  parseArgs,
@@ -32,7 +28,6 @@ import {
32
28
  tmuxKillSession,
33
29
  tmuxCapturePane,
34
30
  waitForRateLimitReset,
35
- waitForChannelReady,
36
31
  waitForHookEvent,
37
32
  writeJsonIfNeeded,
38
33
  writeState,
@@ -76,23 +71,20 @@ function detectRateLimitFromPane(pane) {
76
71
 
77
72
  function detectStartupPromptFromPane(pane) {
78
73
  if (!pane) return false
79
- return /quick safety check:|yes, i trust this folder|accessing workspace:|warning: loading development channels|i am using this for local development/i.test(pane)
74
+ return /quick safety check:|yes, i trust this folder|accessing workspace:/i.test(pane)
80
75
  }
81
76
 
82
77
  async function inspectLaunchState(paths, tmuxSession) {
83
- const [tmuxAlive, channelState, events, pane] = await Promise.all([
78
+ const [tmuxAlive, events, pane] = await Promise.all([
84
79
  tmuxHasSession(tmuxSession),
85
- readJsonIfExists(paths.channelStateFile),
86
80
  readJsonl(paths.hookEventsFile),
87
81
  tmuxCapturePane(tmuxSession),
88
82
  ])
89
83
 
90
84
  const sessionStart = events.find((event) => event?.label === 'SessionStart') || null
91
85
  const sessionId = sessionStart?.payload?.session_id || null
92
- const channelReady = Boolean(channelState?.ready_for_notifications)
93
86
  const rateLimited = detectRateLimitFromPane(pane)
94
87
  const startupPromptVisible = detectStartupPromptFromPane(pane)
95
- const developmentChannelDisabled = detectClaudeDevelopmentChannelDisabled(pane)
96
88
 
97
89
  let code = 'claude_launch_not_ready'
98
90
  let message = 'Claude launch did not reach a ready session state yet.'
@@ -102,16 +94,7 @@ async function inspectLaunchState(paths, tmuxSession) {
102
94
  } else if (rateLimited) {
103
95
  code = 'claude_launch_rate_limited'
104
96
  message = 'Claude launch is blocked by a rate-limit or capacity prompt.'
105
- } else if (developmentChannelDisabled) {
106
- code = 'claude_launch_development_channels_disabled'
107
- message = 'Claude launch appears to have development channels disabled; this lane must be restarted before any prompt is sent.'
108
- } else if (sessionStart && !channelReady) {
109
- code = 'claude_launch_no_channel_ready'
110
- message = 'Claude session started but the channel never became ready.'
111
- } else if (!sessionStart && channelReady) {
112
- code = 'claude_launch_no_sessionstart'
113
- message = 'Claude channel became ready but the SessionStart hook event never arrived.'
114
- } else if (sessionStart && channelReady && !sessionId) {
97
+ } else if (sessionStart && !sessionId) {
115
98
  code = 'claude_launch_missing_session_id'
116
99
  message = 'Claude launch reached partial readiness but no session id was captured.'
117
100
  } else if (startupPromptVisible) {
@@ -121,12 +104,10 @@ async function inspectLaunchState(paths, tmuxSession) {
121
104
 
122
105
  return {
123
106
  tmuxAlive,
124
- channelReady,
125
107
  sessionStart,
126
108
  sessionId,
127
109
  rateLimited,
128
110
  startupPromptVisible,
129
- developmentChannelDisabled,
130
111
  code,
131
112
  message,
132
113
  }
@@ -135,8 +116,6 @@ async function inspectLaunchState(paths, tmuxSession) {
135
116
  function shouldRetryFreshTmuxLaunch(error) {
136
117
  const code = error && typeof error === 'object' ? error.code : null
137
118
  return [
138
- 'claude_launch_development_channels_disabled',
139
- 'claude_launch_no_channel_ready',
140
119
  'claude_launch_no_sessionstart',
141
120
  'claude_launch_not_ready',
142
121
  'claude_launch_prompt_blocked',
@@ -156,19 +135,21 @@ async function waitForLaunchReadyWithRecovery({ paths, tmuxSession, launchTimeou
156
135
  maybeDismissClaudeRateLimitPrompt(tmuxSession, Math.min(windowMs, 5000)),
157
136
  ])
158
137
 
159
- const [hookResult, channelResult] = await Promise.allSettled([
160
- waitForHookEvent(paths, 0, new Set(['SessionStart']), windowMs),
161
- waitForChannelReady(paths, windowMs),
162
- ])
138
+ let hookReady = null
139
+ try {
140
+ hookReady = await waitForHookEvent(
141
+ paths,
142
+ 0,
143
+ new Set(['SessionStart']),
144
+ windowMs,
145
+ )
146
+ } catch {}
163
147
 
164
- const hookReady = hookResult.status === 'fulfilled' ? hookResult.value : null
165
- const channelReady = channelResult.status === 'fulfilled' ? channelResult.value : null
166
148
  const sessionId = hookReady?.event?.payload?.session_id || null
167
149
 
168
- if (hookReady && channelReady && sessionId) {
150
+ if (hookReady && sessionId) {
169
151
  return {
170
152
  event: hookReady.event,
171
- channelState: channelReady,
172
153
  }
173
154
  }
174
155
 
@@ -234,7 +215,6 @@ Options:
234
215
  --resume-sid <sid> Resume an explicit Claude session id
235
216
  --replace 1 Replace an existing live lane state/session
236
217
  --tmux-session <name> Override tmux session name
237
- --channel-name <name> Override bridge channel name
238
218
  `)
239
219
  }
240
220
 
@@ -269,10 +249,8 @@ try {
269
249
  if (existingState?.tmux_session && await tmuxHasSession(existingState.tmux_session)) {
270
250
  if (!replace) {
271
251
  const existingInspection = await inspectLaunchState(paths, existingState.tmux_session)
272
- if (existingInspection.developmentChannelDisabled || !existingInspection.channelReady) {
273
- const code = existingInspection.developmentChannelDisabled
274
- ? 'claude_existing_development_channels_disabled'
275
- : 'claude_existing_channel_not_ready'
252
+ if (!existingInspection.sessionStart || !existingInspection.sessionId) {
253
+ const code = 'claude_existing_not_ready'
276
254
  const ownership = await assessOwnedTmuxSession({ runtimeDir, state: existingState, cwd })
277
255
  if (!ownership.ok) {
278
256
  emitFailure(code, `${existingInspection.message} Refusing automatic restart because tmux ownership is not proven: ${ownership.reason}`, {
@@ -353,15 +331,10 @@ try {
353
331
  const cliCapabilities = await detectClaudeCliCapabilities(claudeCommand)
354
332
 
355
333
  async function launchAttempt(attemptNumber, attemptsTotal) {
356
- const suffix = argv['tmux-session'] || argv['channel-name'] ? makeSuffix() : makeSuffix()
334
+ const suffix = makeSuffix()
357
335
  const tmuxSession = argv['tmux-session'] && attemptNumber === 1
358
336
  ? argv['tmux-session']
359
337
  : `sm-${sanitizeName(lane)}-${suffix}`
360
- const channelName = argv['channel-name'] && attemptNumber === 1
361
- ? argv['channel-name']
362
- : `slopmachine-${sanitizeName(lane)}-${suffix}`
363
- const channelPort = await allocatePort()
364
- const channelToken = makeToken()
365
338
 
366
339
  await clearRuntimeArtifacts(paths)
367
340
  await writeState(runtimeDir, {
@@ -376,14 +349,9 @@ try {
376
349
  effort: laneEffort,
377
350
  subagent_model: subagentModel,
378
351
  tmux_session: tmuxSession,
379
- channel_name: channelName,
380
- channel_port: channelPort,
381
- channel_token: channelToken,
382
352
  runtime_dir: paths.runtimeDir,
383
353
  settings_file: paths.settingsFile,
384
- mcp_config_file: paths.mcpConfigFile,
385
354
  hook_events_file: paths.hookEventsFile,
386
- channel_state_file: paths.channelStateFile,
387
355
  result_file: paths.resultFile,
388
356
  transcript_path: null,
389
357
  current_turn_id: null,
@@ -396,30 +364,17 @@ try {
396
364
  started_at: new Date().toISOString(),
397
365
  })
398
366
 
399
- await Promise.all([
400
- writeJsonIfNeeded(paths.settingsFile, buildHookSettings({
401
- runtimeDir: paths.runtimeDir,
402
- utilsDir,
403
- agentName,
404
- subagentModel,
405
- })),
406
- writeJsonIfNeeded(paths.mcpConfigFile, buildMcpConfig({
407
- paths,
408
- utilsDir,
409
- channelName,
410
- lane,
411
- port: channelPort,
412
- token: channelToken,
413
- })),
414
- ])
367
+ await writeJsonIfNeeded(paths.settingsFile, buildHookSettings({
368
+ runtimeDir: paths.runtimeDir,
369
+ utilsDir,
370
+ agentName,
371
+ subagentModel,
372
+ }))
415
373
 
416
374
  const launchCommand = buildClaudeLaunchCommand({
417
375
  claudeCommand,
418
376
  agentName,
419
- displayName: lane,
420
377
  settingsFile: paths.settingsFile,
421
- mcpConfigFile: paths.mcpConfigFile,
422
- channelName,
423
378
  model: laneModel,
424
379
  effort: laneEffort,
425
380
  subagentModel,
@@ -440,7 +395,7 @@ try {
440
395
 
441
396
  emitTmuxLaunchHint({ tmuxSession, cwd })
442
397
 
443
- const { event, channelState } = await waitForLaunchReadyWithRecovery({
398
+ const { event } = await waitForLaunchReadyWithRecovery({
444
399
  paths,
445
400
  tmuxSession,
446
401
  launchTimeoutMs,
@@ -455,7 +410,6 @@ try {
455
410
  transcript_path: transcriptPath,
456
411
  last_hook_event: 'SessionStart',
457
412
  last_error: null,
458
- channel_status: channelState.status || 'ready',
459
413
  launch_attempt: attemptNumber,
460
414
  launch_attempts_total: attemptsTotal,
461
415
  })