← back to Ticket System
tk: self-title iTerm window (name + durable user.tk) on take/new/doing (TK-10573)
18e23f99e8fffdb6090a0eafa9a35cba379f1d4a · 2026-08-20 16:08:20 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 18e23f99e8fffdb6090a0eafa9a35cba379f1d4a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 20 16:08:20 2026 -0700
tk: self-title iTerm window (name + durable user.tk) on take/new/doing (TK-10573)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
tk | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/tk b/tk
index 4b6a830b..b92889e9 100755
--- a/tk
+++ b/tk
@@ -37,6 +37,21 @@ const die = m => { console.error(m); process.exit(1); };
// canonical stored id — legacy short ids and new TK-00024-slug ids both work.
const norm = ref => { if (!ref) die('missing ticket id'); const id = resolveId(ref); if (!id) die('no such ticket ' + ref); return id; };
const fmt = t => `${t.id} [${t.status}] (${t.assignee || 'unassigned'})${t.project ? ' {' + t.project + '}' : ''} ${t.title}`;
+// Self-title the iTerm2 window with this ticket's short number (TK-<n>) when a session
+// binds to a ticket (take/new/doing) — so even a MANUALLY-opened Claude window labels
+// itself, not just run-ticket.sh-launched ones. Best-effort, non-blocking, no-op off iTerm
+// or if the helper is absent. tk runs in the session's own subshell, which inherits
+// ITERM_SESSION_ID, so the helper titles the correct window. (Steve, TK-10573.)
+function titleWindow(id) {
+ try {
+ if (!process.env.ITERM_SESSION_ID) return;
+ const m = String(id).match(/TK-(\d+)/); if (!m) return;
+ require('child_process').spawn(
+ require('path').join(require('os').homedir(), 'bin', 'tk-iterm-title'),
+ ['TK-' + m[1]], { detached: true, stdio: 'ignore' }
+ ).unref();
+ } catch {}
+}
if (cmd === 'new') {
const project = opt('-p') || opt('--project') || '';
@@ -44,6 +59,7 @@ if (cmd === 'new') {
const title = argv.join(' ').trim(); if (!title) die('usage: tk new "title" [-p project] [-a agent]');
const ev = withLock(() => append({ ts, type: 'create', id: nextId(title), title, project, agent, body }));
console.log(ev.id);
+ titleWindow(ev.id);
} else if (cmd === 'comment' || cmd === 'note' || cmd === 'win' || cmd === 'challenge' || cmd === 'cody') {
const id = norm(argv.shift()); const text = argv.join(' ').trim(); if (!text) die('missing text');
if (!tickets().has(id)) die('no such ticket ' + id);
@@ -64,11 +80,13 @@ if (cmd === 'new') {
const id = norm(argv.shift()); if (!tickets().has(id)) die('no such ticket ' + id);
append({ ts, type: 'assign', id, agent }); append({ ts, type: 'status', id, status: 'doing', agent });
console.log(`${id} → ${agent} (doing)`);
+ titleWindow(id);
} else if (cmd === 'status') {
const id = norm(argv.shift()); const s = (argv.shift() || '').toLowerCase();
if (!STATUSES.includes(s)) die('status must be one of: ' + STATUSES.join(' '));
if (!tickets().has(id)) die('no such ticket ' + id);
append({ ts, type: 'status', id, status: s, agent }); console.log(`${id} → ${s}`);
+ if (s === 'doing') titleWindow(id);
} else if (cmd === 'done') {
const id = norm(argv.shift()); if (!tickets().has(id)) die('no such ticket ' + id);
append({ ts, type: 'status', id, status: 'done', agent }); console.log(`${id} → done`);
← c06d389b run-ticket: title iTerm windows with ticket number + job, re
·
back to Ticket System
·
ticket-board: send Cache-Control: no-store on board + API re a9217088 →