tickmarkr 2.7.0 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -129,7 +129,7 @@ export function parseWorkerResult(raw, nonce) {
129
129
  return fail(cause === "malformed-verdict" ? UNPARSEABLE_TRAILER_SUMMARY : NO_TRAILER_SUMMARY, cause);
130
130
  }
131
131
  // Signatures anchor distinctive CLI-error phrasing — never bare fragments or bare status-code
132
- // numbers (the QUOTA_RE Pitfall-3 lesson): these run only over no-trailer output, but a stalled
132
+ // numbers (the quota classifier's Pitfall-3 lesson): these run only over no-trailer output, but a stalled
133
133
  // worker's harvested pane can still contain ordinary work text. E1: the recorded refresh failure
134
134
  // ("access token could not be refreshed") is auth-required — classified, never re-probed or relaunched.
135
135
  const AUTH_RE = /not logged in|please (?:log ?in|sign in)|please run [^\n]{0,30}log ?in|authentication[ _](?:required|failed|error)|invalid (?:api key|credentials)|api key (?:is )?(?:not set|missing|invalid|required)|credentials? (?:have )?expired|401 unauthorized|access token could not be refreshed/i;
@@ -11,7 +11,7 @@ import { FakeAdapter } from "./fake.js";
11
11
  import { parseWorkerResult } from "./prompt.js";
12
12
  import { sealHerdrEnv } from "../drivers/subprocess.js";
13
13
  import { catalogEntries, isNativeCliDrive, projectCliEntries, SHIPPED_CLI_CATALOG, } from "./catalog.js";
14
- import { channelKey, channelsFromConfig, modelAuthed, MODEL_ID_RE, MODEL_PROBE_ERRORS, QUOTA_RE, shq, } from "./types.js";
14
+ import { channelKey, channelsFromConfig, modelAuthed, MODEL_ID_RE, MODEL_PROBE_ERRORS, QUOTA_PHRASE_RE, shq, STATUS_LABEL, } from "./types.js";
15
15
  // Compatibility projection for callers/tests that only need shipped advisory names. The literal
16
16
  // list is gone: both advisory and routable catalog views derive from SHIPPED_CLI_CATALOG. Native
17
17
  // definitions (claudeCode, codex, cursorAgent, opencode, pi, grok, kimi) are owned by catalog.ts;
@@ -389,7 +389,23 @@ const MODEL_PROBE_TIMEOUT_MS = 60000;
389
389
  // probe of any adapter that prints a comma-grouped token count. Lookarounds reject a group that is part of a
390
390
  // larger number (26,485 · 1.400 · 400.5) while keeping real bare codes (401 · "error 429" · "403 Forbidden."
391
391
  // — trailing sentence punctuation still matches, only a following DIGIT means "decimal, not status code").
392
- const AUTH_FAILURE_RE = /\b(?<![\d.,])4\d\d\b(?![.,]\d)|\bauth(?:entication|orization)?\s+(?:error|failed|failure|denied)|unauthori[sz]ed|forbidden|access denied|credit(?:s)?\s+(?:exhausted|error|denied)/i;
392
+ // Queue row 20 (D-1148 (1)): a bare number is never a status code. qwen's healthy probe telemetry
393
+ // `"totalGenerationDurationMs":438` read as an auth failure and dropped a working channel, so a 4xx counts only
394
+ // where the output labels it as a status — `HTTP 401`, `HTTP/1.1 403`, `status 401`, `status code 401`,
395
+ // `"statusCode":401`, `"httpStatus":401`, `httpStatusCode: 403`, `errorCode: 401`, `code: 403`, `Error: 401`. A
396
+ // label matches only as a WHOLE token — no letter of any script, combining mark, digit or underscore before it:
397
+ // `unicode`, `barcode`, `standardError`, `zipCode`, `foo_code`, `éstatus` are not labels; punctuation such as `“` or
398
+ // `—` is no letter — so compound labels are enumerated (the `u` flag is what lets `\p{…}` name every script). The
399
+ // label only NARROWS the base's bare-4xx match (both `\b`s and the no-decimal/grouping tail stay), so nothing the
400
+ // base read as authed fails here; the base's digit-before guard is implied by the `\b` after a label. The word
401
+ // branches are unchanged.
402
+ // The labelled 4xx needs the `u` flag (\p{…} in STATUS_LABEL). The auth-word branches keep the base's bytes and its
403
+ // "i" flag: under "iu" case folding maps ſ (U+017F) → s and K (U+212A) → k, so `unauthoriſed` would read as a failure
404
+ // the base never read (D-1582).
405
+ const LABELLED_4XX_RE = new RegExp(String.raw `${STATUS_LABEL}\b4\d\d\b(?![.,]\d)`, "iu");
406
+ const AUTH_WORDS_RE = /\bauth(?:entication|orization)?\s+(?:error|failed|failure|denied)|unauthori[sz]ed|forbidden|access denied|credit(?:s)?\s+(?:exhausted|error|denied)/i;
407
+ // The probe's quota reading is the shared QUOTA_PHRASE_RE (types.ts, queue row 106): a labelled 429 is a labelled 4xx
408
+ // above, and an unlabelled `429 Too Many Requests` keeps failing on its phrase.
393
409
  const PROBE_REASON_CAP = 240;
394
410
  // v1.55 T3: the tail must open at a word boundary — a mid-word slice ("odel 'grok-…'") reads as
395
411
  // corruption in operator-facing diagnostics. Input is already space-normalized, so " " is the only
@@ -421,7 +437,7 @@ function probeFailure(code, stdout, stderr, timedOut, timeoutMs = MODEL_PROBE_TI
421
437
  const output = `${stderr}\n${stdout}`.trim().replace(/\s+/g, " ");
422
438
  // OBS-72: TAIL, not head — the error lands at the END of CLI output; a head slice stores only the
423
439
  // startup banner and hid the real "Not inside a trusted directory" failure for a day.
424
- return code !== 0 || QUOTA_RE.test(output) || AUTH_FAILURE_RE.test(output)
440
+ return code !== 0 || QUOTA_PHRASE_RE.test(output) || LABELLED_4XX_RE.test(output) || AUTH_WORDS_RE.test(output)
425
441
  ? reasonTail(output) || `probe exited ${code}`
426
442
  : undefined;
427
443
  }
@@ -237,5 +237,16 @@ export declare const PROMPT_ARGV_CEILING_DEFAULT = 900000;
237
237
  export declare function promptArgvCeiling(platform?: string): number;
238
238
  export declare function promptFitsArgv(promptFile: string, platform?: string): boolean;
239
239
  export declare const CAPACITY_RE: RegExp;
240
- export declare const QUOTA_RE: RegExp;
240
+ export declare const QUOTA_PHRASE_RE: RegExp;
241
+ export declare const STATUS_LABEL: string;
242
+ export declare const LABELLED_429_RE: RegExp;
243
+ export type QuotaSignal = RegExpExecArray & {
244
+ readonly source: string;
245
+ };
246
+ /**
247
+ * The LEFTMOST quota signal in `text` — a phrase or a labelled 429 — with the source of the regex that matched (the
248
+ * quota-banner record names it, so a false positive stays legible from the record alone), or null. A phrase and a
249
+ * label never start at the same index (phrase initials r q u o i t, label initials h e s c); a tie would go to the phrase.
250
+ */
251
+ export declare function quotaSignal(text: string): QuotaSignal | null;
241
252
  export declare const MODEL_ID_RE: RegExp;
@@ -302,7 +302,7 @@ export function promptFitsArgv(promptFile, platform = process.platform) {
302
302
  // on ordinary billing/wallet task output the harness edits (research Pitfall 3, 2026-07-10).
303
303
  // OBS-1161: transient provider capacity ("Selected model is at capacity") is NOT quota — the seat
304
304
  // comes back within minutes, so it takes bounded same-seat backoff before a same-floor failover and
305
- // never permanent demotion. Kept apart from QUOTA_RE so permanent quota policy stays untouched, and
305
+ // never permanent demotion. Kept apart from quotaSignal so permanent quota policy stays untouched, and
306
306
  // anchored on the full phrase (the Pitfall-3 lesson: a bare "capacity" fires on ordinary work text).
307
307
  // A banner is the CLI speaking, so the phrase must OPEN its row — after at most an error glyph
308
308
  // (codex paints "■ Selected model is at capacity. …") or an "error:" label. Anything else in front —
@@ -310,7 +310,25 @@ export function promptFitsArgv(promptFile, platform = process.platform) {
310
310
  // ponytail: row-start grammar, not per-adapter fixtures — the ceiling is a worker whose own output
311
311
  // opens a row with the bare phrase inside the banner tail; widen the glyph set only from a captured frame.
312
312
  export const CAPACITY_RE = /^[ \t■⚠✗✘×!]*(?:(?:stream )?error:[ \t]*)?(?:(?:selected |the )?model is (?:currently |temporarily )?at capacity|(?:provider|service|server) is (?:currently )?at capacity)/im;
313
- export const QUOTA_RE = /rate.?limit|quota|usage limit|out of credits|insufficient credit|insufficient balance or no resource|\b429\b/i;
313
+ // Queue row 106 (D-1597): a bare 429 is never a quota signal. Every bare 429 in the captured corpus was a line number
314
+ // or a count — `429:` in a diff listing failed a pinned codex worker over 41 985 bytes before EOF (OBS-926) — and the
315
+ // one real 429 banner also carries a phrase and a labelled status. Quota is a PHRASE, or a 429 the output LABELS.
316
+ export const QUOTA_PHRASE_RE = /rate.?limit|quota|usage limit|out of credits|insufficient credit|insufficient balance or no resource|too many requests/i;
317
+ // A whole-token status label (row 20, D-1581): no letter of any script, combining mark, digit or underscore before it;
318
+ // compound labels enumerated. Its \p{} classes need the "u" flag, kept on the regexes that use it — under "iu" case
319
+ // folding maps ſ → s and K → k, so it never reaches the phrases above (D-1582). Shared with the model probe.
320
+ export const STATUS_LABEL = String.raw `(?<![\p{L}\p{M}\p{N}_])(?:http[_-]?status(?:[_-]?code)?|error[_-]?code|HTTP(?:\/\d(?:\.\d)?)?|status(?:[\s_-]*code)?|code|error)["']?\s*[:=]?\s*["']?`;
321
+ export const LABELLED_429_RE = new RegExp(String.raw `${STATUS_LABEL}\b429\b(?![.,]\d)`, "iu");
322
+ /**
323
+ * The LEFTMOST quota signal in `text` — a phrase or a labelled 429 — with the source of the regex that matched (the
324
+ * quota-banner record names it, so a false positive stays legible from the record alone), or null. A phrase and a
325
+ * label never start at the same index (phrase initials r q u o i t, label initials h e s c); a tie would go to the phrase.
326
+ */
327
+ export function quotaSignal(text) {
328
+ const phrase = QUOTA_PHRASE_RE.exec(text), label = LABELLED_429_RE.exec(text);
329
+ const [match, re] = phrase && (!label || phrase.index <= label.index) ? [phrase, QUOTA_PHRASE_RE] : label ? [label, LABELLED_429_RE] : [null, null];
330
+ return match && re ? Object.assign(match, { source: re.source }) : null;
331
+ }
314
332
  // v1.5 MODEL-01: charset gate for detected model ids (research Pitfall 4, verified 2026-07-10).
315
333
  // Ids come from CLI stdout / another program's JSON (models_cache.json) and are echoed into
316
334
  // operator-facing lint text and persisted to doctor.json — defense-in-depth for MODEL-05 (config
@@ -307,14 +307,15 @@ export function compileGsd(src, root) {
307
307
  // snapshot; classifyContextPath remains the single authority for missing, untracked, and glob refs.
308
308
  const gitDir = dirname(files[0]);
309
309
  const top = spawnSync("git", ["-C", gitDir, "rev-parse", "--show-toplevel"], { encoding: "utf8", maxBuffer: 1 << 28 });
310
- const tree = spawnSync("git", ["-C", gitDir, "ls-tree", "--full-tree", "-r", "--name-only", "HEAD"], { encoding: "utf8", maxBuffer: 1 << 28 });
310
+ // -z (queue row 71): without it a non-ASCII path is C-quoted and a tracked ref reads as untracked
311
+ const tree = spawnSync("git", ["-C", gitDir, "ls-tree", "--full-tree", "-r", "--name-only", "-z", "HEAD"], { encoding: "utf8", maxBuffer: 1 << 28 });
311
312
  const repoRoot = typeof top.stdout === "string" ? top.stdout.trim() : "";
312
313
  // `root` is the repository whose worktrees will consume these refs. A source nested inside some
313
314
  // unrelated repository (as with vendored fixtures) must not be judged against that outer tree.
314
315
  const authoritative = root === undefined
315
316
  || (top.status === 0 && repoRoot !== "" && realpathSync(root) === realpathSync(repoRoot));
316
317
  if (top.status === 0 && tree.status === 0 && repoRoot && typeof tree.stdout === "string" && authoritative) {
317
- const tracked = new Set(tree.stdout.split("\n").filter(Boolean));
318
+ const tracked = new Set(tree.stdout.split("\0").filter(Boolean));
318
319
  const unreachable = [];
319
320
  for (const [i, t] of tasks.entries()) {
320
321
  for (const entry of compiled[i].refs) {
@@ -23,11 +23,12 @@ function trackedAtHead(dir) {
23
23
  // --full-tree is load-bearing: without it `ls-tree` scopes to the CWD's subtree and prints paths
24
24
  // relative to it, so a spec in specs/ would see only specs/-relative names and misjudge every
25
25
  // path outside it. Caught only by a fixture whose spec is NOT at the repo root.
26
- const r = git("ls-tree", "--full-tree", "-r", "--name-only", "HEAD");
26
+ // -z (queue row 71): without it a non-ASCII path is C-quoted and a tracked file reads as untracked
27
+ const r = git("ls-tree", "--full-tree", "-r", "--name-only", "-z", "HEAD");
27
28
  // not a repo, or no commits yet — fail open, so non-repo fixtures and fresh inits stay silent
28
29
  if (top.status !== 0 || r.status !== 0 || typeof r.stdout !== "string")
29
30
  return undefined;
30
- return { root: top.stdout.trim(), tracked: new Set(r.stdout.split("\n").filter(Boolean)) };
31
+ return { root: top.stdout.trim(), tracked: new Set(r.stdout.split("\0").filter(Boolean)) };
31
32
  }
32
33
  const GLOB_CHARS = /[*?{[]/;
33
34
  /**
@@ -1,5 +1,5 @@
1
1
  import { type JournalEvent } from "../run/journal.js";
2
- import { type ExecutorDriver, type FocusTarget, type FocusResult, type NotifyOpts, type PanesToCloseOpts, type Slot, type SlotOpts } from "./types.js";
2
+ import { type ExecutorDriver, type FocusTarget, type TransportState, type FocusResult, type NotifyOpts, type PanesToCloseOpts, type Slot, type SlotOpts } from "./types.js";
3
3
  export declare const TRAILER_SAFE_FLOOR_COLS = 108;
4
4
  export declare const TRAILER_WIDTH_MARGIN = 2;
5
5
  export declare const DELIVERY_ATTEMPTS = 3;
@@ -134,6 +134,7 @@ export declare class HerdrDriver implements ExecutorDriver {
134
134
  waitAgentStatus(slot: Slot, status: string, timeoutMs: number): Promise<boolean>;
135
135
  status(slot: Slot): Promise<string>;
136
136
  private statusByName;
137
+ transportState(_slot: Slot): Promise<TransportState>;
137
138
  read(slot: Slot, lines: number): Promise<string>;
138
139
  sendKey(slot: Slot, key: string): Promise<void>;
139
140
  notify(msg: string, opts?: NotifyOpts): Promise<void>;
@@ -1142,6 +1142,11 @@ export class HerdrDriver {
1142
1142
  return "unknown";
1143
1143
  }
1144
1144
  }
1145
+ // Queue row 108: a pane's transport closes when the pane is gone, which the dead-worker park already requires
1146
+ // (an empty read), so the answer is closed — parity with the park before row 108, never a new hold.
1147
+ async transportState(_slot) {
1148
+ return { closed: true };
1149
+ }
1145
1150
  async read(slot, lines) {
1146
1151
  const pane = await this.paneId(slot);
1147
1152
  const r = await this.herdr(`pane read ${shq(pane)} --source recent-unwrapped --lines ${lines}`, slot.cwd);
@@ -1,7 +1,7 @@
1
1
  import { type ShResult } from "../run/git.js";
2
2
  import { type JournalEvent } from "../run/journal.js";
3
3
  import { type WatchBoardOwner } from "../run/supervision.js";
4
- import { type ExecutorDriver, type FocusTarget, type FocusResult, type NotifyOpts, type Slot, type SlotOpts } from "./types.js";
4
+ import { type ExecutorDriver, type FocusTarget, type TransportState, type FocusResult, type NotifyOpts, type Slot, type SlotOpts } from "./types.js";
5
5
  /** The response families the ONE shared envelope parser serves. There is no second JSON seam. */
6
6
  export declare const ORCA_RESPONSE_FAMILIES: readonly ["status", "create", "list", "read", "send", "wait", "show", "close", "worktree-current", "worktree-set", "hooks-status", "split"];
7
7
  export type OrcaFamily = (typeof ORCA_RESPONSE_FAMILIES)[number];
@@ -325,6 +325,7 @@ export declare class OrcaDriver implements ExecutorDriver {
325
325
  readScreen(slot: Slot): Promise<string>;
326
326
  /** A single UNPAGED tail read — exactly what the caller asked for and nothing more. Markers split
327
327
  * across cursor pages are not reassembled here; that is waitOutput's job. */
328
+ transportState(_slot: Slot): Promise<TransportState>;
328
329
  read(slot: Slot, lines: number): Promise<string>;
329
330
  /**
330
331
  * Bounded cursor-paged sweep into the slot's accumulated buffer. The first read of a slot carries
@@ -1545,6 +1545,11 @@ export class OrcaDriver {
1545
1545
  }
1546
1546
  /** A single UNPAGED tail read — exactly what the caller asked for and nothing more. Markers split
1547
1547
  * across cursor pages are not reassembled here; that is waitOutput's job. */
1548
+ // Queue row 108: a pane's transport closes when the pane is gone, which the dead-worker park already requires
1549
+ // (an empty read), so the answer is closed — parity with the park before row 108, never a new hold.
1550
+ async transportState(_slot) {
1551
+ return { closed: true };
1552
+ }
1548
1553
  async read(slot, lines) {
1549
1554
  if (!Number.isInteger(lines) || lines <= 0)
1550
1555
  throw new OrcaError("read", `invalid line limit ${lines}`, "");
@@ -1,4 +1,4 @@
1
- import type { ExecutorDriver, NotifyOpts, Slot } from "./types.js";
1
+ import type { ExecutorDriver, NotifyOpts, Slot, TransportState } from "./types.js";
2
2
  export declare const MAX_BUF: number;
3
3
  export declare const HERDR_CONTROL_VARS: readonly ["HERDR_ENV", "HERDR_SOCKET_PATH", "ORCA_TERMINAL_HANDLE", "ORCA_PANE_KEY", "ORCA_TAB_ID"];
4
4
  /**
@@ -22,6 +22,7 @@ export declare class SubprocessDriver implements ExecutorDriver {
22
22
  }): Promise<boolean>;
23
23
  waitAgentStatus(slot: Slot, status: string, timeoutMs: number): Promise<boolean>;
24
24
  status(_slot: Slot): Promise<string>;
25
+ transportState(slot: Slot): Promise<TransportState>;
25
26
  read(slot: Slot, lines: number): Promise<string>;
26
27
  focus(): Promise<import("./types.js").FocusResult>;
27
28
  notify(msg: string, _opts?: NotifyOpts): Promise<void>;
@@ -75,6 +75,16 @@ export class SubprocessDriver {
75
75
  p.stderr.on("data", (d) => (s.buf = (s.buf + d).slice(-MAX_BUF)));
76
76
  p.on("close", () => (s.exited = true));
77
77
  p.on("error", (e) => { s.buf = (s.buf + `\n[tickmarkr subprocess error] ${e}\n`).slice(-MAX_BUF); s.exited = true; });
78
+ // Queue row 108 (D-1622): THIS run's transport state. The slot's `exited` outlives an invocation (a reused slot) and
79
+ // an 'error' sets it before stdio closes, so it is not the fact. This object is fresh per run and written only by
80
+ // THIS process's listeners: a previous worker's late close lands on its own state, never on the current one. Only
81
+ // stdio 'close' closes it — or a spawn that never started (no pid), which has nothing to drain.
82
+ const invocation = { closed: false, exitCode: null, signal: null };
83
+ s.invocation = invocation;
84
+ p.on("exit", (code, signal) => { invocation.exitCode = code; invocation.signal = signal; });
85
+ p.on("close", () => { invocation.closed = true; });
86
+ p.on("error", () => { if (p.pid === undefined)
87
+ invocation.closed = true; });
78
88
  }
79
89
  async waitOutput(slot, pattern, timeoutMs, opts) {
80
90
  const s = this.state(slot);
@@ -103,6 +113,11 @@ export class SubprocessDriver {
103
113
  async status(_slot) {
104
114
  return "unknown"; // no screen to scrape; never reports blocked
105
115
  }
116
+ async transportState(slot) {
117
+ const s = this.state(slot);
118
+ const run = s.invocation;
119
+ return { closed: run?.closed ?? false, bytes: Buffer.byteLength(s.buf), exitCode: run?.exitCode ?? null, signal: run?.signal ?? null };
120
+ }
106
121
  async read(slot, lines) {
107
122
  return this.state(slot).buf.split("\n").slice(-lines).join("\n");
108
123
  }
@@ -83,6 +83,13 @@ export interface SlotPlacement {
83
83
  surface?: string;
84
84
  hostPlatform?: string;
85
85
  }
86
+ /** Queue row 108: a worker transport's own account of its output stream (ExecutorDriver.transportState). */
87
+ export interface TransportState {
88
+ closed: boolean;
89
+ bytes?: number;
90
+ exitCode?: number | null;
91
+ signal?: string | null;
92
+ }
86
93
  export interface ExecutorDriver {
87
94
  id: string;
88
95
  interactive: boolean;
@@ -101,6 +108,7 @@ export interface ExecutorDriver {
101
108
  waitAgentStatus(slot: Slot, status: string, timeoutMs: number): Promise<boolean>;
102
109
  status(slot: Slot): Promise<string>;
103
110
  read(slot: Slot, lines: number): Promise<string>;
111
+ transportState?(slot: Slot): Promise<TransportState>;
104
112
  sendKey?(slot: Slot, key: string): Promise<void>;
105
113
  nudge?(slot: Slot, message: string): Promise<boolean>;
106
114
  notify(msg: string, opts?: NotifyOpts): Promise<void>;
@@ -55,6 +55,7 @@ export type AcceptanceCorpusAuditResult = {
55
55
  };
56
56
  };
57
57
  export declare function auditAcceptanceCorpus(corpusRoot: string, listedTests: readonly VitestListedTest[]): AcceptanceCorpusAuditResult[];
58
+ export declare function changedLinesByFile(diff: string): Map<string, Set<number>>;
58
59
  /** OBS-1151: one criterion of a parsed judge verdict, as the contradiction check compares it. */
59
60
  export interface JudgedCriterion {
60
61
  id: string;
@@ -10,6 +10,7 @@ import { checkTaskDiffCaps, fetchTaskDiff, isProtectedEvidence, setAsideReceiptP
10
10
  import { appendAnchoredReview, COMPLETION_FAKING_CHECKLIST, extractVerdictJson, generateVerdictNonce, runLlm, SeatLaunchError, verdictNonceLine } from "./llm.js";
11
11
  import { classifyVerdictCause } from "./verdict-cause.js";
12
12
  import { reviewableLogicDiff } from "./artifact-manifest.js";
13
+ import { diffSidePath } from "./git-paths.js";
13
14
  import { classifyFailureOutput } from "./baseline.js";
14
15
  // Fable F4: acceptance judge shares review's 900s timeout — 300s default killed frontier judges on cap-sized diffs.
15
16
  const JUDGE_TIMEOUT_MS = 900_000;
@@ -248,8 +249,9 @@ function oracleExecutionFailure(label, code, stdout, stderr) {
248
249
  // holds: a citation outside every hunk, or to an untouched file, still fails (matching the documented
249
250
  // "outside every changed hunk → rejected" contract in acceptance.test.ts).
250
251
  // ponytail: assumes git's default a/ b/ prefixes (fetchTaskDiff uses plain `git diff`); revisit if a
251
- // caller passes a --no-prefix diff.
252
- function changedLinesByFile(diff) {
252
+ // caller passes a --no-prefix diff. Queue row 71b: a +++ side is decoded by the shared git-paths reader (git's
253
+ // C-quoting and the tab that ends a space-holding name), so a non-ASCII file's lines are citable by its real name.
254
+ export function changedLinesByFile(diff) {
253
255
  const byFile = new Map();
254
256
  let path = null;
255
257
  let newLine = 0;
@@ -263,8 +265,7 @@ function changedLinesByFile(diff) {
263
265
  if (!inHunk && raw.startsWith("--- "))
264
266
  continue;
265
267
  if (!inHunk && raw.startsWith("+++ ")) {
266
- const p = raw.slice(4).trim();
267
- path = p === "/dev/null" ? null : p.replace(/^[ab]\//, "");
268
+ path = diffSidePath(raw.slice(4));
268
269
  continue;
269
270
  }
270
271
  const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(raw);
@@ -3,6 +3,7 @@
3
3
  * path receives capture accounting only when its manifest row names a
4
4
  * registered producer and repeats that producer's current provenance exactly.
5
5
  */
6
+ import { diffSidePath, gitHeaderPaths, unquoteGitPath } from "./git-paths.js";
6
7
  const GOLDEN_PROVENANCE = {
7
8
  source: "src/tui/cockpit/capture.ts",
8
9
  entrypoint: "regenerateGoldenFrames",
@@ -201,9 +202,7 @@ function deletedPath(section) {
201
202
  return null;
202
203
  const oldPath = /^--- (.+)$/m.exec(section)?.[1]
203
204
  ?? /^Binary files (.+) and \/dev\/null differ$/m.exec(section)?.[1];
204
- if (!oldPath || oldPath === "/dev/null")
205
- return null;
206
- return unquoteGitPath(oldPath).replace(/^a\//, "");
205
+ return oldPath ? diffSidePath(oldPath) : null;
207
206
  }
208
207
  /**
209
208
  * Whole-file source deletions retain their operation fact instead of spending
@@ -220,26 +219,6 @@ export function reviewableLogicDiff(diff) {
220
219
  return `deleted file: ${path}\n`;
221
220
  }).join("");
222
221
  }
223
- function unquoteGitPath(raw) {
224
- const value = raw.trim();
225
- if (!value.startsWith('"') || !value.endsWith('"'))
226
- return value;
227
- try {
228
- return JSON.parse(value);
229
- }
230
- catch {
231
- return value.slice(1, -1);
232
- }
233
- }
234
- function diffSidePath(raw) {
235
- const value = unquoteGitPath(raw);
236
- if (value === "/dev/null")
237
- return null;
238
- return value.replace(/^[ab]\//, "");
239
- }
240
- function gitHeaderTokens(raw) {
241
- return raw.match(/"(?:\\.|[^"\\])*"|\S+/g) ?? [];
242
- }
243
222
  function parseContentSection(section) {
244
223
  const lines = section.split("\n");
245
224
  const minus = lines.findIndex((line) => line.startsWith("--- "));
@@ -267,12 +246,7 @@ function sectionPaths(section, parsed) {
267
246
  return [...new Set([renamedFrom, renamedTo].filter((path) => path !== undefined).map(unquoteGitPath))];
268
247
  }
269
248
  const header = /^diff --git (.+)$/m.exec(section)?.[1];
270
- if (!header)
271
- return [];
272
- const tokens = gitHeaderTokens(header);
273
- if (tokens.length !== 2)
274
- return [];
275
- return [...new Set(tokens.map(diffSidePath).filter((path) => path !== null))];
249
+ return header ? gitHeaderPaths(header) : [];
276
250
  }
277
251
  function hunkPayload(body, sign) {
278
252
  return body
@@ -0,0 +1,18 @@
1
+ /** A path exactly as git printed it, quoted or not, decoded to the name on disk. Unquoted input is returned as is. */
2
+ export declare function unquoteGitPath(raw: string): string;
3
+ /**
4
+ * One side of a `--- ` / `+++ ` line, after its four-character marker: null for /dev/null, else the path without its
5
+ * a/ or b/ prefix. git ends a side holding a space with ONE tab (quoted or not, measured on git 2.54) — exactly that tab
6
+ * is dropped, never trimmed, so a name that begins or ends with a space survives.
7
+ */
8
+ export declare function diffSidePath(raw: string): string | null;
9
+ /**
10
+ * The paths a `diff --git <a> <b>` header names, for a section with no ---/+++ or rename lines (a mode-only change, a
11
+ * binary or an empty file). git writes such a header for ONE path, twice: `a/<X> b/<X>`. Each grammar only splits the
12
+ * header into its two sides, and both answer through the same samePath rule (D-1608), so they cannot disagree: an
13
+ * unquoted header splits where git apply's git_header_name does — the ` ` before `b/` at the midpoint, whitespace kept, so
14
+ * a name that ends in (or is) a space survives (D-1604); a quoted one must be two complete quoted strings joined by
15
+ * exactly one space, matched end to end, each unquoted with its prefix kept (D-1607). Anything else is unresolvable: []
16
+ * (the artifact manifest then keeps the section as logic, rendered and charged — over-charged, never hidden).
17
+ */
18
+ export declare function gitHeaderPaths(header: string): string[];
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Queue row 71b: git's path quoting, decoded once for every patch-text parser (acceptance's citable changed lines,
3
+ * the artifact manifest's section paths). git C-quotes a path holding a byte >= 0x80 (unless core.quotePath=false),
4
+ * a control character, a double quote or a backslash — `"docs/caf\303\251.md"` — with the escapes \a \b \t \n \v \f
5
+ * \r \" \\ and \ooo octal BYTES, so a non-ASCII name arrives as its UTF-8 bytes. JSON.parse is not this grammar: it
6
+ * has no octal escapes and returned `caf\303\251` with literal backslashes.
7
+ */
8
+ const ESCAPES = { a: 7, b: 8, t: 9, n: 10, v: 11, f: 12, r: 13, '"': 34, "\\": 92 };
9
+ /** A path exactly as git printed it, quoted or not, decoded to the name on disk. Unquoted input is returned as is. */
10
+ export function unquoteGitPath(raw) {
11
+ if (raw.length < 2 || !raw.startsWith('"') || !raw.endsWith('"'))
12
+ return raw;
13
+ const chars = Array.from(raw.slice(1, -1)); // code points, so a non-ASCII character is pushed whole
14
+ const bytes = [];
15
+ for (let i = 0; i < chars.length; i++) {
16
+ const ch = chars[i];
17
+ if (ch !== "\\") {
18
+ bytes.push(...Buffer.from(ch, "utf8"));
19
+ continue;
20
+ }
21
+ const octal = /^[0-7]{1,3}/.exec(chars.slice(i + 1, i + 4).join(""))?.[0];
22
+ if (octal) {
23
+ bytes.push(parseInt(octal, 8) & 0xff);
24
+ i += octal.length;
25
+ continue;
26
+ }
27
+ const escape = chars[i + 1];
28
+ if (escape !== undefined && escape in ESCAPES) {
29
+ bytes.push(ESCAPES[escape]);
30
+ i++;
31
+ continue;
32
+ }
33
+ bytes.push(92); // a backslash git never writes alone: kept literally
34
+ }
35
+ return Buffer.from(bytes).toString("utf8");
36
+ }
37
+ /**
38
+ * One side of a `--- ` / `+++ ` line, after its four-character marker: null for /dev/null, else the path without its
39
+ * a/ or b/ prefix. git ends a side holding a space with ONE tab (quoted or not, measured on git 2.54) — exactly that tab
40
+ * is dropped, never trimmed, so a name that begins or ends with a space survives.
41
+ */
42
+ export function diffSidePath(raw) {
43
+ const value = unquoteGitPath(raw.endsWith("\t") ? raw.slice(0, -1) : raw);
44
+ if (value === "/dev/null")
45
+ return null;
46
+ return value.replace(/^[ab]\//, "");
47
+ }
48
+ /** The one path a header side pair names — `a/<X>` and `b/<X>`, the same non-empty X — or none (D-1608: one rule). */
49
+ function samePath(left, right) {
50
+ return left.startsWith("a/") && right.startsWith("b/") && left.length > 2 && left.slice(2) === right.slice(2) ? [left.slice(2)] : [];
51
+ }
52
+ /**
53
+ * The paths a `diff --git <a> <b>` header names, for a section with no ---/+++ or rename lines (a mode-only change, a
54
+ * binary or an empty file). git writes such a header for ONE path, twice: `a/<X> b/<X>`. Each grammar only splits the
55
+ * header into its two sides, and both answer through the same samePath rule (D-1608), so they cannot disagree: an
56
+ * unquoted header splits where git apply's git_header_name does — the ` ` before `b/` at the midpoint, whitespace kept, so
57
+ * a name that ends in (or is) a space survives (D-1604); a quoted one must be two complete quoted strings joined by
58
+ * exactly one space, matched end to end, each unquoted with its prefix kept (D-1607). Anything else is unresolvable: []
59
+ * (the artifact manifest then keeps the section as logic, rendered and charged — over-charged, never hidden).
60
+ */
61
+ export function gitHeaderPaths(header) {
62
+ if (!header.includes('"')) {
63
+ const half = (header.length - 5) / 2;
64
+ return Number.isInteger(half) && half > 0 && header[2 + half] === " "
65
+ ? samePath(header.slice(0, 2 + half), header.slice(3 + half)) : [];
66
+ }
67
+ const quoted = /^("(?:\\.|[^"\\])*") ("(?:\\.|[^"\\])*")$/.exec(header);
68
+ return quoted ? samePath(unquoteGitPath(quoted[1]), unquoteGitPath(quoted[2])) : [];
69
+ }
@@ -32,7 +32,9 @@ export async function scopeGate(worktree, integrationTip, files, result, allowDe
32
32
  if (!files.length)
33
33
  return { gate: "scope", pass: true, details: "no file scope declared — unrestricted" };
34
34
  const baseRef = await scopeDiffBase(worktree, integrationTip);
35
- const changed = (await shGitOk(`git diff --name-only '${baseRef}..HEAD'`, worktree)).trim().split("\n").filter(Boolean);
35
+ // Queue row 71: without -z git C-quotes a non-ASCII path (`"docs/caf\303\251.md"`), which no files[] entry can ever
36
+ // match; -z prints every path verbatim, NUL-terminated (no trim: a name may begin or end with a space).
37
+ const changed = (await shGitOk(`git diff --name-only -z '${baseRef}..HEAD'`, worktree)).split("\0").filter(Boolean);
36
38
  const inScope = filesGlob(files); // the ONE files[] matcher — src/graph/files-glob.ts (Q120s)
37
39
  const offenders = changed.filter((f) => !inScope(f));
38
40
  if (!offenders.length)
@@ -18,7 +18,7 @@ export interface DispatchObservation {
18
18
  outcome: "accepted" | "not-accepted";
19
19
  authoritative: true;
20
20
  }
21
- export declare function heldWorkerTransport(driver: ExecutorDriver, slot: Slot, dispatchId: string, held: (data: Record<string, unknown>) => void, sleep?: (ms: number) => Promise<void>): Pick<ExecutorDriver, "run" | "read" | "status" | "waitOutput" | "waitAgentStatus">;
21
+ export declare function heldWorkerTransport(driver: ExecutorDriver, slot: Slot, dispatchId: string, held: (data: Record<string, unknown>) => void, sleep?: (ms: number) => Promise<void>): Pick<ExecutorDriver, "run" | "read" | "status" | "waitOutput" | "waitAgentStatus" | "transportState">;
22
22
  export declare function setAttemptHardTimeoutMsForTests(ms: number): void;
23
23
  export declare function resetAttemptHardTimeoutMsForTests(): void;
24
24
  export { harvestCpuFlatWindowMs, resetHarvestCpuFlatMsForTests, setHarvestCpuFlatMsForTests, workerTreeCpuMs, } from "./stall.js";
@@ -13,7 +13,7 @@ import { batchedProcessProbes, runnerPids } from "./suite-census.js";
13
13
  import { stringify } from "yaml";
14
14
  import { BOOTSTRAP_FAILURE_RE, classifyDeadChannel, classifyTransientCapacity, trailerPattern, writePrompt } from "../adapters/prompt.js";
15
15
  import { allAdapters, getAdapter, probeAll, readDoctor, rolePools } from "../adapters/registry.js";
16
- import { SettledTrailerTracker, addUsage, CAPACITY_RE, channelKey, matchesInputBox, matchesTrustDialog, QUOTA_RE } from "../adapters/types.js";
16
+ import { SettledTrailerTracker, addUsage, CAPACITY_RE, channelKey, matchesInputBox, matchesTrustDialog, quotaSignal } from "../adapters/types.js";
17
17
  import { bannerShell, paneDispatchCommand } from "../brand.js";
18
18
  import { collateralHits } from "../compile/collateral.js";
19
19
  import { ExecutionPolicySchema, DEFAULT_DIFF_CAP, globalConfigDir, loadConfigWithMode, readOverlayFile, repoOverlayPath, } from "../config/config.js";
@@ -103,6 +103,8 @@ export function heldWorkerTransport(driver, slot, dispatchId, held, sleep = (ms)
103
103
  status: (s) => probe("status", () => driver.status(s)),
104
104
  waitOutput: (s, p, ms, o) => probe("waitOutput", () => driver.waitOutput(s, p, ms, o)),
105
105
  waitAgentStatus: (s, st, ms) => probe("waitAgentStatus", () => driver.waitAgentStatus(s, st, ms)),
106
+ // row 108: forwarded only when the driver answers; absent stays absent, so the park reads it as unknown
107
+ ...(driver.transportState ? { transportState: (s) => probe("transportState", () => driver.transportState(s)) } : {}),
106
108
  run: async (s, command) => {
107
109
  const observer = driver;
108
110
  // A second delivery needs a receipt for THIS mutation, not an empty or idle terminal.
@@ -683,7 +685,7 @@ export function resetNudgeTimingForTests() {
683
685
  workerNudgeGraceMs = WORKER_NUDGE_GRACE_MS;
684
686
  }
685
687
  // T1 (OBS-263): in-loop quota-banner classification — the banner IS output, so the empty-output
686
- // rules can never catch it and the post-loop QUOTA_RE check only runs after the full window. Two
688
+ // rules can never catch it and the post-loop quota check only runs after the full window. Two
687
689
  // consecutive matching slices plus this much monotonic-tracker silence classify (a worker whose
688
690
  // diff merely quotes "rate limit" keeps working undisturbed).
689
691
  const QUOTA_BANNER_SILENT_MS = 3 * 60_000;
@@ -5361,6 +5363,7 @@ export async function runDaemon(repoRoot, opts = {}) {
5361
5363
  let rowSaturationHeld = false; // journaled once per attempt when the kill stands down
5362
5364
  let cpuHeld = false; // likewise for the CPU leg's stand-down (OBS-548)
5363
5365
  let paneReadHeld = false;
5366
+ let transportHeldNoted = false; // row 108: an open transport is journaled once per attempt
5364
5367
  let paneStatusHeld = false;
5365
5368
  const hardDeadline = attemptStart + (attemptHardTimeoutMs ?? stallWindowMs * 4);
5366
5369
  await armCpuLeg(true);
@@ -5512,7 +5515,7 @@ export async function runDaemon(repoRoot, opts = {}) {
5512
5515
  // and concludes the attempt the same way — only its post-loop outcome differs (bounded
5513
5516
  // requeue on this seat, then same-floor failover, never demotion). Quota wins a tie.
5514
5517
  const bannerRows = stallSnapshotBannerRows(paneText);
5515
- const quotaBanner = QUOTA_RE.exec(bannerRows);
5518
+ const quotaBanner = quotaSignal(bannerRows);
5516
5519
  const bannerMatch = quotaBanner ?? CAPACITY_RE.exec(bannerRows);
5517
5520
  if (bannerMatch)
5518
5521
  quotaStreak++;
@@ -5533,7 +5536,7 @@ export async function runDaemon(repoRoot, opts = {}) {
5533
5536
  // no `output =` here: the post-loop no-trailer tail re-reads the pane anyway, so an
5534
5537
  // assignment would only split the classification read from the verdict read.
5535
5538
  quotaBannerKilled = true;
5536
- journal.append("quota-banner", t.id, { slot: slot.name, attempt, silentMs: sliceNow - lastProgressAt, matched: quotaBanner[0], excerpt: quotaBanner.input, regex: QUOTA_RE.source });
5539
+ journal.append("quota-banner", t.id, { slot: slot.name, attempt, silentMs: sliceNow - lastProgressAt, matched: quotaBanner[0], excerpt: quotaBanner.input, regex: quotaBanner.source });
5537
5540
  break;
5538
5541
  }
5539
5542
  // T1 (OBS-262): the `paged` latch is deleted — status is sampled EVERY slice (and
@@ -5675,10 +5678,42 @@ export async function runDaemon(repoRoot, opts = {}) {
5675
5678
  const confirmedProcessTree = worktreeDelta === "unchanged"
5676
5679
  ? await observeWorkerProcessTree(dispatchScript, wt)
5677
5680
  : "unmeasurable";
5681
+ // Queue row 108 (D-1619): the transport's own CLOSED fact. A subprocess 'exit' precedes its stdio 'close', so a
5682
+ // just-finished worker can look gone — no pane bytes, no process, an unchanged worktree — with its trailer still
5683
+ // in flight. Not closed, or a driver with no answer, never parks: the rolling timeout continues, as for every
5684
+ // other unproven leg. Pane drivers answer closed (their closure is the pane gone, already required above).
5685
+ // ORDER MATTERS: the closed fact is read FIRST, then the pane once more. Closed means every byte is already in
5686
+ // the read surface, so an empty read AFTER it proves the worker wrote nothing; the earlier empty reads may
5687
+ // predate the close and are not that proof (the stale-empty race this row's own test caught).
5688
+ const transport = confirmedProcessTree === "empty" ? await workerTransport.transportState?.(slot) : undefined;
5689
+ let drainedEmpty = false;
5690
+ if (transport?.closed === true) {
5691
+ try {
5692
+ const drained = await workerTransport.read(slot, PANE_READ_ROWS);
5693
+ drainedEmpty = drained.trim().length === 0;
5694
+ if (!drainedEmpty) {
5695
+ everHadOutput = true;
5696
+ if (stallProgress.observe({ paneText: drained, contextTokens }))
5697
+ lastProgressAt = Date.now();
5698
+ }
5699
+ }
5700
+ catch (error) {
5701
+ if (error instanceof HeldProbeExhausted)
5702
+ throw error;
5703
+ }
5704
+ }
5678
5705
  const deathCertain = paneAbsent
5679
5706
  && processTree === "empty"
5680
5707
  && confirmedProcessTree === "empty"
5681
- && worktreeDelta === "unchanged";
5708
+ && worktreeDelta === "unchanged"
5709
+ && transport?.closed === true
5710
+ && drainedEmpty;
5711
+ if (confirmedProcessTree === "empty" && transport?.closed !== true && !transportHeldNoted) {
5712
+ transportHeldNoted = true;
5713
+ noteWorkerLiveness("worker-dead-held", {
5714
+ slot: slot.name, attempt, reason: transport ? "transport-open" : "transport-unknown", ...(transport ? { transport } : {}),
5715
+ });
5716
+ }
5682
5717
  if (deathCertain) {
5683
5718
  const preservation = await preserveDeadWorker(wt, taskBase);
5684
5719
  if (!preservation.ref) {
@@ -5692,7 +5727,8 @@ export async function runDaemon(repoRoot, opts = {}) {
5692
5727
  deadWorkerPark = { ref, reason };
5693
5728
  journal.append("worktree-preserved", t.id, { ref, ...producerFields(producerNow()) });
5694
5729
  noteWorkerLiveness("worker-dead-held", {
5695
- slot: slot.name, attempt, reason: "unambiguous-worker-death", ref,
5730
+ // row 108 legibility: the transport's closed state, byte count and exit code/signal (a kill shows its signal)
5731
+ slot: slot.name, attempt, reason: "unambiguous-worker-death", ref, transport,
5696
5732
  });
5697
5733
  break;
5698
5734
  }
@@ -6256,7 +6292,7 @@ export async function runDaemon(repoRoot, opts = {}) {
6256
6292
  // this repository's own fixture prose. The matched bytes and their stream offset ride the
6257
6293
  // row so a false positive is legible from the record alone.
6258
6294
  const quotaMatch = (interactive ? !workerFinished : exitCode !== 0)
6259
- ? QUOTA_RE.exec(stallSnapshotBannerRows(output))
6295
+ ? quotaSignal(stallSnapshotBannerRows(output))
6260
6296
  : null;
6261
6297
  // OBS-1161: transient capacity reads the SAME tail under the same guards — a live idle banner
6262
6298
  // and a no-trailer capacity exit classify identically — through the parse-boundary rule that a
@@ -74,6 +74,24 @@ export interface DetachedAddCapability {
74
74
  export interface TrustedStoreCapability {
75
75
  store: string;
76
76
  }
77
+ /**
78
+ * Whether, before the child spawns, the git its PATH resolves honours GIT_REFERENCE_BACKEND (git >= 2.54): a fresh
79
+ * probe repository whose own refs lack PROBE_REF, pinned to a fresh store that holds it as a symbolic ref. A git that
80
+ * ignores the pin reads the probe repository's own refs. Nothing is cached: each attempt probes again.
81
+ * Queue row 21: only a git that RAN TO COMPLETION and answered something else has shown it ignores the pin. A spawn
82
+ * error (EAGAIN, EMFILE, ENOENT), a timeout or a kill is no answer at all — "unknown", never "git < 2.54".
83
+ */
84
+ export type RefStorePinProbe = {
85
+ pin: "honoured" | "ignored";
86
+ } | {
87
+ pin: "unknown";
88
+ cause: string;
89
+ };
90
+ /** A probe git that threw: "ignored" only when it ran to completion and exited non-zero (a numeric exit status, no
91
+ * signal, no error code); anything else never gave an answer that was read — a timeout, a kill, a spawn failure, or
92
+ * ENOBUFS (status 0 with its output over the buffer cap) — and the cause is named for the refusal (queue row 21's
93
+ * closed table, D-1563). */
94
+ export declare function refStorePinProbeFailure(error: unknown): RefStorePinProbe;
77
95
  /**
78
96
  * v2.6.8 T2 (P3): the same uncached check, returning the child of tickmarkr's OWN git in a linked checkout — `env`
79
97
  * (the caller's) with GIT_DIR, GIT_COMMON_DIR and GIT_WORK_TREE set to the canonical gitdir, trusted common directory
@@ -83,7 +101,8 @@ export interface TrustedStoreCapability {
83
101
  * descendant) is followed for neither config, hooks, info/, objects nor refs: HEAD, branches, status and diffs read
84
102
  * the real repository and every ref write lands in the trusted store, whatever an inherited GIT_CONFIG_* or ref backend
85
103
  * says; a commondir REMOVED after it cannot move the main checkout's branch. Before such a child spawns, the git it
86
- * would run must prove it honours that pin (refStorePinHonoured); one that cannot (git < 2.54) runs without it, below.
104
+ * would run must prove it honours that pin (refStorePinProbe); one that cannot (git < 2.54) runs without it, below, and
105
+ * one whose probe never answers twice is refused (queue row 21).
87
106
  * Nothing here reads the command, and no caller leaves any pin. `recheck` (after the child exits) fails closed on ANY change to commondir since the check: the result
88
107
  * of that child is never returned. An ordinary checkout or a non-repository gets no pin.
89
108
  * NAMED RESIDUAL: git still reads $GIT_DIR/config.worktree when the trusted common config already enables
@@ -235,12 +235,21 @@ function assertFilesRefStore(common) {
235
235
  /** A detached auto gc or maintenance would outlive its child and read refs through a view already released. */
236
236
  const NO_BACKGROUND_GIT = "'gc.auto=0' 'maintenance.auto=false'";
237
237
  const PROBE_REF = "refs/tickmarkr/ref-pin-probe", PROBE_TARGET = "refs/tickmarkr/ref-pin-honoured";
238
- /**
239
- * Whether, before the child spawns, the git its PATH resolves honours GIT_REFERENCE_BACKEND (git >= 2.54): a fresh
240
- * probe repository whose own refs lack PROBE_REF, pinned to a fresh store that holds it as a symbolic ref. A git that
241
- * ignores the pin reads the probe repository's own refs. Nothing is cached: each attempt probes again.
242
- */
243
- function refStorePinHonoured(env) {
238
+ /** A probe git that threw: "ignored" only when it ran to completion and exited non-zero (a numeric exit status, no
239
+ * signal, no error code); anything else never gave an answer that was read — a timeout, a kill, a spawn failure, or
240
+ * ENOBUFS (status 0 with its output over the buffer cap) — and the cause is named for the refusal (queue row 21's
241
+ * closed table, D-1563). */
242
+ export function refStorePinProbeFailure(error) {
243
+ const ran = error;
244
+ if (typeof ran?.status === "number" && !ran.signal && !ran.code)
245
+ return { pin: "ignored" };
246
+ // Queue row 107: the code names the cause before the signal — Node kills an overflowing child with SIGTERM and sets
247
+ // ENOBUFS, and "killed by SIGTERM" would hide why; a self-inflicted kill carries no code, so it still reads "killed by".
248
+ const cause = ran?.code === "ETIMEDOUT" ? "timed out after 15 s" : ran?.code ? `failed with ${ran.code}`
249
+ : ran?.signal ? `killed by ${ran.signal}` : "failed with an unknown error";
250
+ return { pin: "unknown", cause };
251
+ }
252
+ function refStorePinProbe(env) {
244
253
  const dir = mkdtempSync(join(tmpdir(), "tickmarkr-ref-pin-"));
245
254
  try {
246
255
  const repo = join(dir, "repo"), store = join(dir, "store");
@@ -252,12 +261,14 @@ function refStorePinHonoured(env) {
252
261
  const probe = { ...env, GIT_DIR: repo, GIT_REFERENCE_BACKEND: `files://${store}` };
253
262
  for (const k of ["GIT_COMMON_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_OBJECT_DIRECTORY", "GIT_ALTERNATE_OBJECT_DIRECTORIES"])
254
263
  delete probe[k];
255
- let read = "";
264
+ let read;
256
265
  try {
257
266
  read = execFileSync("git", ["symbolic-ref", PROBE_REF], { cwd: dir, env: probe, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15_000 }).trim();
258
267
  }
259
- catch { /* refused below */ }
260
- return read === PROBE_TARGET;
268
+ catch (error) {
269
+ return refStorePinProbeFailure(error);
270
+ }
271
+ return { pin: read === PROBE_TARGET ? "honoured" : "ignored" };
261
272
  }
262
273
  finally {
263
274
  rmSync(dir, { recursive: true, force: true });
@@ -280,7 +291,8 @@ function warnRefStorePinUnavailable() {
280
291
  * descendant) is followed for neither config, hooks, info/, objects nor refs: HEAD, branches, status and diffs read
281
292
  * the real repository and every ref write lands in the trusted store, whatever an inherited GIT_CONFIG_* or ref backend
282
293
  * says; a commondir REMOVED after it cannot move the main checkout's branch. Before such a child spawns, the git it
283
- * would run must prove it honours that pin (refStorePinHonoured); one that cannot (git < 2.54) runs without it, below.
294
+ * would run must prove it honours that pin (refStorePinProbe); one that cannot (git < 2.54) runs without it, below, and
295
+ * one whose probe never answers twice is refused (queue row 21).
284
296
  * Nothing here reads the command, and no caller leaves any pin. `recheck` (after the child exits) fails closed on ANY change to commondir since the check: the result
285
297
  * of that child is never returned. An ordinary checkout or a non-repository gets no pin.
286
298
  * NAMED RESIDUAL: git still reads $GIT_DIR/config.worktree when the trusted common config already enables
@@ -309,7 +321,16 @@ export function ownGitPin(cwd, env, capability = {}) {
309
321
  // linked checkout on most installed gits, and a hostile git binary ignores every pin anyway; so it runs with the
310
322
  // path pins and the post-exit recheck (the 2.6.7 guard). NAMED RESIDUAL there: refs still resolve through the
311
323
  // commondir file, so a rewrite between check and exit can reach the hostile store before the recheck refuses.
312
- if (!refStorePinHonoured(child)) {
324
+ // Queue row 21: a probe that never answered is asked once more; a second non-answer refuses, because dropping to
325
+ // the weaker guard on a spawn error or timeout under load would silently weaken own git and blame the git version.
326
+ let pin = refStorePinProbe(child);
327
+ if (pin.pin === "unknown")
328
+ pin = refStorePinProbe(child);
329
+ if (pin.pin === "unknown") {
330
+ // infra, not a verdict: the ref-store pin probe never answered — name the probe and its cause
331
+ throw new GitTrustRefusal(common, `could not tell whether this git honours the ref-store pin: the probe (git symbolic-ref ${PROBE_REF}) did not complete twice (${pin.cause}), so tickmarkr's own git does not run here without it`);
332
+ }
333
+ if (pin.pin === "ignored") {
313
334
  delete child.GIT_REFERENCE_BACKEND;
314
335
  warnRefStorePinUnavailable();
315
336
  return { env: child, store: common, release: () => { }, unview: (text) => text, recheck };
package/dist/run/stall.js CHANGED
@@ -559,7 +559,7 @@ export function stallSnapshotTail(text, rows = QUOTA_BANNER_TAIL_ROWS) {
559
559
  }
560
560
  // T1 review (chrome-blind-matcher class, OBS-152/155): the tail of a RENDERED TUI frame is not
561
561
  // "what the pane printed last" — its bottom rows are fixed composer/welcome chrome. Codex pins
562
- // "• You have 3 usage limit resets available." there, so a raw-tail QUOTA_RE match fires on every
562
+ // "• You have 3 usage limit resets available." there, so a raw-tail quota match fires on every
563
563
  // frame of a wedged pane (verified against all 8 frames of tests/fixtures/codex-mcp-spinner) and
564
564
  // would fail a live worker over mid-work. Filter the KNOWN chrome instead of everything on screen
565
565
  // at some anchor: a novelty baseline cannot distinguish "chrome that was already there" from "a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickmarkr",
3
- "version": "2.7.0",
3
+ "version": "2.7.1",
4
4
  "description": "Spec in, verified work out.",
5
5
  "type": "module",
6
6
  "license": "MIT",