Skip to content

fix(discord): auto presence health signal#33277

Merged
thewilloftheshadow merged 1 commit intomainfrom
feat/discord-presence-health
Mar 3, 2026
Merged

fix(discord): auto presence health signal#33277
thewilloftheshadow merged 1 commit intomainfrom
feat/discord-presence-health

Conversation

@thewilloftheshadow
Copy link
Copy Markdown
Member

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: Discord connected state was not reported for health monitoring, and there was no runtime/quota-driven presence signal for operators.
  • Why it matters: Stuck reconnect loops are not auto-restarted, and operators lack at-a-glance health status in Discord.
  • What changed: added auto-presence controller (runtime availability -> Discord status), config schema/docs/labels/tests, and connected true/false reporting in monitor startup/shutdown.
  • What did NOT change (scope boundary): no new self-profile or action-gate behavior; no generic cross-provider presence tooling.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • 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

User-visible / Behavior Changes

  • New channels.discord.autoPresence configuration to map runtime availability to Discord presence (online/idle/dnd) with optional status text overrides.
  • Discord provider now reports connected true on login and false on shutdown for health monitoring.

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: n/a
  • Runtime/container: n/a
  • Model/provider: n/a
  • Integration/channel (if any): Discord
  • Relevant config (redacted):
{
  channels: {
    discord: {
      autoPresence: {
        enabled: true,
        intervalMs: 30000,
        minUpdateIntervalMs: 15000,
        exhaustedText: "token exhausted",
      },
    },
  },
}

Steps

  1. Enable channels.discord.autoPresence.enabled=true.
  2. Start the gateway and simulate exhausted runtime profile state.
  3. Observe Discord presence updates (dnd/idle/online) as runtime state changes.

Expected

  • Presence reflects runtime availability mapping; connected status updates in runtime snapshot.

Actual

  • Not run (tests not executed).

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: not run.
  • Edge cases checked: not run.
  • What you did not verify: end-to-end Discord presence update against live gateway.

Compatibility / Migration

  • Backward compatible? (Yes)

  • Config/env changes? (Yes)

  • Migration needed? (No)

  • If yes, exact upgrade steps:

    • Optional config enablement only: set channels.discord.autoPresence.enabled=true.

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly:
    • Disable auto-presence: channels.discord.autoPresence.enabled=false
  • Files/config to restore:
    • Revert commits from this PR
  • Known bad symptoms reviewers should watch for:
    • Unexpected presence flapping or missing connected state updates.

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: Presence oscillation with noisy runtime signals.
    • Mitigation: min update interval throttling and signature dedupe.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 3, 2026

Greptile Summary

This PR adds auto-presence and connected-state health monitoring to the Discord provider. The implementation is functionally sound:

  • Auto-presence controller correctly maps runtime availability to Discord bot status (healthy→online, degraded→idle, exhausted→dnd) on a configurable interval with deduplication and rate-limiting
  • setStatus({ connected: true/false }) calls are properly placed on login and shutdown for health monitoring
  • Timer lifecycle is safe: starts before connect, refreshes on Ready, and always stops in finally blocks
  • Zod cross-field validation for minUpdateIntervalMs ≤ intervalMs is present and tested
  • Config schema, types, and unit tests are all included

Minor documentation gap: The degradedText config option supports the {reason} placeholder in the code (just like exhaustedText), but this is not documented in the Discord channel docs—only exhaustedText is called out as supporting templates.

Confidence Score: 4/5

  • PR is functionally safe to merge; only a minor documentation consistency gap remains.
  • The auto-presence implementation is sound: timer lifecycle is correct (starts before connect, refreshes on Ready, always stops in finally), deduplication and rate-limiting are in place, cross-field Zod validation works, and runtime logic properly guards against gateway disconnection. The prior review's concern about a TypeScript type error in auto-presence.ts was a false positive—"unknown" is already a valid member of the AuthProfileFailureReason union type. The only issue is that the docs don't mention that degradedText supports the {reason} placeholder, though the code implements it correctly.
  • docs/channels/discord.md (documentation consistency: add {reason} placeholder support note for degradedText)

Last reviewed commit: e6dbd1f

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +858 to +860
- `autoPresence.healthyText`
- `autoPresence.degradedText`
- `autoPresence.exhaustedText` (supports `{reason}` placeholder)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in resolvePresenceActivities calls renderTemplate(template, { reason: reasonLabel }) for the degraded state as well (line 174), so autoPresence.degradedText supports the {reason} placeholder too. The docs currently only mention this template capability for exhaustedText.

Suggested change
- `autoPresence.healthyText`
- `autoPresence.degradedText`
- `autoPresence.exhaustedText` (supports `{reason}` placeholder)
- `autoPresence.healthyText`
- `autoPresence.degradedText` (supports `{reason}` placeholder)
- `autoPresence.exhaustedText` (supports `{reason}` placeholder)
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/channels/discord.md
Line: 858-860

Comment:
The code in `resolvePresenceActivities` calls `renderTemplate(template, { reason: reasonLabel })` for the `degraded` state as well (line 174), so `autoPresence.degradedText` supports the `{reason}` placeholder too. The docs currently only mention this template capability for `exhaustedText`.

```suggestion
    - `autoPresence.healthyText`
    - `autoPresence.degradedText` (supports `{reason}` placeholder)
    - `autoPresence.exhaustedText` (supports `{reason}` placeholder)
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6dbd1fb27

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +125 to +126
if (profileIds.length === 0) {
return { state: "degraded", unavailableReason: null };
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle empty auth store without forcing degraded presence

When channels.discord.autoPresence.enabled is on and auth-profiles.json has no entries, resolveAuthAvailability always returns degraded, so Discord status stays idle even if the runtime is actually healthy (for example, env/config-backed provider credentials or local models). This conflicts with runtime fallback behavior, which only treats a provider as unavailable when profiles exist and all are in cooldown (src/agents/model-fallback.ts checks profileIds.length > 0 && !isAnyProfileAvailable). As written, many valid setups will report permanent degradation.

Useful? React with 👍 / 👎.

const botIdentity =
botUserId && botUserName ? `${botUserId} (${botUserName})` : (botUserId ?? botUserName ?? "");
runtime.log?.(`logged in to discord${botIdentity ? ` as ${botIdentity}` : ""}`);
opts.setStatus?.({ connected: true });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report connected only after gateway connectivity is confirmed

This sets connected: true before any confirmed gateway connection event, so startup paths where REST identity fetch fails or websocket connect never succeeds can still be reported as connected until some later lifecycle patch arrives. In those cases, operators and health tooling get a false healthy signal. The connected=true update should come from the existing lifecycle connection callbacks that observe actual gateway connectivity.

Useful? React with 👍 / 👎.

@StdCarrot
Copy link
Copy Markdown

Thanks for consolidating this work 🙏

One important gap: this PR currently does not include the Discord self-profile update scope from #33202.

In #33202, there is a separate, security-scoped feature for Discord-only self updates:

  • new self-profile action
  • strict self-only enforcement (userId/memberId/target must match bot self)
  • action gate: channels.discord.actions.selfProfile (default false)
  • avatar buffer size limit checks
  • gateway preflight for combined profile+presence updates to avoid partial mutation

Why self-profile update is needed

autoPresence solves runtime health signaling, but it does not solve operational bot identity control.
For Discord ops, teams still need a safe in-band way to:

  • update bot nickname per guild (context-aware identity)
  • update avatar for environment/team separation (prod/staging/tenant clarity)
  • set status/custom message in controlled automation flows
  • avoid manual Discord UI changes or direct bot-token API scripts

Without self-profile, operators fall back to ad-hoc/manual updates, which are:

  • less auditable
  • harder to automate
  • more error-prone
  • and often broader in privilege surface than a gated self-only action

So these are complementary scopes:

  • autoPresence: health/quota signaling
  • self-profile: secure, least-privilege identity/profile management

Could we either:

  1. include/cherry-pick the self-profile commits from feat(discord): use bot presence as runtime healthcheck signal (self-profile + quota-aware status) #33202, or
  2. keep fix(discord): auto presence health signal #33277 focused on autoPresence and open a follow-up PR that preserves feat(discord): use bot presence as runtime healthcheck signal (self-profile + quota-aware status) #33202’s self-profile scope and attribution?

I’m happy to help split this cleanly if preferred.

@openclaw-barnacle openclaw-barnacle bot added the agents Agent runtime and tooling label Mar 3, 2026
@thewilloftheshadow thewilloftheshadow self-assigned this Mar 3, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1596941fcb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +125 to +126
if (profileIds.length === 0) {
return { state: "degraded", unavailableReason: null };
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat empty auth stores as healthy availability

When auto presence is enabled, this branch forces state: "degraded" whenever auth-profiles.json has no entries, so setups that use env/config credentials without auth profiles will remain idle even while requests succeed. Fresh evidence: runWithModelFallback only marks provider availability as failed when profileIds.length > 0 and all profiles are in cooldown, so this logic diverges from the runtime failover path and reports a persistent false-degraded status.

Useful? React with 👍 / 👎.

@thewilloftheshadow thewilloftheshadow force-pushed the feat/discord-presence-health branch from 1596941 to 4ab3bbd Compare March 3, 2026 17:20
@thewilloftheshadow thewilloftheshadow merged commit e28ff12 into main Mar 3, 2026
10 checks passed
@thewilloftheshadow thewilloftheshadow deleted the feat/discord-presence-health branch March 3, 2026 17:21
@thewilloftheshadow
Copy link
Copy Markdown
Member Author

Landed via temp rebase onto main.

  • Gate: pnpm check && pnpm build
  • Tests:
    • pnpm vitest run --config vitest.unit.config.ts src/config/config.discord-presence.test.ts
    • pnpm vitest run --config vitest.config.ts src/agents/auth-profiles/usage.test.ts src/agents/auth-profiles.cooldown-auto-expiry.test.ts
    • pnpm vitest run --config vitest.channels.config.ts src/discord/monitor/auto-presence.test.ts src/discord/monitor/provider.test.ts
  • Land commit: 4ab3bbd
  • Merge commit: ${merge_sha}

Thanks @thewilloftheshadow!

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ab3bbd7e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +131 to +133
const hasUsableProfile = profileIds.some(
(profileId) => !isProfileInCooldown(params.store, profileId, params.now),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict availability check to runtime-eligible profiles

This marks Discord auto-presence as healthy when any auth profile is usable, even if that profile is for an unrelated provider that the current runtime/model path cannot use. In mixed-profile setups (for example, OpenAI model in cooldown plus an unused Anthropic key), status will stay online while requests fail, because resolveAuthAvailability only checks Object.keys(store.profiles) here, whereas fallback execution is provider-scoped (src/agents/model-fallback.ts computes profileIds per candidate provider before deciding availability). This mismatch can hide real outages from operators.

Useful? React with 👍 / 👎.