openclaw - ✅(Solved) Fix browser.tabCleanup config keys validate as 'Unrecognized' even though the runtime reads them [2 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#74577Fetched 2026-04-30 06:22:47
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
2
Author
Timeline (top)
cross-referenced ×3referenced ×2closed ×1commented ×1

BrowserTabCleanupConfig exists as a type, the runtime resolver resolveBrowserTabCleanupConfig(cfg.tabCleanup) reads it, and the dotted help-text/label entries (browser.tabCleanup.enabled, browser.tabCleanup.idleMinutes, browser.tabCleanup.maxTabsPerSession, browser.tabCleanup.sweepMinutes) are present in the runtime schema labeling layer. But the actual zod schema for the browser object has additionalProperties: false and does not declare tabCleanup — so any operator who tries to override the defaults via openclaw.json gets:

Gateway aborted: config is invalid.
browser: Unrecognized key: "tabCleanup"

…and at hot-reload time, the gateway auto-rolls back to last-known-good with [reload] config reload restored last-known-good config after invalid-config.

The defaults (enabled=true, idleMinutes=120, maxTabsPerSession=8, sweepMinutes=5) still apply at runtime, so cleanup does happen — operators just can't tune it.

Root Cause

Root cause sketch

Fix Action

Workaround

There is no openclaw.json-side workaround — operators have to live with the defaults (or the runtime fix from #71165, which closed COMPLETED). For more aggressive cleanup, adding a tab-hygiene rule to per-agent AGENTS.md so the model proactively closes tabs after each task works well in practice.

PR fix notes

PR #74588: Fix/browser tabcleanup schema 74577

Description (problem / solution / changelog)

Closes #74577.

`BrowserTabCleanupConfig` exists as a TS type, the runtime resolver
`resolveBrowserTabCleanupConfig` reads `cfg.browser.tabCleanup`, and
the dotted help/label entries (`browser.tabCleanup.enabled`,
`.idleMinutes`, `.maxTabsPerSession`, `.sweepMinutes`) are present —
but the strict zod schema for `browser` did not declare `tabCleanup`,
so any operator who tried to override the documented defaults
(`enabled=true`, `idleMinutes=120`, `maxTabsPerSession=8`,
`sweepMinutes=5`) saw their config rolled back at startup with:

    Gateway aborted: config is invalid.
    browser: Unrecognized key: "tabCleanup"

…and at hot-reload time:

    [reload] config reload restored last-known-good config after invalid-config

Defaults still applied at runtime, so cleanup *did* happen — operators
just couldn't tune it.

## Fix

Add a `tabCleanup` field to the `browser` zod object, mirroring
`BrowserTabCleanupConfig` exactly:

- `enabled: boolean | undefined`
- `idleMinutes: nonnegative integer | undefined` — `0` is a documented
  sentinel ("disable idle cleanup")
- `maxTabsPerSession: nonnegative integer | undefined` — `0` is a
  documented sentinel ("disable the cap")
- `sweepMinutes: positive integer | undefined` — must be > 0 because a
  zero-interval sweep would either spin or never fire downstream
- `.strict()` on the inner object so typos surface immediately

Regenerated `src/config/schema.base.generated.ts` (and its sha256) via
`scripts/generate-base-config-schema.ts` so the JSON-schema mirror
picks up the same shape.

## Tests

`src/config/zod-schema.browser-tab-cleanup.test.ts` adds 9 cases:

- Full documented shape parses
- Each sentinel-zero (`idleMinutes=0`, `maxTabsPerSession=0`) parses
- Partial overrides parse
- Unknown keys under `tabCleanup` are rejected
- Negative and non-integer `idleMinutes` are rejected
- `sweepMinutes <= 0` is rejected
- The surrounding browser strict-schema invariants still hold

`pnpm test src/config/zod-schema.browser-tab-cleanup.test.ts` —
9 passed. `pnpm exec oxfmt --check` — clean. `pnpm exec oxlint` —
0 warnings, 0 errors.

Closes #74577

Changed files

  • docs/.generated/config-baseline.sha256 (modified, +2/-2)
  • src/config/schema.base.generated.ts (modified, +39/-0)
  • src/config/zod-schema.browser-tab-cleanup.test.ts (added, +130/-0)
  • src/config/zod-schema.ts (modified, +20/-0)

PR #74638: config: accept browser.tabCleanup keys in zod schema (#74577)

Description (problem / solution / changelog)

Summary

  • Problem: The browser zod schema in src/config/zod-schema.ts is .strict() and does not declare a tabCleanup field, so any operator who tries to override the runtime defaults via openclaw.json gets Gateway aborted: config is invalid. browser: Unrecognized key: "tabCleanup". The runtime resolver, the BrowserTabCleanupConfig type, the schema labels, the help text, and the generated config baseline all already advertise these keys (browser.tabCleanup.{enabled,idleMinutes,maxTabsPerSession,sweepMinutes}).
  • Why it matters: As reported in #74577, operators see hot-reload roll back to last-known-good with [reload] config reload restored last-known-good config after invalid-config and there is no openclaw.json-side workaround. The defaults still apply, so cleanup happens, but operators cannot tune it.
  • What changed: Added a tabCleanup field to the browser zod schema mirroring BrowserTabCleanupConfig (all fields optional, .strict() so unknown subkeys still fail closed). Regenerated src/config/schema.base.generated.ts and docs/.generated/config-baseline.sha256 per the repo drift-check workflow. Added 3 regression tests in src/config/config.schema-regressions.test.ts.
  • What did NOT change (scope boundary): No runtime behavior, no resolver logic, no labels, no help text. The runtime already reads these keys via resolveBrowserTabCleanupConfig; this PR only stops the validator from rejecting them.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #74577
  • Related #71165 (runtime cleanup wiring landed)
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: When the runtime cleanup wiring landed in #71165, the BrowserTabCleanupConfig type, the help text in src/config/schema.help.ts:323-331, the labels in src/config/schema.labels.ts:675-679, the resolver resolveBrowserTabCleanupConfig, and the generated baseline were all added — but the corresponding zod object property was not. With .strict() set on the browser object, any explicit override is rejected as Unrecognized.
  • Missing detection / guardrail: pnpm check:base-config-schema checks that the generated JSON Schema matches the zod schema, and pnpm config:docs:check checks docs drift, but neither flagged that the help/labels referenced a path that the zod schema did not accept. A direct validateConfigObject({ browser: { tabCleanup: ... } }) regression test would have caught this; this PR adds that.
  • Contributing context: The zod source of truth and the labels/help layer evolved in parallel; the help layer was already in place when the runtime resolver landed.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
  • Target test or file: src/config/config.schema-regressions.test.ts
  • Scenario the test should lock in:
    • Positive: validateConfigObject({ browser: { tabCleanup: { enabled, idleMinutes, maxTabsPerSession, sweepMinutes } } }) returns ok.
    • Negative on bounds: sweepMinutes: 0 is rejected (zod schema declares .positive() for sweepMinutes).
    • Negative on unknown subkey: browser.tabCleanup.unknownKey is rejected (.strict() is preserved).
  • Why this is the smallest reliable guardrail: The bug is purely at the zod validation layer; a parser-level test directly on validateConfigObject is the smallest piece of code that proves both the accept and the reject paths.
  • Existing test that already covers this: No.
  • If no new test is added, why not: N/A (3 new tests added).

Test Plan

  • pnpm test src/config/config.schema-regressions.test.ts — 21/21 pass (3 new + 18 existing)
  • pnpm check:base-config-schema — clean after regenerate
  • pnpm config:docs:check — clean after regenerate (docs/.generated/config-baseline.sha256 updated and committed)
  • pnpm check:changed — clean (typecheck, oxlint, runtime sidecar guard, import-cycle guard, pairing/auth guards)
  • pnpm exec oxfmt --write --threads=1 src/config/zod-schema.ts src/config/config.schema-regressions.test.ts src/config/schema.base.generated.ts — clean

Notes

  • idleMinutes and maxTabsPerSession are nonnegative (>= 0) since 0 is a documented "disable" sentinel for these knobs in the help text. sweepMinutes is positive (> 0) because the existing runtime resolver uses normalizePositiveInteger(raw?.sweepMinutes, 5) and there is no 0 sentinel for the sweep interval.
  • Subobject is also .strict() so unknown keys under browser.tabCleanup continue to fail closed, consistent with the rest of the browser schema.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • docs/.generated/config-baseline.sha256 (modified, +3/-3)
  • src/config/config.schema-regressions.test.ts (modified, +39/-0)
  • src/config/schema.base.generated.ts (modified, +39/-0)
  • src/config/zod-schema.ts (modified, +9/-0)

Code Example

Gateway aborted: config is invalid.
browser: Unrecognized key: "tabCleanup"

---

{
  "browser": {
    "tabCleanup": {
      "enabled": true,
      "idleMinutes": 10,
      "sweepMinutes": 5,
      "maxTabsPerSession": 10
    }
  }
}

---

$ openclaw gateway restart
Gateway aborted: config is invalid.
browser: Unrecognized key: "tabCleanup"
Fix the config and retry, or run "openclaw doctor" to repair.

---

[gateway] Config auto-restored from last-known-good: ~/.openclaw/openclaw.json (reload-invalid-config)
[reload] config reload restored last-known-good config after invalid-config

---

function resolveBrowserTabCleanupConfig(cfg) {
  const raw = cfg?.tabCleanup;
  return {
    enabled: raw?.enabled ?? true,
    idleMinutes: normalizeNonNegativeInteger(raw?.idleMinutes, 120),
    maxTabsPerSession: normalizeNonNegativeInteger(raw?.maxTabsPerSession, 8),
    sweepMinutes: normalizePositiveInteger(raw?.sweepMinutes, 5)
  };
}
RAW_BUFFERClick to expand / collapse

Summary

BrowserTabCleanupConfig exists as a type, the runtime resolver resolveBrowserTabCleanupConfig(cfg.tabCleanup) reads it, and the dotted help-text/label entries (browser.tabCleanup.enabled, browser.tabCleanup.idleMinutes, browser.tabCleanup.maxTabsPerSession, browser.tabCleanup.sweepMinutes) are present in the runtime schema labeling layer. But the actual zod schema for the browser object has additionalProperties: false and does not declare tabCleanup — so any operator who tries to override the defaults via openclaw.json gets:

Gateway aborted: config is invalid.
browser: Unrecognized key: "tabCleanup"

…and at hot-reload time, the gateway auto-rolls back to last-known-good with [reload] config reload restored last-known-good config after invalid-config.

The defaults (enabled=true, idleMinutes=120, maxTabsPerSession=8, sweepMinutes=5) still apply at runtime, so cleanup does happen — operators just can't tune it.

Reproduction

OpenClaw 2026.4.26.

~/.openclaw/openclaw.json:

{
  "browser": {
    "tabCleanup": {
      "enabled": true,
      "idleMinutes": 10,
      "sweepMinutes": 5,
      "maxTabsPerSession": 10
    }
  }
}

Restart:

$ openclaw gateway restart
Gateway aborted: config is invalid.
browser: Unrecognized key: "tabCleanup"
Fix the config and retry, or run "openclaw doctor" to repair.

Or, if the gateway is already running and picks up the change via hot-reload, the err log shows:

[gateway] Config auto-restored from last-known-good: ~/.openclaw/openclaw.json (reload-invalid-config)
[reload] config reload restored last-known-good config after invalid-config

Root cause sketch

In dist/runtime-schema-TpYHXgGk.js the browser object schema lists enabled, evaluateEnabled, cdpUrl, remoteCdpTimeoutMs, remoteCdpHandshakeTimeoutMs, localLaunchTimeoutMs, localCdpReadyTimeoutMs, actionTimeoutMs, color, executablePath, headless, noSandbox, attachOnly, cdpPortRangeStart, defaultProfile, profiles, snapshotDefaults, ssrfPolicy, extraArgs. No tabCleanup. With additionalProperties: false, the field is rejected.

In dist/config-B0K_3bA1.js:

function resolveBrowserTabCleanupConfig(cfg) {
  const raw = cfg?.tabCleanup;
  return {
    enabled: raw?.enabled ?? true,
    idleMinutes: normalizeNonNegativeInteger(raw?.idleMinutes, 120),
    maxTabsPerSession: normalizeNonNegativeInteger(raw?.maxTabsPerSession, 8),
    sweepMinutes: normalizePositiveInteger(raw?.sweepMinutes, 5)
  };
}

…so the runtime expects exactly that path.

In dist/plugin-sdk/src/config/types.browser.d.ts the BrowserTabCleanupConfig type is also exported.

Suggested fix

Add a tabCleanup property to the browser zod schema mirroring BrowserTabCleanupConfig (all fields optional, additionalProperties: false) so the keys the help text and runtime already advertise become accepted at validation time.

Workaround

There is no openclaw.json-side workaround — operators have to live with the defaults (or the runtime fix from #71165, which closed COMPLETED). For more aggressive cleanup, adding a tab-hygiene rule to per-agent AGENTS.md so the model proactively closes tabs after each task works well in practice.

Related

  • #71165 — runtime cleanup wiring landed (closed COMPLETED 2026-04-25)
  • #40694 — broader behavioral request for ephemeral-tab cleanup (open)

extent analysis

TL;DR

Add a tabCleanup property to the browser zod schema to allow configuration overrides via openclaw.json.

Guidance

  • Review the browser object schema in dist/runtime-schema-TpYHXgGk.js and add a tabCleanup property with optional fields mirroring BrowserTabCleanupConfig.
  • Verify that the updated schema allows configuration overrides by checking the openclaw.json validation.
  • Test the updated configuration by restarting the gateway or triggering a hot-reload.
  • If issues persist, check the err log for any configuration-related errors.

Example

No code snippet is provided as the fix involves updating the zod schema, which is not shown in the issue.

Notes

The fix requires updating the browser object schema to include the tabCleanup property. This will allow operators to override the defaults via openclaw.json. The workaround mentioned in the issue can be used temporarily, but it requires adding a tab-hygiene rule to per-agent AGENTS.md.

Recommendation

Apply the suggested fix by adding the tabCleanup property to the browser zod schema, as it will allow operators to configure the tab cleanup settings via openclaw.json.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix browser.tabCleanup config keys validate as 'Unrecognized' even though the runtime reads them [2 pull requests, 1 comments, 2 participants]