← back to Ticket System
front desk review fixes (TK-12235): tk claim/renew/release reject unknown flags + help intercept + claim --dry; renew/release require the lease holder (force+reason override, logged)
312dca6672a481c2c1a4397f99a2db12f1085ddf · 2026-09-25 10:18:58 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJmtLm2HzLnqEsye4iifpy
Files touched
Diff
commit 312dca6672a481c2c1a4397f99a2db12f1085ddf
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 25 10:18:58 2026 -0700
front desk review fixes (TK-12235): tk claim/renew/release reject unknown flags + help intercept + claim --dry; renew/release require the lease holder (force+reason override, logged)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJmtLm2HzLnqEsye4iifpy
---
lib.js | 12 ++++++++++--
server.js | 4 ++--
tk | 27 ++++++++++++++++++++-------
3 files changed, 32 insertions(+), 11 deletions(-)
diff --git a/lib.js b/lib.js
index 703fa545..777a1611 100644
--- a/lib.js
+++ b/lib.js
@@ -356,11 +356,15 @@ function claim({ machine = '', engine = '', agent = 'codex', projects, dry = fal
}
// Extend a live lease by LEASE_MS. Fails (throws) if lease_id doesn't match
// the current live lease, or the lease already expired.
-function renew({ id, lease_id }) {
+function renew({ id, lease_id, agent, force }) {
id = requireTicket(id);
return withLock(() => {
const l = leases().get(id);
if (!l || l.lease_id !== lease_id) throw new Error('no matching live lease for ' + id);
+ if (agent && l.agent && agent !== l.agent) {
+ if (!force) throw new Error(`lease on ${id} is held by ${l.agent}, not ${agent} (pass force with a reason to override)`);
+ append({ ts: now(), type: 'comment', id, kind: 'comment', agent, text: `FORCED renew of lease held by ${l.agent}: ${force}` });
+ }
const exp = Date.parse(l.lease_expires);
if (!Number.isFinite(exp) || exp <= Date.now()) throw new Error('lease expired for ' + id);
const tsNow = now();
@@ -371,11 +375,15 @@ function renew({ id, lease_id }) {
}
// End a lease (matching lease_id required, expired or not — so a stale local
// lease can always be cleaned up), optionally also setting ticket status.
-function release({ id, lease_id, status }) {
+function release({ id, lease_id, status, agent, force }) {
id = requireTicket(id);
return withLock(() => {
const l = leases().get(id);
if (!l || l.lease_id !== lease_id) throw new Error('no matching live lease for ' + id);
+ if (agent && l.agent && agent !== l.agent) {
+ if (!force) throw new Error(`lease on ${id} is held by ${l.agent}, not ${agent} (pass force with a reason to override)`);
+ append({ ts: now(), type: 'comment', id, kind: 'comment', agent, text: `FORCED release of lease held by ${l.agent}: ${force}` });
+ }
const tsNow = now();
append({ ts: tsNow, type: 'lease_end', id, lease_id, agent: l.agent, reason: 'release' });
if (status) {
diff --git a/server.js b/server.js
index b6551a4b..8a36017d 100644
--- a/server.js
+++ b/server.js
@@ -690,7 +690,7 @@ http.createServer((req, res) => {
if (!body) return json(res, 400, { error: 'bad json' });
const id = resolveId(body.id, cachedTickets());
if (!id) return json(res, 400, { error: 'no such ticket ' + body.id });
- try { const r = renewLease({ id, lease_id: body.lease_id }); json(res, 200, { renewed: r }); }
+ try { const r = renewLease({ id, lease_id: body.lease_id, agent: body.agent, force: body.force }); json(res, 200, { renewed: r }); }
catch (e) { json(res, 409, { error: String(e.message || e) }); }
});
}
@@ -699,7 +699,7 @@ http.createServer((req, res) => {
if (!body) return json(res, 400, { error: 'bad json' });
const id = resolveId(body.id, cachedTickets());
if (!id) return json(res, 400, { error: 'no such ticket ' + body.id });
- try { const r = releaseLease({ id, lease_id: body.lease_id, status: body.status }); invalidateViews(); json(res, 200, { released: r }); }
+ try { const r = releaseLease({ id, lease_id: body.lease_id, status: body.status, agent: body.agent, force: body.force }); invalidateViews(); json(res, 200, { released: r }); }
catch (e) { json(res, 409, { error: String(e.message || e) }); }
});
}
diff --git a/tk b/tk
index 17a6276d..855dfb35 100755
--- a/tk
+++ b/tk
@@ -200,25 +200,38 @@ if (cmd === 'new') {
console.log(`${id} → ${agent} (doing)${force && lease ? ' (forced over lease)' : ''}`);
titleWindow(id);
} else if (cmd === 'claim') {
+ // TK-12235 review: `tk claim --help` performed a REAL claim (unknown flags were ignored).
+ // Help prints usage; --dry previews without writing; any other leftover arg is refused.
+ const CLAIM_USAGE = 'usage: tk claim [--engine X] [--machine Y] [--projects a,b] [--dry]';
+ if (argv.some(a => /^(--help|-h|--h)$/i.test(a))) { console.log(CLAIM_USAGE); process.exit(0); }
+ const dry = flagSet('--dry');
const engine = opt('--engine') || '';
const machine = opt('--machine') || os.hostname();
const projectsRaw = opt('--projects');
const projects = projectsRaw ? projectsRaw.split(',').map(s => s.trim()).filter(Boolean) : undefined;
+ if (argv.length) die(`tk claim: unknown argument(s): ${argv.join(' ')}\n${CLAIM_USAGE}`);
let result;
- try { result = claim({ machine, engine, agent, projects }); }
+ try { result = claim({ machine, engine, agent, projects, dry }); }
catch (e) { die('tk claim failed: ' + e.message); }
if (!result) { console.log('(no eligible ticket)'); process.exit(0); }
+ if (dry) { console.log(`[dry] would claim ${result.id}${result.title ? ' — ' + result.title : ''}`); process.exit(0); }
console.log(`${result.id} → ${agent} (doing) lease_id=${result.lease_id} expires=${result.lease_expires}${result.title ? ' — ' + result.title : ''}`);
titleWindow(result.id);
} else if (cmd === 'renew') {
- const id = norm(argv.shift()); const lease_id = opt('--lease-id') || opt('-l');
- if (!lease_id) die('usage: tk renew TK-3 --lease-id L-xxx');
- try { const r = renew({ id, lease_id }); console.log(`${id} lease renewed → expires=${r.lease_expires}`); }
+ const RENEW_USAGE = 'usage: tk renew TK-3 --lease-id L-xxx [--force "reason"]';
+ if (argv.some(a => /^(--help|-h|--h)$/i.test(a))) { console.log(RENEW_USAGE); process.exit(0); }
+ const id = norm(argv.shift()); const lease_id = opt('--lease-id') || opt('-l'); const force = opt('--force');
+ if (!lease_id) die(RENEW_USAGE);
+ if (argv.length) die(`tk renew: unknown argument(s): ${argv.join(' ')}\n${RENEW_USAGE}`);
+ try { const r = renew({ id, lease_id, agent, force }); console.log(`${id} lease renewed → expires=${r.lease_expires}`); }
catch (e) { die('tk renew failed: ' + e.message); }
} else if (cmd === 'release') {
- const id = norm(argv.shift()); const lease_id = opt('--lease-id') || opt('-l'); const status = opt('--status');
- if (!lease_id) die('usage: tk release TK-3 --lease-id L-xxx [--status open|blocked]');
- try { release({ id, lease_id, status }); console.log(`${id} lease released${status ? ' → ' + status : ''}`); }
+ const RELEASE_USAGE = 'usage: tk release TK-3 --lease-id L-xxx [--status open|blocked] [--force "reason"]';
+ if (argv.some(a => /^(--help|-h|--h)$/i.test(a))) { console.log(RELEASE_USAGE); process.exit(0); }
+ const id = norm(argv.shift()); const lease_id = opt('--lease-id') || opt('-l'); const status = opt('--status'); const force = opt('--force');
+ if (!lease_id) die(RELEASE_USAGE);
+ if (argv.length) die(`tk release: unknown argument(s): ${argv.join(' ')}\n${RELEASE_USAGE}`);
+ try { release({ id, lease_id, status, agent, force }); console.log(`${id} lease released${status ? ' → ' + status : ''}`); }
catch (e) { die('tk release failed: ' + e.message); }
} else if (cmd === 'status') {
const id = norm(argv.shift()); const s = (argv.shift() || '').toLowerCase();
← 5b093eee Front desk: atomic lease-based ticket claim (TK-12235)
·
back to Ticket System
·
auto-data-snapshot: 2026-09-25T10:22:25 (2 data files) — dat b4c361a8 →