Skip to content

Commit 9cd2696

Browse files
steveyeggeclaude
andcommitted
chore: Bump version to 0.4.0
Key fix: Orphan cleanup now skips Claude processes in valid Gas Town tmux sessions (gt-*/hq-*), preventing false kills of witnesses, refineries, and deacon during startup. Updated all component versions: - gt CLI: 0.3.1 → 0.4.0 - npm package: 0.3.0 → 0.4.0 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2b3f287 commit 9cd2696

9 files changed

Lines changed: 35 additions & 242 deletions

File tree

.beads/config.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,6 @@
6262
# - github.repo
6363
sync-branch: beads-sync
6464

65-
# Gas Town custom types (bd-t5o8i)
66-
# These types are used by Gas Town infrastructure but are not core beads types.
67-
# They will be removed from beads built-in types (bd-find4) and configured here instead.
68-
types:
69-
custom: "molecule,gate,convoy,merge-request,slot,agent,role,rig,event,message"
70-
7165
# Cross-project dependencies (gt-o3is)
7266
# Maps project names to paths for external dependency resolution
7367
# Format: external:<project>:<capability> in bd dep commands

.beads/formulas/mol-backoff-test.formula.toml

Lines changed: 0 additions & 95 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.4.0] - 2026-01-17
11+
12+
### Fixed
13+
14+
- **Orphan cleanup skips valid tmux sessions** - `gt orphans kill` and automatic orphan cleanup now check for Claude processes belonging to valid Gas Town tmux sessions (gt-*/hq-*) before killing. This prevents false kills of witnesses, refineries, and deacon during startup when they may temporarily show TTY "?"
15+
1016
## [0.3.1] - 2026-01-17
1117

1218
### Fixed

internal/cmd/info.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ type VersionChange struct {
7474

7575
// versionChanges contains agent-actionable changes for recent versions
7676
var versionChanges = []VersionChange{
77+
{
78+
Version: "0.4.0",
79+
Date: "2026-01-17",
80+
Changes: []string{
81+
"FIX: Orphan cleanup skips valid tmux sessions - Prevents false kills of witnesses/refineries/deacon during startup by checking gt-*/hq-* session membership",
82+
},
83+
},
7784
{
7885
Version: "0.3.1",
7986
Date: "2026-01-17",

internal/cmd/molecule_await_signal.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,8 @@ func calculateEffectiveTimeout(idleCycles int) (time.Duration, error) {
237237
// waitForActivitySignal starts bd activity --follow and waits for any output.
238238
// Returns immediately when a line is received, or when context is canceled.
239239
func waitForActivitySignal(ctx context.Context, workDir string) (*AwaitSignalResult, error) {
240-
// Start bd activity --follow --since 1s
241-
// The --since flag ensures we only wait for NEW events, not historical ones.
242-
// Without this, the feed would immediately return old activity and never timeout.
243-
cmd := exec.CommandContext(ctx, "bd", "activity", "--follow", "--since", "1s")
240+
// Start bd activity --follow
241+
cmd := exec.CommandContext(ctx, "bd", "activity", "--follow")
244242
cmd.Dir = workDir
245243

246244
stdout, err := cmd.StdoutPipe()

internal/cmd/statusline.go

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,9 @@ func runMayorStatusLine(t *tmux.Tmux) error {
184184

185185
// Track per-rig status for LED indicators and sorting
186186
type rigStatus struct {
187-
hasWitness bool
188-
hasRefinery bool
189-
polecatCount int
190-
opState string // "OPERATIONAL", "PARKED", or "DOCKED"
187+
hasWitness bool
188+
hasRefinery bool
189+
opState string // "OPERATIONAL", "PARKED", or "DOCKED"
191190
}
192191
rigStatuses := make(map[string]*rigStatus)
193192

@@ -202,10 +201,8 @@ func runMayorStatusLine(t *tmux.Tmux) error {
202201
working int
203202
}
204203
healthByType := map[AgentType]*agentHealth{
205-
AgentPolecat: {},
206204
AgentWitness: {},
207205
AgentRefinery: {},
208-
AgentDeacon: {},
209206
}
210207

211208
// Single pass: track rig status AND agent health
@@ -215,7 +212,8 @@ func runMayorStatusLine(t *tmux.Tmux) error {
215212
continue
216213
}
217214

218-
// Track rig-level status (witness/refinery/polecat presence)
215+
// Track rig-level status (witness/refinery presence)
216+
// Polecats are not tracked in tmux - they're a GC concern, not a display concern
219217
if agent.Rig != "" && registeredRigs[agent.Rig] {
220218
if rigStatuses[agent.Rig] == nil {
221219
rigStatuses[agent.Rig] = &rigStatus{}
@@ -225,8 +223,6 @@ func runMayorStatusLine(t *tmux.Tmux) error {
225223
rigStatuses[agent.Rig].hasWitness = true
226224
case AgentRefinery:
227225
rigStatuses[agent.Rig].hasRefinery = true
228-
case AgentPolecat:
229-
rigStatuses[agent.Rig].polecatCount++
230226
}
231227
}
232228

@@ -254,9 +250,10 @@ func runMayorStatusLine(t *tmux.Tmux) error {
254250
var parts []string
255251

256252
// Add per-agent-type health in consistent order
257-
// Format: "1/10 😺" = 1 working out of 10 total
253+
// Format: "1/3 👁️" = 1 working out of 3 total
258254
// Only show agent types that have sessions
259-
agentOrder := []AgentType{AgentPolecat, AgentWitness, AgentRefinery, AgentDeacon}
255+
// Note: Polecats and Deacon excluded - idle state display is misleading noise
256+
agentOrder := []AgentType{AgentWitness, AgentRefinery}
260257
var agentParts []string
261258
for _, agentType := range agentOrder {
262259
health := healthByType[agentType]
@@ -287,7 +284,7 @@ func runMayorStatusLine(t *tmux.Tmux) error {
287284
rigs = append(rigs, rigInfo{name: rigName, status: status})
288285
}
289286

290-
// Sort by: 1) running state, 2) polecat count (desc), 3) operational state, 4) alphabetical
287+
// Sort by: 1) running state, 2) operational state, 3) alphabetical
291288
sort.Slice(rigs, func(i, j int) bool {
292289
isRunningI := rigs[i].status.hasWitness || rigs[i].status.hasRefinery
293290
isRunningJ := rigs[j].status.hasWitness || rigs[j].status.hasRefinery
@@ -297,20 +294,15 @@ func runMayorStatusLine(t *tmux.Tmux) error {
297294
return isRunningI
298295
}
299296

300-
// Secondary sort: polecat count (descending)
301-
if rigs[i].status.polecatCount != rigs[j].status.polecatCount {
302-
return rigs[i].status.polecatCount > rigs[j].status.polecatCount
303-
}
304-
305-
// Tertiary sort: operational state (for non-running rigs: OPERATIONAL < PARKED < DOCKED)
297+
// Secondary sort: operational state (for non-running rigs: OPERATIONAL < PARKED < DOCKED)
306298
stateOrder := map[string]int{"OPERATIONAL": 0, "PARKED": 1, "DOCKED": 2}
307299
stateI := stateOrder[rigs[i].status.opState]
308300
stateJ := stateOrder[rigs[j].status.opState]
309301
if stateI != stateJ {
310302
return stateI < stateJ
311303
}
312304

313-
// Quaternary sort: alphabetical
305+
// Tertiary sort: alphabetical
314306
return rigs[i].name < rigs[j].name
315307
})
316308

@@ -352,17 +344,12 @@ func runMayorStatusLine(t *tmux.Tmux) error {
352344
}
353345
}
354346

355-
// Show polecat count if > 0
356347
// All icons get 1 space, Park gets 2
357348
space := " "
358349
if led == "🅿️" {
359350
space = " "
360351
}
361-
display := led + space + rig.name
362-
if status.polecatCount > 0 {
363-
display += fmt.Sprintf("(%d)", status.polecatCount)
364-
}
365-
rigParts = append(rigParts, display)
352+
rigParts = append(rigParts, led+space+rig.name)
366353
}
367354

368355
if len(rigParts) > 0 {
@@ -421,7 +408,6 @@ func runDeaconStatusLine(t *tmux.Tmux) error {
421408
}
422409

423410
rigs := make(map[string]bool)
424-
polecatCount := 0
425411
for _, s := range sessions {
426412
agent := categorizeSession(s)
427413
if agent == nil {
@@ -431,16 +417,13 @@ func runDeaconStatusLine(t *tmux.Tmux) error {
431417
if agent.Rig != "" && registeredRigs[agent.Rig] {
432418
rigs[agent.Rig] = true
433419
}
434-
if agent.Type == AgentPolecat && registeredRigs[agent.Rig] {
435-
polecatCount++
436-
}
437420
}
438421
rigCount := len(rigs)
439422

440423
// Build status
424+
// Note: Polecats excluded - they're ephemeral and idle detection is a GC concern
441425
var parts []string
442426
parts = append(parts, fmt.Sprintf("%d rigs", rigCount))
443-
parts = append(parts, fmt.Sprintf("%d 😺", polecatCount))
444427

445428
// Priority 1: Check for hooked work (town beads for deacon)
446429
hookedWork := ""
@@ -466,7 +449,8 @@ func runDeaconStatusLine(t *tmux.Tmux) error {
466449
}
467450

468451
// runWitnessStatusLine outputs status for a witness session.
469-
// Shows: polecat count, crew count, hook or mail preview
452+
// Shows: crew count, hook or mail preview
453+
// Note: Polecats excluded - they're ephemeral and idle detection is a GC concern
470454
func runWitnessStatusLine(t *tmux.Tmux, rigName string) error {
471455
if rigName == "" {
472456
// Try to extract from session name: gt-<rig>-witness
@@ -483,33 +467,27 @@ func runWitnessStatusLine(t *tmux.Tmux, rigName string) error {
483467
townRoot, _ = workspace.Find(paneDir)
484468
}
485469

486-
// Count polecats and crew in this rig
470+
// Count crew in this rig (crew are persistent, worth tracking)
487471
sessions, err := t.ListSessions()
488472
if err != nil {
489473
return nil // Silent fail
490474
}
491475

492-
polecatCount := 0
493476
crewCount := 0
494477
for _, s := range sessions {
495478
agent := categorizeSession(s)
496479
if agent == nil {
497480
continue
498481
}
499-
if agent.Rig == rigName {
500-
if agent.Type == AgentPolecat {
501-
polecatCount++
502-
} else if agent.Type == AgentCrew {
503-
crewCount++
504-
}
482+
if agent.Rig == rigName && agent.Type == AgentCrew {
483+
crewCount++
505484
}
506485
}
507486

508487
identity := fmt.Sprintf("%s/witness", rigName)
509488

510489
// Build status
511490
var parts []string
512-
parts = append(parts, fmt.Sprintf("%d 😺", polecatCount))
513491
if crewCount > 0 {
514492
parts = append(parts, fmt.Sprintf("%d crew", crewCount))
515493
}

internal/cmd/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
// Version information - set at build time via ldflags
1414
var (
15-
Version = "0.3.1"
15+
Version = "0.4.0"
1616
// Build can be set via ldflags at compile time
1717
Build = "dev"
1818
// Commit and Branch - the git revision the binary was built from (optional ldflag)

0 commit comments

Comments
 (0)