Skip to content

fix(duration): reject junk between duration tokens instead of skipping it - #343

Open
devYRPauli wants to merge 3 commits into
steipete:mainfrom
devYRPauli:fix/parse-duration-contiguous
Open

fix(duration): reject junk between duration tokens instead of skipping it#343
devYRPauli wants to merge 3 commits into
steipete:mainfrom
devYRPauli:fix/parse-duration-contiguous

Conversation

@devYRPauli

@devYRPauli devYRPauli commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Problem

The multi-unit branch of parseDuration (src/duration.ts) scans with a global regex:

const multiDuration = /([0-9]+)(ms|h|m|s)/g;
...
if (total > 0 && lastIndex === normalized.length) {
  return total;
}

exec() on a global regex skips forward past anything that does not match, and the only structural check afterwards is that the last match ends at the end of the string. Junk before or between the unit tokens is therefore silently dropped rather than rejected:

Input Current result Expected
10gibberish5s 5000 fallback
abc5s 5000 fallback
1h!30m 5400000 fallback
5s!! fallback fallback (already correct)

The trailing-junk case is already rejected, which is what makes the other three look like an oversight rather than intent: 5s!! falls back, but abc5s quietly returns 5 seconds.

parseDuration backs --browser-timeout, --browser-input-timeout, --browser-attachment-timeout, --browser-recheck-delay, --browser-recheck-timeout and --browser-reuse-wait (src/cli/browserConfig.ts). A typo in one of those silently produces a different timeout than the user asked for, with no warning.

Fix

Require each match to begin where the previous one ended, so the tokens must cover the string contiguously:

if (match.index !== lastIndex) {
  return fallback;
}

Malformed input now returns the caller's fallback, matching what the trailing-junk case already did.

Verification

Behaviour before and after:

Input Before After
1h30m 5400000 5400000
2h15m30s 8130000 8130000
1 h 30 m 5400000 5400000
500ms / 30s / 2h unchanged unchanged
10gibberish5s 5000 fallback
abc5s 5000 fallback
1h!30m 5400000 fallback
1h30, zzz, 5s!! fallback fallback

Every valid input is unaffected; only the three silently-wrong cases change.

src/duration.ts had no test file, so this adds tests/duration.test.ts covering bare numbers, single units, multi-unit, whitespace, the fallback cases and the three above. Confirmed the new junk-rejection test fails against the unpatched module (AssertionError: expected 5000 to be 42) and passes with the fix.

  • tests/duration.test.ts: 4 passed
  • Caller suites (browserConfig, browserDefaults, dryRun, dryRun.coverage, sessionRunner, searchPersist, followup, mcp/consult): 138 passed
  • oxlint: clean; oxfmt --check: clean; tsc --noEmit: no errors in the changed files

Follow-up: surface rejected browser durations

Fair point on the review. The parser change is what makes a malformed value fall back rather than be partially parsed, but buildBrowserConfig passed a real default as the fallback, so --browser-timeout 1h!30m quietly became the default with nothing shown to the operator.

The eleven --browser-* duration options now go through one helper that parses with a NaN fallback, warns naming the option, the rejected value and the fallback actually in effect, then returns that fallback. Deliberately still a warning and not a hard error: erroring would be a bigger behaviour change than the problem calls for, and these are optional tuning flags. The wording and chalk.yellow + Warning: prefix follow src/cli/bundleWarnings.ts, which is the existing convention for non-fatal CLI input problems. src/duration.ts is untouched.

Real-behavior proof

Real CLI runs, valid value then malformed value. --dry-run summary because a live browser run needs a signed-in Chrome session, but this still goes through the real entrypoint and the real buildBrowserConfig(), stopping just before Chrome launches:

$ oracle --engine browser --model gpt-5.5-pro --browser-timeout 1h30m --dry-run summary --prompt "duration diagnostic proof"
oracle 0.16.1 - Guidance without the guesswork.
[preview] Oracle (0.16.1) browser mode (gpt-5.5-pro) with ~10 tokens.
...
exit_code=0

$ oracle --engine browser --model gpt-5.5-pro --browser-timeout "1h!30m" --dry-run summary --prompt "duration diagnostic proof"
oracle 0.16.1 - Globs become guidance.
Warning: invalid --browser-timeout duration "1h!30m"; using fallback 1200000ms.
[preview] Oracle (0.16.1) browser mode (gpt-5.5-pro) with ~10 tokens.
...
exit_code=0

The valid run prints no warning. Calling the same production config builder directly makes the resulting values explicit:

1h30m  => timeoutMs=5400000
Warning: invalid --browser-timeout duration "1h!30m"; using fallback 1200000ms.
1h!30m => timeoutMs=1200000

Checks:

$ pnpm vitest run tests/duration.test.ts tests/cli/browserConfig.test.ts
 Test Files  2 passed (2)
      Tests  51 passed (51)

$ pnpm run format:check   -> All matched files use the correct format. (410 files)
$ pnpm run lint           -> tsc --noEmit + oxlint, exit 0
$ pnpm test               -> 147 files / 1611 passed (18 files / 43 pre-existing skips)

Two tests added to tests/cli/browserConfig.test.ts: a valid explicit duration asserting no warning is logged, and a malformed one asserting both the fallback value and the exact warning text.

Wider test coverage for the shared helper

Good point that only --browser-timeout was exercised while the helper backs eleven options. Added coverage for the shapes that could actually differ rather than duplicating one test eleven times:

  • --browser-recheck-delay, whose fallback is 0 (the case most likely to be mishandled, since 0 is falsy)
  • --browser-auto-reattach-timeout, a second option with a real non-zero default
  • a combined case passing --browser-input-timeout, --browser-reuse-wait and --browser-auto-reattach-interval malformed together, asserting each resulting config field independently plus all three distinct warning strings and toHaveBeenCalledTimes(3), so a copy-pasted option name in one call site could not pass unnoticed

While in there I also audited all eleven call sites against their flag names and default constants. All eleven are correct, so no source change was needed.

$ pnpm vitest run tests/cli/browserConfig.test.ts tests/duration.test.ts
 Test Files  2 passed (2)
      Tests  54 passed (54)

$ pnpm run format:check   -> All matched files use the correct format. (410 files)
$ pnpm run lint           -> tsc --noEmit + oxlint, exit 0

…g it

The multi-unit branch of parseDuration scans with a global regex and only
checks that the final match ends at the end of the string. exec() skips
any text that does not match, so junk before or between the unit tokens
is silently dropped and a wrong duration is returned.

  parseDuration('10gibberish5s', fallback)  ->  5000
  parseDuration('abc5s', fallback)          ->  5000
  parseDuration('1h!30m', fallback)         ->  5400000

Require each match to start where the previous one ended, so the tokens
have to cover the whole string contiguously. Malformed input now returns
the caller's fallback, which is what the trailing-junk case already did.

parseDuration backs the --browser-timeout, --browser-input-timeout,
--browser-recheck-delay and related CLI options, so a mistyped value
silently produced a different timeout than the user asked for.

Adds tests/duration.test.ts; the file had no test coverage.
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Jul 29, 2026
@clawsweeper

clawsweeper Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed August 1, 2026, 7:21 PM ET / 23:21 UTC.

ClawSweeper review

What this changes

The branch requires multi-unit duration tokens to cover the whole input and makes malformed --browser-* duration values warn before using their existing fallback values.

Merge readiness

⚠️ Ready for maintainer review - 2 items remain

This PR remains necessary: current main still accepts leading or embedded junk in multi-unit duration strings and silently applies browser timeout fallbacks. The patch is focused, preserves valid duration behavior, adds the missing operator warning for rejected browser flags, and has sufficient real CLI proof; no actionable correctness finding remains.

Priority: P2
Reviewed head: 0a35bced0dc075b730135eb3709979977819629c

Review scores

Measure Result What it means
Overall readiness 🦞 diamond lobster (5/6) Focused source correction, broad shared-helper coverage, passing reported checks, and direct CLI proof make this an above-average merge-ready fix.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (terminal): The PR body includes after-fix terminal output from the real CLI dry-run path: a valid duration produces no warning, while malformed input reaches browser configuration, reports the fallback, and exits successfully.
Patch quality 🦞 diamond lobster (5/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The PR body includes after-fix terminal output from the real CLI dry-run path: a valid duration produces no warning, while malformed input reaches browser configuration, reports the fallback, and exits successfully.
Evidence reviewed 5 items Current-main parser defect: Current main scans multi-unit durations with a global regex and accepts a result when only the final match reaches the end, allowing unmatched leading or embedded text to be skipped.
Focused parser repair: The PR returns the supplied fallback when a regex match does not begin at the previous token boundary, which rejects malformed strings without changing contiguous valid units.
Fallback visibility and coverage: The shared helper parses with a NaN sentinel, emits a yellow warning only for rejected values, and returns the same per-flag fallback; tests cover valid input, non-zero fallback, zero fallback, and multiple malformed flags.
Findings None None.
Security None None.

How this fits together

Oracle’s browser CLI accepts duration strings for timeouts and retry delays, converts them to milliseconds, and passes the resulting configuration to browser-session execution. This PR hardens that parsing boundary so malformed optional flags cannot be partially interpreted without operator feedback.

flowchart LR
  A[Browser CLI duration flags] --> B[Duration parser]
  B --> C[Contiguous token validation]
  C --> D[Fallback and warning]
  D --> E[Browser configuration]
  E --> F[Browser session runtime]
Loading

Before merge

  • Resolve merge risk (P2) - Existing users with malformed multi-unit browser-duration values will now receive the intended configured fallback rather than a partially parsed timeout; the warning makes this compatibility correction visible.
  • Complete next step (P2) - This open, clean, proof-backed PR already owns the bounded repair, so a parallel ClawSweeper fix lane would duplicate contributor work.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Changed surface 4 files affected; 167 added, 12 removed The branch combines a three-line parser guard with shared browser-flag diagnostics and focused regression coverage.
Browser duration flags 11 call sites routed through 1 helper A shared validation path keeps warning text and fallback behavior consistent across browser timeout and retry settings.

Merge-risk options

Maintainer options:

  1. Accept the malformed-input correction (recommended)
    Merge the PR with the intentional compatibility change that malformed durations now fall back visibly instead of being partially parsed.

Technical review

Best possible solution:

Merge the narrow validation and warning change so valid browser durations remain unchanged while malformed values consistently use their documented defaults with an operator-visible diagnostic.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main source provides a high-confidence path: the global multi-token regex may skip unmatched text, while only the end position of its last match is validated.

Is this the best way to solve the issue?

Yes. The contiguous-match guard fixes the parser at its source, and the browser configuration helper retains existing defaults while making rejected operator input visible instead of silently changing runtime timing.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 68b8c51b0ee0.

Labels

Label changes:

  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦞 diamond lobster, so this older rating label is no longer current.

Label justifications:

  • P2: Malformed optional browser-duration input can alter browser runtime timing but does not indicate emergency user impact.
  • merge-risk: 🚨 compatibility: The PR intentionally changes malformed multi-unit duration values from partially parsed timing to the existing per-option fallback with a warning.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes after-fix terminal output from the real CLI dry-run path: a valid duration produces no warning, while malformed input reaches browser configuration, reports the fallback, and exits successfully.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal output from the real CLI dry-run path: a valid duration produces no warning, while malformed input reaches browser configuration, reports the fallback, and exits successfully.

Evidence

What I checked:

  • Current-main parser defect: Current main scans multi-unit durations with a global regex and accepts a result when only the final match reaches the end, allowing unmatched leading or embedded text to be skipped. (src/duration.ts:19, 68b8c51b0ee0)
  • Focused parser repair: The PR returns the supplied fallback when a regex match does not begin at the previous token boundary, which rejects malformed strings without changing contiguous valid units. (src/duration.ts:24, 0a35bced0dc0)
  • Fallback visibility and coverage: The shared helper parses with a NaN sentinel, emits a yellow warning only for rejected values, and returns the same per-flag fallback; tests cover valid input, non-zero fallback, zero fallback, and multiple malformed flags. (src/cli/browserConfig.ts:343, 0a35bced0dc0)
  • Feature provenance and likely owner: The current parser and browser-duration call sites trace to the same initial repository commit by Peter Steinberger; blame assigns the relevant lines to that commit. (src/duration.ts:19, 5daa6ce8c352)
  • Current-main and release check: The PR head is not contained by the local main branch or any local release tag; the latest available release tag is v0.16.1, so this repair has not already landed. (0a35bced0dc0)

Likely related people:

  • steipete: The duration parser and the browser-duration configuration call sites are both attributed by current blame to the initial repository commit authored by Peter Steinberger. (role: original implementation owner; confidence: high; commits: 5daa6ce8c352; files: src/duration.ts, src/cli/browserConfig.ts)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (19 earlier review cycles; latest 8 shown)
  • reviewed 2026-07-31T14:27:21.364Z sha 0a35bce :: needs changes before merge. :: [P3] Remove the inline test explanation
  • reviewed 2026-07-31T20:16:35.577Z sha 0a35bce :: needs changes before merge. :: [P3] Remove the inline test explanation
  • reviewed 2026-07-31T21:40:12.726Z sha 0a35bce :: needs changes before merge. :: [P3] Remove the inline test explanation
  • reviewed 2026-08-01T13:53:19.509Z sha 0a35bce :: needs changes before merge. :: [P3] Remove the redundant inline test explanation
  • reviewed 2026-08-01T15:48:37.443Z sha 0a35bce :: needs changes before merge. :: [P3] Remove the redundant inline test explanation
  • reviewed 2026-08-01T17:03:11.848Z sha 0a35bce :: needs changes before merge. :: [P3] Remove the redundant inline test explanation
  • reviewed 2026-08-01T18:19:41.214Z sha 0a35bce :: needs changes before merge. :: [P3] Remove the redundant parser explanation
  • reviewed 2026-08-01T19:46:13.608Z sha 0a35bce :: needs changes before merge. :: [P3] Remove the redundant parser explanation

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 30, 2026
The contiguity guard makes a malformed value fall back instead of being
partially parsed, but buildBrowserConfig passed a real default as the
fallback, so a bad --browser-timeout silently became the default with no
signal to the operator.

Route the browser duration options through a helper that parses with a
NaN fallback, warns naming the option, the rejected value and the
fallback in effect, then returns the fallback.
@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 30, 2026
The helper serves eleven --browser-* duration options but only
--browser-timeout was exercised. Add a zero-fallback option, a second
non-zero-default option, and a combined case asserting each malformed
option produces its own correctly named warning and its own fallback.
@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant