Skip to content

task-work silently writes no TAB_ID when the tab lookup misses, so 'no tab id captured' surfaces hours later in task-done #242

Description

@martin-conur

Symptom

task-done prints:

Skipping zellij close-tab (no tab id captured; close this tab manually if needed).

and the tab does not close. Reported on both kiro and claude, intermittently.

Cause: the capture fails silently at a different time, in a different command

claude-gh/bin/task-work:295-298 (and the six sibling copies, region info-tab-id):

INFO_TAB_ID=$(aw_zellij_tab_id_by_name "$SLUG" || true)
if [[ -n "$INFO_TAB_ID" ]]; then
  printf 'TAB_ID=%s\n' "$INFO_TAB_ID" >> "$INFO_FILE"
fi

When the lookup returns empty, no TAB_ID= line is written and nothing is printed. The block is not guarded on $ZELLIJ either — it runs unconditionally and fails quietly.

So task-work succeeds, the worker runs for hours, and the first sign anything went wrong is task-done's message at teardown — by which time the context that caused it is gone. The user cannot act on the message because it reports a consequence in the wrong command.

This is the same failure shape the repo has now fixed three times: #194 (an empty check list reads as green), #182 (a filter that never executed and failed open), #229 (two distinct failures that both wrote no log line). A state that is invisible because its failure mode produces no artifact.

Evidence

Auditing every <worktree-base>/.<slug>.info on this machine — 9 of 36 sidecars across 8 repos have no TAB_ID, and they are not randomly distributed:

gdi-chatbot-worktrees:
  .add-readme.info                                                TAB_ID=[]
  .add-readme-7qegu.info  .add-readme-9wgw1.info                  TAB_ID=[]
  .add-readme-g8sgw.info  .add-readme-ldwpy.info                  TAB_ID=[]
  .add-readme-smy9q.info                                          TAB_ID=[]
  .bug-model-renders-table-but-the-chat-already-outpu.info        TAB_ID=[]
  .bug-model-renders-table-but-the-chat-already-outpu-hu1s5.info  TAB_ID=[]
  .fix-stores-skill.info                                          TAB_ID=[]

every other repo (retail-scraper, recommender-systems, groppo, portai, task-force): TAB_ID populated

Two shapes stand out, both produced by task-work itself:

  • Collision suffixes. task-work:206-207 appends a random 5-char hash on a name collision (SLUG="${SLUG}-${HASH}"). Six add-readme variants exist, so that path ran repeatedly.
  • 50-char truncation. task-work:136 does SLUG="${SLUG:0:50}". bug-model-renders-table-but-the-chat-already-outpu is exactly 50 characters.

Both are cases where the name task-work asks zellij for is one it derived through a transformation — which is where a mismatch between the slug and the actual tab name would live. Not proven; see below.

What the lookup can return empty for

lib/zellij-tab.sh:38-52, aw_zellij_tab_id_by_name, ends in | .[0] // empty, so empty means no tab matched the name. Four ways to get there:

  1. $ZELLIJ unset — task-work run outside zellij, so no tab was created at all. The block does not check for this.
  2. no jq on PATH — :42 returns empty.
  3. the tab's name is not byte-equal to $SLUG after paint-stripping. Note the paint set (⏸️ /▶️ /❓︎ ) matches radio's exactly, so the documented race the comment addresses is genuinely covered — this would be a different mismatch, e.g. a long name zellij renders differently.
  4. a genuine race despite (3).

I could not determine which of these fires from artifacts alone. The distribution above points at (3) and the two transformations that produce it, but the failing tabs are long gone and the message carries no diagnostic.

Fix

Two parts, and the first matters more than the second:

1. Make the failure loud, at the point it happens. When the lookup comes back empty, task-work should say so and say why it can tell — zellij absent, jq absent, or a name lookup that found nothing — and name the slug it searched for. Log it too, so the reconstruction is a grep rather than an audit of every sidecar on the machine. Whatever the trigger turns out to be, this is what makes the next report diagnosable in one line instead of this.

2. Then find the trigger, with (1) in place to report it. Worth checking specifically whether zellij's list-tabs --json .name is byte-equal to the requested name for a 50-char slug, and whether anything in the collision-suffix path creates the tab under a different name than it writes to the sidecar.

Do not fall back to the unscoped zellij action close-tab in task-done — #107/#108 established that closes whichever tab is focused, and task-done:201 already explains why that is worse than skipping.

Verification

  • With the lookup forced to return empty, task-work reports it (message + log line) and still completes — a failed tab-id capture must not abort a worker launch.
  • The message distinguishes the causes it can distinguish: no zellij, no jq, no match for <slug>.
  • A successful capture is byte-unchanged in output (regression guard).
  • task-done's existing "no tab id captured" message gains a pointer to the task-work log line, so the two ends of the failure are linkable.
  • All seven loadouts: this lives in the drift-guarded info-tab-id region, so the fix lands in every copy or the guard fails.

References

Activity

  1. added this to the v0.3.2 milestone on Oct 2, 2026
  2. martin-conur commented on Oct 2, 2026

    @martin-conur
    OwnerAuthor

    My Evidence section was wrong — correcting it (2026-10-02)

    PR #243's Part 2 investigation disproved the evidence I put in this issue's body, and I verified the correction myself before accepting it.

    All nine TAB_ID-less sidecars predate the feature. Their mtimes are May 12-14; #118 (c7bf459, "persist TAB_ID in $INFO_FILE") landed May 24. They have no TAB_ID because the capture did not exist when they were written — ten days earlier. They are pre-feature artifacts, not failures.

    May 12 13:53  .add-readme-ldwpy.info
    May 12 13:55  .add-readme-7qegu.info
    ...
    May 14 16:06  .bug-model-renders-table-but-the-chat-already-outpu-hu1s5.info
    2026-05-24    c7bf459  Fix #117: ... persist TAB_ID in $INFO_FILE (#118)
    

    Both shapes I pointed at are red herrings. The worker verified that 50-char truncated names (task-work:136) and collision-suffixed names (:206-207) match byte-for-byte against zellij action list-tabs --json on zellij 0.44.2. Neither transformation breaks the lookup. My reasoning — that a derived name is where a mismatch would live — was plausible and wrong, and the distribution that suggested it was an artifact of file age, not of slug shape.

    What this means for the ticket: Part 1 is the whole fix, and the reported symptom is still unexplained. The nine stale sidecars account for none of the live occurrences the reporter is hitting on kiro and claude today. So the cause remains unknown — and that is precisely why the fix is to make the miss loud and logged at the point it happens. The next occurrence will say which of the four causes fired instead of requiring an audit of every sidecar on the machine, which is what produced this wrong conclusion in the first place.

    Leaving the issue open against PR #243 rather than editing the body, so the wrong inference and its correction both stay readable. The lesson is the one #229 already taught: a distribution is not a cause, and an artifact's existence says nothing about when it was written unless you check.

  3. added 2 commits that reference this issue on Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions