- Light mode --color-status-ready: #5B7EF8 → #5e6ad2 (DESIGN.md specifies darker shade for light backgrounds)
- Light mode --color-tint-red: rgba(207,34,46,...) → rgba(220,38,38,...) to match new #dc2626
- Dark mode --color-tint-red: rgba(255,123,114,...) → rgba(239,68,68,...) to match new #ef4444
- ActivityDot.tsx: update hardcoded bg tints — active gets green rgba(34,197,94,0.1), ready gets blue rgba(91,126,248,0.1)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Without this, the catch block's `err === lastError` identity check fails and
the error is absorbed into the retry loop instead of propagating.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When rateLimitRetries was exhausted, a 429 fell through to the normal
error path where isRetryableHttpStatus(429)=true caused it to also
consume the error retry budget — giving 2×retries total attempts.
Now throws immediately when rate-limit budget is exhausted so the two
counters remain independent: up to `retries` rate-limit waits, then
up to `retries+1` attempts for genuine errors (5xx), never compounding.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- openclaw-probe.ts: fix trailing ": " when response body is empty —
use conditional interpolation instead of .trim() on the full string
- doctor.ts: split connectivity check and test-notify into separate
try-catch blocks with distinct error messages so failures are
attributed correctly ("Notifier connectivity check failed" vs
"Sending test notifications failed")
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, a 429 with no Retry-After header fell through to response.text()
with no explicit wait, relying on the generic exponential backoff. Now all 429
responses are handled uniformly via rateLimitRetries: use the Retry-After
value when present, otherwise fall back to retryDelayMs as the minimum wait.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each 429 with a Retry-After header was consuming one slot from the error
retry budget. With retries=3, three sustained rate-limits would exhaust
retries before any real error retry could fire.
Track rate-limit waits with a separate rateLimitRetries counter (capped at
retries to prevent infinite loops) and decrement attempt before continue so
the for-loop increment cancels out and the error retry budget is preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When notificationRouting didn't exist, the setup wizard created a default
that included desktop only for urgent/action and dropped it for warning/info.
Users with defaults.notifiers: [desktop] would silently lose desktop
notifications for lower-priority events since notificationRouting takes
precedence over defaults.notifiers.
Now seeds all four priority levels from the existing defaults.notifiers array
(plus openclaw) so no previously-configured notifier is lost.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix YAML project key detection for inline comments: trimmed.endsWith(":")
fails on valid YAML like "my-app: # description" — strip inline comments
before the check so getConfiguredRepos works with commented project keys
- Fix Discord retry backoff skipping after 429+5xx sequence: the skipNextBackoff
flag caused exponential backoff to be skipped on the attempt AFTER a 429,
even when that attempt failed with an unrelated 5xx error. Removed the flag
entirely — continue already skips backoff naturally for the 429 iteration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add module-level sanitizeCliArg() and apply to all AI tool execute
handlers (ao_spawn, ao_batch_spawn, ao_send, ao_kill, ao_session_restore,
ao_review_check, ao_session_cleanup) to prevent CLI flag injection from
LLM-supplied params
- Clear batchSpawnFollowUpTimeouts in board scanner stop handler too —
previously leaked when healthPollIntervalMs <= 0 (health service never starts)
- Exclude commented lines from shell profile token regex with (?!\s*#)
negative lookahead to avoid replacing # OPENCLAW_HOOKS_TOKEN=... lines
- Resolve \${ENV_VAR} placeholders in doctor's OpenClaw token check —
ao setup openclaw writes the literal string "\${OPENCLAW_HOOKS_TOKEN}"
which was being sent as the Bearer token instead of the actual value
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When `ao doctor --test-notify` is requested and config loading throws,
use fail() instead of warn() so the process exits non-zero. Consistent
with the existing behavior when no config file exists at all.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use consistent regex for OPENCLAW_HOOKS_TOKEN detection and replacement
in shell profile (prevents silent no-ops for non-exported lines)
- Broaden token detection regex to match lines with/without export prefix
and leading whitespace
- Fix misleading --non-interactive help text (token is auto-generated)
- Fix doctor.ts catch block to say "Notifier checks failed" not "load config"
- Fix 204 mock in Discord notifier test (ok: true, not ok: false)
- Fix weak no-duplicate assertion in setup.test.ts (actually count list items)
- Add discord to notifier options comment in config-instruction.ts
- URL-encode threadId in Discord webhook URL construction
- Add aoCwd to required[] in openclaw.plugin.json configSchema
- Add HTTPS recommendation comment to agent-orchestrator.yaml.example
- Add rimraf for cross-platform clean script in notifier-discord
- Rename "Recommended Settings" to "Required: Disable Conflicting Built-in Skills"
with explicit warning in docs
- Add /ao setup post-setup reminder to manually disable coding-agent skill
- Fix misleading README non-interactive example wording
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create test-utils.ts with reusable factories (createTestEnvironment,
createMockPlugins, createMockRegistry, createMockSCM, createMockNotifier,
createMockSessionManager, makeSession, makePR) and refactor
lifecycle-manager.test.ts to use them, reducing setup boilerplate by >70%.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>