You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Mail template holes accept a second | formatter silently: {{ x | default:0 | number:2 }} renders the literal text '0 | number:2', and validate and lint pass it #26673
Found by the hotclm app (objectstack-ai/hotclm#183) with EmailService.renderTemplate from @objectstack/plugin-email@18.2.0.
What happens. A mail hole takes one formatter. A chained hole {{ actual_amount | default:0 | number:2 }} is read as default with the argument 0 | number:2. It renders the literal text 0 | number:2 for null, and the bare unformatted value (71100) otherwise, in the subject, text and html faces. os validate and os lint both exit 0 on it with no diagnostic. Neither the formula parseHole nor plugin-email's FILTER_RE refuses the second pipe: each takes everything after the first colon as the argument.
Reach. Any app author who reaches for chaining, the natural spelling in most template languages, ships a notice that prints formatter syntax to recipients, and no gate tells them.
Expected. Either validate and lint refuse a hole with more than one |, naming the one-formatter rule and the 'branch in the caller' remedy, or the renderer supports chaining. The refusal is the smaller fix and matches the engine's stated data-only design.
Dedupe: searched mail template formatter chain / second pipe / hole argument. The nearest are #25082, #25189 and #25351 (hole placement encoding, a different rule). Sibling: #26660 (currency code literal-only).
Triage: audit line · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T13:02Z
⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in the mail-template hole parser (plugin-email); rationale: p3 · domain:server (lane merge: domain:services → domain:server) · area:i18n. A chained formatter is refused at os validate / save, with the refusal naming the one-formatter rule. Supporting chains would widen the template grammar and come back to triage. ⛔ Never the literal text.
Claim: PM loop round 1 (family dispatch member; chain head #26660 carries the full claim and serial constraints)
Session: session_01QSeKYmQXM3rHJcPnrEPk4B
Account: os-justin
Branch: claude/issue-26660-mail-hole-grammar
Worktree: objectstack-issue-26660
Domain: domain:server
Seat: domain:server#3
File surface: as the chain head #26660 declares (plugin-email and formula template engines, the mail-template validate rule if it is the producer) (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: "no path-derived mandate … default opus"), effort: high
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Responsibility: the hole parsers take everything after the first colon as the argument, so a second | is swallowed into it | none: os validate and os lint exit 0 on the chained hole | any mail-template author; hotclm hit it on 18.2.0
Thread-read: 6442560667
Premise: holds (seat re-check on origin/main74db43ef: FILTER_RE as quoted on #26660's claim; no second-pipe refusal)
Serial constraints cleared: see #26660's claim.
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: audit line · triage seat ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T13:02Z⛔ Not a claim, ⛔ not a dispatch.
plugin-email); rationale: p3 ·domain:server(lane merge:domain:services→domain:server) ·area:i18n. A chained formatter is refused atos validate/ save, with the refusal naming the one-formatter rule. Supporting chains would widen the template grammar and come back to triage. ⛔ Never the literal text.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1 (family dispatch member; chain head #26660 carries the full claim and serial constraints)
Session:
session_01QSeKYmQXM3rHJcPnrEPk4BAccount:
os-justinBranch:
claude/issue-26660-mail-hole-grammarWorktree:
objectstack-issue-26660Domain:
domain:serverSeat:
domain:server#3File surface: as the chain head #26660 declares (
plugin-emailandformulatemplate engines, the mail-template validate rule if it is the producer) (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: "no path-derived mandate … default opus"),effort: highClause-②: no (narrowing)
Metadata-protocol: narrowed
Responsibility: the hole parsers take everything after the first colon as the argument, so a second
|is swallowed into it | none:os validateandos lintexit 0 on the chained hole | any mail-template author; hotclm hit it on 18.2.0Thread-read: 6442560667
Premise: holds (seat re-check on
origin/main74db43ef:FILTER_REas quoted on #26660's claim; no second-pipe refusal)Serial constraints cleared: see #26660's claim.
objectstack-fleet commentedon Oct 27, 2026
ACCEPT → PR #26799 comment 6450350754 · contract review PASS
6450017524·domain:serverseat 3 · 2026-10-27T18:30Z. Landing waits on CI green at898b6a1f.objectstack-fleet commentedon Oct 27, 2026
Landed ·
domain:serverseat 3 · 2026-10-27T19:13Zdabe77182completedby the PR'sFixeslineState:
pm:dispatchedremoved in the same pass. #26803 (spec lane,Blocked-by: #26673) is now unblocked.