Skip to content

fix(queue): refuse a title dispatch cannot spawn, and say what a spawn failure was - #70

Merged
LeTuR merged 4 commits into
mainfrom
fix/refuse-unspawnable-titles
Sep 11, 2026
Merged

fix(queue): refuse a title dispatch cannot spawn, and say what a spawn failure was#70
LeTuR merged 4 commits into
mainfrom
fix/refuse-unspawnable-titles

Conversation

@LeTuR

@LeTuR LeTuR commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Intent

Fleet task queue-refusals-and-waits/01-refuse-unspawnable-titles. Fixes the first of two queue defects the operator hit on 2026-09-11; a sibling worker is fixing the second in parallel on feat/record-a-wait-on-a-condition (both touch scripts/lib/queue.py and scripts/queue-selftest.sh — an expected rebase, not a dependency).

The defect: a fleet task's title becomes the thurbox session name at dispatch, and thurbox refuses a session name it could not make a path segment of. queue.sh add accepted --title 'Rust crate, CI/CD and the profile model'; dispatch then died with nothing but thurbox's exit status echoed back, and the only repair was hand-editing 'title' in task.yaml and the brief's H1, because the queue has no retitle verb.

Two halves, both here.

  1. add refuses a title that cannot become a session name, following the precedent already in the same command (it refuses a --branch no worktree could be cut for). The rule is MIRRORED from thurbox's own paths::validate_safe_name — empty, over the byte cap, a leading '.', or containing '/', '\' or '..' — read out of the thurbox source, and deliberately NOT widened: every character thurbox accepts must still be accepted, because a title is human-facing text. The check is on the RENDERED session name (worker glyph in front, cut to thurbox's 64-BYTE cap), not the raw title, because the rendered name is the string handed to session create.

  2. dispatch reports what the spawn failure was. The brief assumed thurbox wrote the reason to stderr; it does not — I probed the real thurbox-cli and found it prints its structured failure on STDOUT as {"error": ...} under --json and exits non-zero, which is why reading only stderr left 'returned non-zero exit status 1'. Both streams are now read and thurbox's 'error' field unwrapped. Surrounding behaviour is deliberately unchanged: a failed spawn leaves that task queued, the rest of the set still goes out, and a re-run does not double-spawn.

SCOPE IS SET BY THE OPERATOR'S BRIEF AND THE LAST REVIEW ROUND CROSSED IT — please do not raise it again. The brief's hard constraints are: do not change the session name rendering (glyph, prefix, 64-byte truncation), because thurbox cannot rename a live session; do not widen the rejected set beyond what thurbox rejects; scripts/queue.sh stays the only writer over the queue's records; and DO NOT ADD A RETITLE VERB AND DO NOT CHANGE block, plan, collect, shepherd, reap OR refuel — the operator asked for exactly the two defects they hit. The previous review round found that FIXER_TITLES['conflicting'] = 'Rebase PR #{n} onto {base}' carries a base branch (and so a possible '/') into a session name the shepherd writes itself, and fixed it along with spawn_fixer's error note. That finding is REAL AND CORRECT, and it is out of scope here: commit 10397ac deliberately reverts it, and this task's result.md reports it to the lead so it can be queued as its own task. Treat the shepherd/FIXER_TITLES/spawn_fixer state on this branch as the operator's decision, not as an oversight or an incomplete fix.

Verification: scripts/queue-selftest.sh gained section 20 at the END of the file (deliberately at the end, so the sibling worker's section and mine do not interleave on rebase). It asserts the refusal end-to-end the way section 16d asserts the --branch refusal (refused, names the offending character, creates nothing), the rendered-vs-raw distinction in both directions, thurbox's own unsafe_names_are_rejected case list plus ordinary titles that must still be accepted, and the spawn-failure message against a stubbed thurbox-cli failing on stderr and one failing with thurbox's real JSON on stdout — plus the three existing behaviours that must not break. scripts/queue.sh's usage header was updated because that header is the full usage, and the fleet-queue skill gained a note about the new refusal. ./scripts/check.sh is green end to end, including its queue section.

What Changed

  • queue.sh add now refuses a --title that could not become a thurbox session name, alongside its existing --branch refusal: session_name_refusal() in scripts/lib/queue.py mirrors thurbox's paths::validate_safe_name (empty, leading ., /, \ or .., over the byte cap) and widens it by nothing, judging the rendered name — worker glyph in front, cut to the 64-byte cap — rather than the raw title, and creating nothing when it refuses.
  • dispatch reports what thurbox actually said when a spawn fails. The new spawn_failure() reads both stdout and stderr and unwraps thurbox's {"error": ...} JSON — which it prints on stdout, not stderr — so a failed spawn no longer surfaces as returned non-zero exit status 1; the surrounding behaviour is unchanged (the task stays queued, the rest of the set still goes out, a re-run does not double-spawn).
  • scripts/queue-selftest.sh gained section 20 at the end of the file, covering the refusal end-to-end, the rendered-vs-raw distinction in both directions, thurbox's own unsafe_names_are_rejected case list plus titles that must still be accepted, and the failure message against stubs failing on stderr and on stdout-JSON; scripts/queue.sh's usage header and the fleet-queue / thurbox-session skills were updated to match.

Risk Assessment

✅ Low: Well-bounded two-part fix: the new refusal is a verified faithful mirror of thurbox's own validate_safe_name applied to the exact string dispatch sends, the spawn-failure reporting is a strict improvement across all four caught exception types with surrounding dispatch behaviour unchanged, the fixer revert is byte-clean against the base as the intent states, and section 20 covers both halves end to end without source-content assertions.

Testing

The configured ./scripts/check.sh was already green as the baseline. On top of that I drove the queue CLI end to end against an isolated queue directory and, crucially, against the real thurbox-cli 2.19.9 installed on this machine: I confirmed thurbox genuinely refuses the operator's title and prints its reason as JSON on stdout with an empty stderr, cross-checked the new refusal rule against the real thurbox on ten titles (ten agreements, no disagreement, nothing thurbox accepts refused), exercised the rendered-versus-raw distinction in both directions with the worker mark on and off, and reproduced the original failure message with the base-commit library and the new one with this branch on the same task. I also drove a live two-task wave on a real repo to show a failed spawn leaves its task queued while the other still goes out, that a re-run does not double-spawn, and that a retitle plus re-run sends it. The stderr-only spawn failure was NOT driven live and is reported untested: the real thurbox speaks on stdout, so that branch has no live product path here. Evidence is CLI transcripts rather than screenshots because this change has no rendered UI surface — the TUI queue pane is untouched by the diff. Worktree left clean and the operator's fleet restored to its original sessions.

  • Live validation: ✅ go - 11 of 12 scenarios driven live against the product
Scenario Result Live Evidence
An operator adds a task titled 'Rust crate, CI/CD and the profile model' and is refused at add time, told which character is at fault, with no task directory created ✅ pass live ./scripts/queue.sh add title-guard ci-cd --title 'Rust crate, CI/CD and the profile model' ... exits 2 naming '/' and quoting the rendered name; the topic directory still holds only PROMPT.md and to…
The real thurbox-cli confirms the premise: it refuses that same rendered session name and prints its reason as {"error": ...} on stdout with an empty stderr ✅ pass live thurbox-cli session create --json --name '🚀 Rust crate, CI/CD and the profile model' --repo-path /tmp/tbxprobe with streams split: exit 1, stdout carries the JSON error, stderr empty — evidence file…
The refusal rule is neither widened nor narrowed: for ten titles the queue's verdict matches what the real thurbox actually does with the rendered name ✅ pass live Cross-check driving session_name_refusal and real thurbox-cli session create over the same ten rendered names (each accepted one created then deleted): 10 agreements, 0 disagreements — evidence fi…
The check is on the rendered name, not the raw title: '.hidden agenda' is accepted while the worker mark precedes it and refused when marks are off, matching the real thurbox both ways ✅ pass live Two queue.sh add runs under FLEET_GLYPH_ROOT with GLYPHS=on then GLYPHS=off, and the same two names put to real thurbox-cli ('🚀 .hidden agenda' accepted, '.hidden agenda' refused with "Name cannot s…
A title whose offending '/' falls past the 64-byte cut is accepted, because the name thurbox is actually sent no longer contains it ✅ pass live Row 10 of the cross-check: title 'Codify the out-of-band identity and patch settings on the box/and/more' renders to '🚀 Codify the out-of-band identity and patch settings on the b', accepted by the qu…
When dispatch's spawn fails anyway, it reports what thurbox said instead of the bare exit status the operator got ✅ pass live Same task, same real thurbox-cli, only the library differs: base commit prints "returned non-zero exit status 1", this branch prints "Name contains invalid characters" — evidence file 05-dispatch-spaw…
A task whose spawn failed is left queued with no session, so fixing the title and re-running still sends it ✅ pass live queue.sh show after the failed dispatch reports state: queued, session: None, dispatch: no session dispatched; after a hand-retitle the re-run spawned session c0bc6d37 — evidence files 05 and 10
Adversarial titles carrying a backslash or '..' are refused at add, and the real thurbox refuses the same rendered names ✅ pass live queue.sh add --title 'Windows C:\paths and the model' and --title 'Wait.. then retry' both refused naming the character; both rendered names refused by real thurbox-cli with "Name contains invalid…
A spawn failure where neither stream says anything still reports something useful rather than a blank message ✅ pass live PATH=<no thurbox-cli> python3 scripts/lib/queue.py dispatch ... prints "spawn failed: [Errno 2] No such file or directory: 'thurbox-cli'" — evidence file 07-spawn-failure-fallback-no-thurbox.txt
In a wave where one task cannot be spawned, the rest of the set still goes out and a re-run does not double-spawn the one that already went ✅ pass live Live queue.sh dispatch wave/10-goes-out wave/11-boom against a real git repo and real thurbox: 11-boom reported thurbox's refusal and stayed queued, 10-goes-out got session f1c72608 and answered its…
A spawn failure that thurbox writes to stderr rather than stdout is still reported in full ⏸️ untested no Not driven against the live product: the real thurbox-cli prints its structured failure on stdout, so no real invocation produces a stderr-only spawn failure. The prior payload established this path o…
A lead reading the CLI's own usage learns the title rule before hitting it ✅ pass live ./scripts/queue.sh --help renders the add entry documenting that the title becomes the session name, wearing the worker's mark and cut to the byte cap, carrying no '/', '', '..' or leading '.' —…
Evidence: add refuses the operator's real title, names the offending character, creates nothing

queue: --title 'Rust crate, CI/CD and the profile model' cannot become a session name: thurbox is asked to create '🚀 Rust crate, CI/CD and the profile model', and it contains '/', which thurbox refuses. ... exit=2 $ ls $FLEET_QUEUE_DIR/title-guard/ PROMPT.md topic.yaml

$ ./scripts/queue.sh add title-guard ci-cd --title "Rust crate, CI/CD and the profile model" --repo /tmp/repo-a --branch feat/ci-cd --number 01
queue: --title 'Rust crate, CI/CD and the profile model' cannot become a session name:
thurbox is asked to create '🚀 Rust crate, CI/CD and the profile model', and it contains '/', which thurbox refuses.
That name becomes a path there, so it carries no '/', no '\', no '..' and no
leading '.'. The spawn fails with thurbox's own refusal and the task stays
queued.
Retitle the task; nothing else about it has to change.
exit=2

$ ls $FLEET_QUEUE_DIR/title-guard/
PROMPT.md
topic.yaml
Evidence: the real thurbox-cli refuses that name and prints its reason on STDOUT

exit=1 --- stdout --- {"error":"Name contains invalid characters","suggestion":"the command ran and failed; ..."} --- stderr ---

$ thurbox-cli session create --json --name "🚀 Rust crate, CI/CD and the profile model" --repo-path /tmp/tbxprobe   # (stdout and stderr split)
exit=1
--- stdout ---
{"error":"Name contains invalid characters","suggestion":"the command ran and failed; the message says what went wrong — `thurbox-cli` prints the state it was working against"}
--- stderr ---
Evidence: the queue's rule vs the real thurbox on 10 titles — 0 disagreements

queue.sh add real thurbox agree rendered session name REFUSE REFUSE yes '🚀 Rust crate, CI/CD and the profile model' ACCEPT ACCEPT yes '🚀 Rust crate, CI-CD and the profile model' ACCEPT ACCEPT yes '🚀 .hidden' REFUSE REFUSE yes '🚀 foo/bar' REFUSE REFUSE yes '🚀 foo..bar' REFUSE REFUSE yes '🚀 foo\bar' ACCEPT ACCEPT yes '🚀 Ship v2.1: metrics & alerts @ 99% — done?' ACCEPT ACCEPT yes '🚀 Réécrire le lecteur ~ étape 1' ACCEPT ACCEPT yes '🚀 a.b.c and #42 + [brackets] {braces} <angles>' ACCEPT ACCEPT yes '🚀 Codify the out-of-band identity and patch settings on the b' 10 titles, disagreements: 0

queue.sh add  real thurbox  agree  rendered session name
----------------------------------------------------------------------------------------------------
REFUSE        REFUSE        yes    '🚀 Rust crate, CI/CD and the profile model'   <- thurbox: Name contains invalid characters
ACCEPT        ACCEPT        yes    '🚀 Rust crate, CI-CD and the profile model'
ACCEPT        ACCEPT        yes    '🚀 .hidden'
REFUSE        REFUSE        yes    '🚀 foo/bar'   <- thurbox: Name contains invalid characters
REFUSE        REFUSE        yes    '🚀 foo..bar'   <- thurbox: Name contains invalid characters
REFUSE        REFUSE        yes    '🚀 foo\\bar'   <- thurbox: Name contains invalid characters
ACCEPT        ACCEPT        yes    '🚀 Ship v2.1: metrics & alerts @ 99% — done?'
ACCEPT        ACCEPT        yes    '🚀 Réécrire le lecteur ~ étape 1'
ACCEPT        ACCEPT        yes    '🚀 a.b.c and #42 + [brackets] {braces} <angles>'
ACCEPT        ACCEPT        yes    '🚀 Codify the out-of-band identity and patch settings on the b'
----------------------------------------------------------------------------------------------------
10 titles, disagreements: 0
Evidence: the rendered name is judged, not the raw title — mark on vs mark off, confirmed against real thurbox

marks ON -> title-guard/03-dot-mark (accepted) marks OFF -> queue: --title '.hidden agenda' cannot become a session name: ... a name beginning with '.' is not one it accepts. real thurbox: '🚀 .hidden agenda' ACCEPTED / '.hidden agenda' REFUSED: Name cannot start with '.'

# marks ON — the rendered name is "🚀 .hidden agenda", which does not start with a dot
$ queue.sh add title-guard dot-mark --title ".hidden agenda" ...
title-guard/03-dot-mark
exit=0

# marks OFF — the rendered name IS ".hidden agenda"
$ queue.sh add title-guard dot-nomark --title ".hidden agenda" ...
queue: --title '.hidden agenda' cannot become a session name:
thurbox is asked to create '.hidden agenda', and a name beginning with '.' is not one it accepts.
That name becomes a path there, so it carries no '/', no '\', no '..' and no
leading '.'. The spawn fails with thurbox's own refusal and the task stays
queued.
Retitle the task; nothing else about it has to change.
exit=2

# and the real thurbox on the same two names:
  🚀\ .hidden\ agenda   -> thurbox ACCEPTED (session deleted again)
  .hidden\ agenda      -> thurbox REFUSED: Name cannot start with '.'
Evidence: dispatch's spawn failure, before and after, on the same task and the same real thurbox

=== BEFORE (base e559816) === title-guard/05-ci-cd: spawn failed: Command '['thurbox-cli', 'session', 'create', '--name', '🚀 Rust crate, CI/CD and the profile model', ...]' returned non-zero exit status 1. === AFTER (this branch) === title-guard/05-ci-cd: spawn failed: Name contains invalid characters === the task is left queued either way === state: queued session: None

The operator hand-edited `title` back to what `add` used to accept, then ran dispatch.
Same task, same real thurbox-cli 2.19.9, same rendered session name — only queue.py differs.

=== BEFORE the fix (scripts/lib/queue.py at base commit e559816) ===
$ ./scripts/queue.sh dispatch title-guard/05-ci-cd
    title-guard/05-ci-cd: spawn failed: Command '['thurbox-cli', 'session', 'create', '--name', '🚀 Rust crate, CI/CD and the profile model', '--repo-path', '/tmp/tbxprobe', '--worktree-branch', 'feat/ci-cd', '--base-branch', 'main', '--on-existing', 'fail', '--agent', 'claude', '--json']' returned non-zero exit status 1.
dispatch: 1 named task(s), launched together — no concurrency cap.

=== AFTER the fix (this branch, commit 10397ac) ===
$ ./scripts/queue.sh dispatch title-guard/05-ci-cd
    title-guard/05-ci-cd: spawn failed: Name contains invalid characters
dispatch: 1 named task(s), launched together — no concurrency cap.

=== and the task is left queued either way, so a retitle + re-run still sends it ===
    state:       queued
    session:     None
    dispatch:    no session dispatched
Evidence: adversarial titles: backslash and '..', refused here and by real thurbox
--- backslash in a title ---
queue: --title 'Windows C:\\paths and the model' cannot become a session name:
thurbox is asked to create '🚀 Windows C:\\paths and the model', and it contains '\\', which thurbox refuses.
--- ".." in a title ---
queue: --title 'Wait.. then retry' cannot become a session name:
thurbox is asked to create '🚀 Wait.. then retry', and it contains '..', which thurbox refuses.
--- an empty title, with marks off, renders an empty session name ---
title-guard/08-empty
--- and nothing above was created ---
03-dot-mark
05-ci-cd
08-empty
PROMPT.md
topic.yaml
# the three rendered names `add` refused above, put to the real thurbox-cli:
  🚀 Windows C:\paths and the model              -> REFUSED: Name contains invalid characters
  🚀 Wait.. then retry                           -> REFUSED: Name contains invalid characters
Evidence: a spawn failure with nothing on either stream falls back to the exception, not a blank message

title-guard/05-ci-cd: spawn failed: [Errno 2] No such file or directory: 'thurbox-cli'

# thurbox-cli not on PATH at all: neither stream says anything, so the
# fallback is the exception itself rather than a blank message.
$ PATH=<no thurbox-cli> ./scripts/queue.sh dispatch title-guard/05-ci-cd
    title-guard/05-ci-cd: spawn failed: [Errno 2] No such file or directory: 'thurbox-cli'
dispatch: 1 named task(s), launched together — no concurrency cap.
          1 other ready task(s) stay queued, and nothing records that:
          no ref is the norm, and the next bare `dispatch` sends them.
Evidence: the CLI's own usage now documents the title rule
153:  scripts/queue.sh add <topic> <slug> --title T --repo P --branch B [--base main]
154-                       [--host H] [--profile default] [--touches a,b] [--brief-file F]
155-                       [--publish no-mistakes|pr|push] [--how 'run `/publish`']
156-                       # --brief-file fills whichever of the brief's four
157-                       # sections its own `## ` headings name; a body with no
158-                       # headings all goes into `What to do`. A file that
159-                       # leaves any section unwritten is refused HERE, naming
160-                       # them, and nothing is created — as is a --branch no
161-                       # worktree could be cut for, which includes --base,
162-                       # and a --title thurbox could not make a session name
163-                       # of: that name is the title wearing the worker's mark
164-                       # and cut to thurbox's byte cap, and it carries no
165-                       # `/`, no `\`, no `..` and no leading `.`
166-  scripts/queue.sh block <ref> --on <ref> --kind KIND --why 'reason'   # or --clear,
167-                       which names the blocker to remove, since a task can
Evidence: queue-selftest.sh section 20 — all 23 assertions
  ok    a title that cannot become a session name is refused at add
  ok    and the refusal names the offending character
  ok    and says it is the SESSION NAME that cannot carry it
  ok    and quotes the name thurbox would have been asked to create
  ok    and creates nothing, so the repair is one re-run and not an edit
  ok    a title only made over-long by the glyph and the cut is still accepted
  ok    and it really was the rendering that made it long: 66 bytes, cut to 64
  ok    a title starting '.' is accepted while a mark goes in front of it
  ok    and refused with the mark off, where the name really does start '.'
  ok    naming the rule it broke and not merely the character
  ok    every name thurbox's own unsafe_names_are_rejected lists is refused
  ok    and nothing thurbox accepts is refused alongside them
  ok    a title is human-facing text, so no character is narrowed beyond that
  ok    a failing spawn reports what thurbox said
  ok    and not only the exit status the code used to echo back
  ok    the task that could be spawned still went out
  ok    and it really got its session
  ok    the failed one is left queued, so fixing it and re-running sends it
  ok    a refusal thurbox printed on stdout is read too
  ok    and its JSON wrapping is unwrapped rather than echoed
  ok    a re-run spawns the one that failed
  ok    and does not spawn the one already sent
  ok    whose session is still the first one

queue-selftest: every claim holds
Evidence: a live wave with one unspawnable task: the rest still goes out, no double-spawn, retitle + re-run sends it

wave/11-boom: spawn failed: Name contains invalid characters dispatch: 2 named task(s), launched together — no concurrency cap. wave/10-goes-out -> f1c72608-9671-4554-87a8-ba6e577e0819 [wave/10-goes-out] state: dispatched session: f1c72608-... [wave/11-boom] state: queued session: None -- re-run -- queue: these tasks were named and are not ready to go out: wave/10-goes-out: dispatched, and only a queued task is dispatched (and 10-goes-out still holds its FIRST session) -- retitled by hand, re-run -- wave/11-boom -> c0bc6d37-e775-454a-a6a1-016f8690ce61

$ ./scripts/queue.sh dispatch wave/10-goes-out wave/11-boom   # real thurbox-cli, real repo
    wave/11-boom: spawn failed: Name contains invalid characters
dispatch: 2 named task(s), launched together — no concurrency cap.
          2 other ready task(s) stay queued, and nothing records that:
          no ref is the norm, and the next bare `dispatch` sends them.
    wave/10-goes-out  -> f1c72608-9671-4554-87a8-ba6e577e0819
        session-trust: answered claude's trust dialog with 'down enter'; the dialog is gone

--- what the queue recorded ---
[wave/10-goes-out]
    state:       dispatched
    session:     f1c72608-9671-4554-87a8-ba6e577e0819
[wave/11-boom]
    state:       queued
    session:     None
    dispatch:    no session dispatched

--- a re-run: the failed one is tried again, the one already sent is not ---
$ ./scripts/queue.sh dispatch wave/10-goes-out wave/11-boom
queue: these tasks were named and are not ready to go out:
    wave/10-goes-out: dispatched, and only a queued task is dispatched
Clear what is holding them, or leave them out of the dispatch.

--- and 10-goes-out still holds its FIRST session ---
    state:       dispatched
    session:     f1c72608-9671-4554-87a8-ba6e577e0819

--- retitling the failed task by hand (the repair `add` now prevents) and re-running sends it ---
$ ./scripts/queue.sh dispatch wave/11-boom
dispatch: 1 named task(s), launched together — no concurrency cap.
          2 other ready task(s) stay queued, and nothing records that:
          no ref is the norm, and the next bare `dispatch` sends them.
    wave/11-boom  -> c0bc6d37-e775-454a-a6a1-016f8690ce61
        session-trust: no dialog: claude is already reporting; nothing sent
    state:       dispatched
    session:     c0bc6d37-e775-454a-a6a1-016f8690ce61

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ scripts/lib/queue.py:1464 - Acknowledged tradeoff, no action expected. The refusal is evaluated with worker_glyph() read at add time, while spawn_commands re-reads it at dispatch time, so flipping GLYPHS between the two re-opens the exact defect for a leading-'.' title: add '.env rewrite' with GLYPHS=on (rendered '\U0001F680 .env rewrite', accepted), then set GLYPHS=off in orchestration/session-glyphs.conf — a documented supported action for a terminal that shears on wide glyphs — and dispatch renders '.env rewrite', which thurbox refuses, leaving the hand-edit of title in task.yaml and the brief's H1 that half 1 exists to prevent. The selftest at scripts/queue-selftest.sh:5133-5153 encodes both directions of this, so it is a known property rather than an oversight, and the intent forbids the only remedy that would close it (checking the raw title as well would refuse names thurbox accepts: 'do not widen the rejected set beyond what thurbox rejects'). Half 2 of this change means the dispatch failure now says 'Name cannot start with .' instead of a bare exit status, which is the mitigation actually available.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 11 of 12 scenarios driven live against the product
Scenario Result Live Evidence
An operator adds a task titled 'Rust crate, CI/CD and the profile model' and is refused at add time, told which character is at fault, with no task directory created ✅ pass live ./scripts/queue.sh add title-guard ci-cd --title &#39;Rust crate, CI/CD and the profile model&#39; ... exits 2 naming '/' and quoting the rendered name; the topic directory still holds only PROMPT.md and to…
The real thurbox-cli confirms the premise: it refuses that same rendered session name and prints its reason as {"error": ...} on stdout with an empty stderr ✅ pass live thurbox-cli session create --json --name &#39;🚀 Rust crate, CI/CD and the profile model&#39; --repo-path /tmp/tbxprobe with streams split: exit 1, stdout carries the JSON error, stderr empty — evidence file…
The refusal rule is neither widened nor narrowed: for ten titles the queue's verdict matches what the real thurbox actually does with the rendered name ✅ pass live Cross-check driving session_name_refusal and real thurbox-cli session create over the same ten rendered names (each accepted one created then deleted): 10 agreements, 0 disagreements — evidence fi…
The check is on the rendered name, not the raw title: '.hidden agenda' is accepted while the worker mark precedes it and refused when marks are off, matching the real thurbox both ways ✅ pass live Two queue.sh add runs under FLEET_GLYPH_ROOT with GLYPHS=on then GLYPHS=off, and the same two names put to real thurbox-cli ('🚀 .hidden agenda' accepted, '.hidden agenda' refused with "Name cannot s…
A title whose offending '/' falls past the 64-byte cut is accepted, because the name thurbox is actually sent no longer contains it ✅ pass live Row 10 of the cross-check: title 'Codify the out-of-band identity and patch settings on the box/and/more' renders to '🚀 Codify the out-of-band identity and patch settings on the b', accepted by the qu…
When dispatch's spawn fails anyway, it reports what thurbox said instead of the bare exit status the operator got ✅ pass live Same task, same real thurbox-cli, only the library differs: base commit prints "returned non-zero exit status 1", this branch prints "Name contains invalid characters" — evidence file 05-dispatch-spaw…
A task whose spawn failed is left queued with no session, so fixing the title and re-running still sends it ✅ pass live queue.sh show after the failed dispatch reports state: queued, session: None, dispatch: no session dispatched; after a hand-retitle the re-run spawned session c0bc6d37 — evidence files 05 and 10
Adversarial titles carrying a backslash or '..' are refused at add, and the real thurbox refuses the same rendered names ✅ pass live queue.sh add --title &#39;Windows C:\paths and the model&#39; and --title &#39;Wait.. then retry&#39; both refused naming the character; both rendered names refused by real thurbox-cli with "Name contains invalid…
A spawn failure where neither stream says anything still reports something useful rather than a blank message ✅ pass live PATH=&lt;no thurbox-cli&gt; python3 scripts/lib/queue.py dispatch ... prints "spawn failed: [Errno 2] No such file or directory: 'thurbox-cli'" — evidence file 07-spawn-failure-fallback-no-thurbox.txt
In a wave where one task cannot be spawned, the rest of the set still goes out and a re-run does not double-spawn the one that already went ✅ pass live Live queue.sh dispatch wave/10-goes-out wave/11-boom against a real git repo and real thurbox: 11-boom reported thurbox's refusal and stayed queued, 10-goes-out got session f1c72608 and answered its…
A spawn failure that thurbox writes to stderr rather than stdout is still reported in full ⏸️ untested no Not driven against the live product: the real thurbox-cli prints its structured failure on stdout, so no real invocation produces a stderr-only spawn failure. The prior payload established this path o…
A lead reading the CLI's own usage learns the title rule before hitting it ✅ pass live ./scripts/queue.sh --help renders the add entry documenting that the title becomes the session name, wearing the worker's mark and cut to the byte cap, carrying no '/', '&#39;, '..' or leading '.' —…
  • ./scripts/check.sh
  • ./scripts/check.sh (baseline, already green before this phase)
  • ./scripts/queue.sh add &lt;topic&gt; ci-cd --title &#39;Rust crate, CI/CD and the profile model&#39; ... against an isolated FLEET_QUEUE_DIR
  • thurbox-cli session create --json --name &#39;🚀 Rust crate, CI/CD and the profile model&#39; --repo-path /tmp/tbxprobe (stdout/stderr split, real thurbox-cli 2.19.9)
  • cross-check script: 10 titles through queue.session_name_refusal and through real thurbox-cli session create, each accepted one deleted again
  • FLEET_GLYPH_ROOT=&lt;GLYPHS=on|off&gt; ./scripts/queue.sh add ... --title &#39;.hidden agenda&#39;, both renderings also put to real thurbox-cli
  • ./scripts/queue.sh add ... --title &#39;Windows C:\paths and the model&#39; and --title &#39;Wait.. then retry&#39;, both rendered names also put to real thurbox-cli
  • python3 /tmp/oldlib/queue.py dispatch &lt;ref&gt; (base commit e559816 library) vs ./scripts/queue.sh dispatch &lt;ref&gt; on the same task and same real thurbox-cli
  • ./scripts/queue.sh show &lt;ref&gt; after each failed dispatch (state/session/dispatch fields)
  • PATH=<no thurbox-cli> python3 scripts/lib/queue.py dispatch <ref>
  • ./scripts/queue.sh dispatch wave/10-goes-out wave/11-boom against a real git repo, then a re-run, then a hand-retitle and re-dispatch; both spawned sessions and their worktrees deleted
  • ./scripts/queue-selftest.sh (exit 0; section 20's 23 assertions all pass)
  • ./scripts/queue.sh --help (rendered usage for add)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…n failure was

A title becomes the worker's thurbox session name, and thurbox refuses a name
it could not make a path segment of. `add` took `Rust crate, CI/CD and the
profile model`; `dispatch` then died with nothing but thurbox's exit status,
and the repair was a hand-edit of `title` in task.yaml and the brief's H1.

`add` now asks thurbox's own rule — empty, over the byte cap, leading `.`,
or holding `/`, `\` or `..` — about the RENDERED name, since that is the
string `session create` is handed: the glyph goes in front and the title is
cut to the cap first. Nothing thurbox accepts is refused.

And `dispatch` reports what thurbox said. It read only stderr; thurbox-cli
prints its structured failure on STDOUT (`{"error": ...}` under `--json`),
so the reason was being discarded. Both streams are read and the `error`
field unwrapped. A failed spawn still leaves that task queued, still lets the
rest of the set go out, and still does not double-spawn on a re-run.
The review found that `FIXER_TITLES["conflicting"]` carries the base branch
into a session name, and a base branch holds `/` as a matter of course — the
same defect this branch fixes for an operator's `--title`, on the one path
where nobody can refuse it. It is a real finding and it is out of scope: this
task's brief forbids changing `shepherd`, and the operator asked for exactly
the two defects they hit.

So the fixer title, `spawn_fixer` and the selftest section about them go back
to what they were. The finding is reported to the lead in this task's
result.md instead, to be queued as its own task.

The skill note the same round added stays: it documents `add`'s new refusal,
which is this task's own work.
@LeTuR
LeTuR enabled auto-merge (squash) September 11, 2026 11:56
@LeTuR
LeTuR merged commit 01a8fee into main Sep 11, 2026
11 checks passed
@LeTuR
LeTuR deleted the fix/refuse-unspawnable-titles branch September 11, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant