Skip to content

fix: regexes with a linear pattern over trusted input carry no wall-clock match timeout - #471

Merged
Blind-Striker merged 2 commits into
masterfrom
fix/427-tool-regex-timeout
Oct 17, 2026
Merged

Blind-Striker merged 2 commits into
masterfrom
fix/427-tool-regex-timeout

Conversation

@Blind-Striker

@Blind-Striker Blind-Striker commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Regexes whose pattern matches in linear time over trusted input no longer carry a wall-clock match timeout; the four that can backtrack quadratically keep theirs, with the reason beside each. The rule goes into the coding style. Generated output is unchanged.

Closes #427.

Why

.NET's regex match timeout is wall-clock: on a descheduled thread it fails a match that needs microseconds. #426 showed this on a stalled macOS merge-group runner and ejected an unrelated PR, and #428 removed the timeout from eleven test-side regexes. This PR covers the rest.

What changes

  • Tool-side (5): SnapshotStateReader's full-SHA check, SourceWatchLoader's SHA-256 check, and SnapshotMarkdownRenderer's identity-table, pin-prose and date-line patterns move to Timeout.Infinite.
    • Inputs: spec/SNAPSHOT.md, spec/source-watch.json, git rev-parse output and a maintainer-selected receipt.
    • Why it's safe: the hashes match a fixed width; the renderer's runs are delimiter-separated (one pattern is unanchored), so it scans linearly.
  • Test-side (14): 13 Tools.Tests regexes plus DriveManifestTests move to Timeout.Infinite, or to Regex.InfiniteMatchTimeout where the API takes a TimeSpan. PackedReadmeTests' timeout field goes with its only use.
    • Inputs: repository files, packed test outputs and test constants.
    • Evidence: each one was audited structurally and its caller traced; see below.
  • Kept (4): these can backtrack quadratically on adversarial text, so each keeps its finite timeout, with the reason in a comment:
    • AnalyzerConfigReader's property line, where a lazy value and a trailing whitespace run overlap;
    • the comment scan's documentation-path rule, whose path class also matches docs/;
    • its Stryker marker, where each unanchored start retries a line that can't reach $;
    • CommentReader's PowerShell block, where each unclosed <# scans to the end.
  • Canon (docs/engineering/coding-style.md §3): a regex whose pattern matches in linear time over repository-pinned or test-local input passes Timeout.Infinite, or Regex.InfiniteMatchTimeout where the API takes a TimeSpan. One over untrusted input, or one whose pattern can backtrack superlinearly, keeps a finite timeout with its reason beside it. This records the decision UpstreamClientContract's route regex times out on loaded runners and can eject any PR #426 and test: regexes over pinned and in-test input carry no wall-clock match timeout (#426) #428 already took, so that a new regex doesn't reintroduce the flake.
  • Out of scope: the SDK's generated 100 ms key-regex reads untrusted runtime input, and it stays as it is (protocol-and-generation.md).

Evidence

  • The audit: every changed pattern was checked for linearity on any input, meaning no nested quantifier, no quantified group whose alternatives overlap, and no backreference under a quantifier. Each caller was traced to its input. The four kept patterns each have a concrete quadratic input. Only the timeout argument changes; every pattern and every option is untouched.
  • A search across tools/, tests/ and src/ found no other finite regex timeout over trusted input.
  • Covering tests: the existing classes cover every changed regex, so there are no new rows. A timeout argument's removal has no deterministic behaviour to test, so there is no mutation row.

Local check and review

  • Local check on dev-01, after a root Release build: slopwatch 0 issues; 0 warnings; both format passes; verify-clones passing; generate --verify current; the whole Tools.Tests project at 2086/0, Packaging included; DriveManifestTests 3/3 on net10.0 and net11.0. CI is the full gate.
  • A miss caught by the gate: the first draft of the Stryker reason comment quoted the marker's own text. The repository's comment-residue guard flagged it, and the reworded comment passes.
  • Process:
    • The tool-side edit is a coordinator hand edit: six one-token changes, cheaper than a dispatch.
    • A fresh cross-family review (Sol) found the 13 extra test-side sites and corrected the original rationale. The renderer has an unanchored pattern, and one caller reads a maintainer-selected receipt; neither changes the decision.
    • A Sol audit task then classified all 17 test sites. Its edits stopped on a scope question (removing the unused field), so the coordinator applied the audited verdicts.

Provenance: Generator lane, coordinated and finished in Claude Code; review and audit on the opencode dispatch harness (openai/gpt-6.1-sol-fast-1m).


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

…lock match timeout (#427)

.NET's match timeout is wall-clock, so on a stalled runner it fails a match that takes
microseconds (#426). The tool-side snapshot reader, renderer and source-watch loader read
spec/SNAPSHOT.md, spec/source-watch.json, `git rev-parse` output and a maintainer-selected
receipt; their patterns either match a fixed width (the 40- and 64-character hashes) or scan
linearly (the renderer's delimiter-separated runs, one of them unanchored). Thirteen Tools.Tests
regexes over repository files, packed test outputs and test constants are the same case, as is
DriveManifestTests' instance-name check. Each moves to Timeout.Infinite, or
Regex.InfiniteMatchTimeout where the API takes a TimeSpan; PackedReadmeTests' timeout field goes
with its only use.

Four Tools.Tests patterns can backtrack quadratically on adversarial text (AnalyzerConfigReader's
property line, the comment scan's documentation-path and Stryker rules, CommentReader's
PowerShell block), so they keep their finite timeout with the reason beside it.

Closes #427
…out; a superlinear or untrusted one keeps a finite timeout with its reason
@Blind-Striker
Blind-Striker added this pull request to the merge queue Oct 17, 2026
Merged via the queue into master with commit ff26ea1 Oct 17, 2026
12 checks passed
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.

Tool-side regexes over pinned input carry a 1000 ms wall-clock match timeout (same flake class as #426)

1 participant