Skip to main content

Step 1: Call debug()

When the widget isn’t behaving the way you expect, the first thing to do is grab a snapshot of its current state. Open your browser console on the page where the widget should be and run:
window.HaloAgents.getInstance()?.debug();
You’ll see something like:
HaloAgents debug snapshot (sdk 0.9.0)
  org=org_abc123 user=user_42 path=/app/forms/new
  identity: token=set getUserToken=yes blocked=false hash=(unset) apiKey=set
  agent: Sales Agent (agt_xyz) [requireAuth]
  session: id=sess_1745684234_a8b1c2 open=false returning=true
  ui flags: trigger=false badge=false proactive=true greeting=false autoOpenUnseen=true banners=true inAppMessages=true
  dom: host=true trigger=false panel=true badge=false teasers=0 banners=0 inApp=0
  liveAgent: active=false
  scheduledProactiveTimers=0
Each line maps to a category of question:
What you seeWhat it tells you
sdk 0.x.yWhich SDK build the page actually loaded. Check this against the latest release when chasing version-specific bugs.
org=... user=...Whether init() and identify() got the values you expected. user=anonymous means identify() either wasn’t called or was called with "anonymous".
identity: token=set getUserToken=yes blocked=false hash=(unset) apiKey=setWhich auth credentials the SDK has. blocked=true means user-scoped requests are paused after an identity failure. getUserToken=yes means an async refresh callback is configured. Important for orgs with identity verification enabled.
agent: Sales Agent (agt_xyz) or agent: not loaded yetWhether the agent config has been fetched. If “not loaded yet” persists past the initial page load, check the network tab for failures on GET /api/sdk/config.
session: open=true/false returning=true/falseWhether the chat panel is currently expanded, and whether this is a continuation of a prior conversation.
ui flags: ...The resolved UI suppression flags after ui config and manualOnPaths evaluation. If you set ui: 'manual' and see flags as true here, your config didn’t take effect (typo, or init() was called twice).
dom: host=... trigger=... panel=...What’s actually present in the DOM right now. Mismatches with ui flags mean a render path isn’t respecting the flags (please report).
liveAgent: active=true name=SarahWhether a human teammate is currently handling this conversation.
scheduledProactiveTimers=NHow many proactive teasers are queued to fire.
The same snapshot is also returned as a serializable object you can pipe to support tooling:
const info = window.HaloAgents.getInstance().debug();
fetch("/internal/halo-debug", {
  method: "POST",
  body: JSON.stringify(info),
});
Sensitive fields (userToken, apiKey) are reduced to presence flags, never echoed, so the snapshot is safe to share.

Common issues

”The widget never appears”

Run window.HaloAgents.getInstance()?.debug(). If it returns undefined, the SDK isn’t initialized:
  1. Check that the <script> tag for cdn.haloagents.ai/sdk/latest/haloagents.umd.js is actually loading. Open the network tab, refresh, and look for it. If the request fails with a CSP violation, see Content Security Policy.
  2. Check that window.HaloAgents.init({ orgId: "..." }) is being called. Browser console: typeof window.HaloAgents should be "object".
  3. Confirm init() is called only once. A second call without destroy() is a no-op and logs a warning.
If debug() runs but dom.host=false, the host element was unmounted. Check whether something else on the page is calling destroy().

init() ran but nothing works / getInstance() is undefined”

If you load the SDK dynamically (async, next/script, or document.createElement("script")), onload can fire before window.HaloAgents is defined. Poll for the global before calling init():
function waitForHaloAgents(onReady, timeoutMs) {
  const deadline = Date.now() + (timeoutMs || 10000);
  (function poll() {
    if (window.HaloAgents) return onReady();
    if (Date.now() >= deadline) {
      console.error("HaloAgents SDK failed to load within timeout");
      return;
    }
    setTimeout(poll, 50);
  })();
}
See Async loading for a full example.

”The widget appears on marketing pages but not on /app/*”

Check the agent’s Page URL Rules in the dashboard (AI Agents → [Agent] → Channels → Chat Widget → Configure). If a rule excludes the current path, the widget unmounts entirely. debug() would return undefined in this case. If debug() returns a snapshot but dom.trigger=false, you’ve likely set ui: { trigger: false } or ui: 'manual'. Run debug() and look at ui flags to confirm.

”Identify isn’t working / users show as Anonymous”

Run debug() and check the user= line. Three failure modes:
SymptomCause
user=anonymousidentify() was never called with a real user ID.
user=user_42 but inbox shows “Anonymous”identify() was called with the ID but no name or email traits. Pass at least both.
identity: token=(unset) for an org with identity verification onThe host page never minted a JWT, or didn’t pass it via userToken to init() / identify(). See Identity Verification.

”My in-app message didn’t show (hidden widget / Contact Support button)”

In-app messages render independently of the floating launcher. They do not require the chat panel to be open. Common causes:
  1. init() was deferred until the user clicked Contact Support. In-app messages only fetch after init() runs. Call init() on page load and use openChat() on your button click instead.
  2. The embedder opted out. Run HaloAgents.getInstance()?.debug() and check ui flags.inAppMessages. If it’s false, the host set ui: { inAppMessages: false } (or pinned an SDK version before 0.9.0 with ui: 'manual').
  3. Audience / identify. Segment- or field-targeted messages require identify() with a matching user. Check user= in the debug() output.
  4. Previously dismissed. In-app dismissals persist in localStorage under haloagents_dismissed_{orgId}.

”I want zero overlays in manual mode (SDK ≥ 0.9.0)”

Since 0.9.0, ui: 'manual' and manualOnPaths allow dashboard banners and in-app messages by default. To restore the old behavior, pass a ui object with explicit opt-outs:
// Full manual preset with zero dashboard outreach
HaloAgents.init({
  orgId: "your-org-id",
  ui: {
    trigger: false,
    proactive: false,
    inAppMessages: false,
    banners: false,
  },
});

// manualOnPaths with opt-out on matched paths only
HaloAgents.init({
  orgId: "your-org-id",
  manualOnPaths: ["/app/*"],
  ui: { inAppMessages: false, banners: false },
});
Run debug() and confirm banners=false and inAppMessages=false.

”I set ui: 'manual' but a banner still showed up (SDK < 0.9.0)”

In SDK 0.5.1 through 0.8.x, ui: 'manual' suppressed banners and in-app messages. If you’re on ≥ 0.9.0, that is expected: manual mode now allows dashboard outreach. Upgrade intentionally or add the opt-out flags above if you want zero overlays.

”Console says ‘HaloAgents is already initialized’”

HaloAgents.init() was called twice without an intervening destroy(). The second call is a no-op and returns the existing instance, so the widget continues working with the first config (not the second). In SPAs this usually means init() is being called inside an effect that re-runs. Either:
  • Move init() to a singleton guard outside React/Vue lifecycles, or
  • Call HaloAgents.getInstance()?.destroy() before the second init() if you genuinely want to reinitialize with new config.

”Chat works on localhost but is blocked in production”

Almost always CSP. Open the production page, open DevTools console, look for messages mentioning “Content Security Policy” or “Refused to connect”. The fix is documented at Content Security Policy.

”Chat works in production but is blocked on localhost”

Inverse of the CSP case, and the cause is almost always Allowed Domains (origin lockdown). When that feature is on, every SDK request from a domain not in the list is rejected with a 403, and localhost will never match a production hostname. Open DevTools → Network. If the failing request is GET /api/sdk/config or POST /api/sdk/events with status 403 and a body like:
{
  "error": "Origin \"http://localhost:3000\" is not in this org's Allowed Domains. Add it (or 127.0.0.1 for local development) at https://app.haloagents.ai/dashboard/setup/install."
}
You’re hitting origin lockdown. Two fixes:
  1. Add a loopback entry to your allow-list. Go to Setup → Install → Allowed Domains in the dashboard and add 127.0.0.1 or localhost. Then run your dev server on the host you added (http://127.0.0.1:3000 or http://localhost:3000).
  2. Disable Allowed Domains while developing. Toggle it off in the dashboard. The widget will accept any origin until you re-enable it. Identity Verification (the per-user JWT defense) keeps working independently.
See Origin allow-list for the full model.

”Voice doesn’t work / microphone errors”

Microphone access requires Permissions-Policy: microphone=(self) on the page (or the parent if the SDK is iframed). See Microphone permissions.

”I see ‘config issue’ warnings on init”

SDK 0.6.0+ validates the config object you pass to init() and surfaces three classes of issue as a single grouped console.warn. The widget keeps working in every case; the warning just makes mistakes visible that previously failed silently. Typo. A common typo gets a “did you mean?” suggestion when there’s a near match:
HaloAgents: 1 config issue:
  • Unknown config key "manualOnPath". Did you mean "manualOnPaths"? Ignored.
Fix the spelling. Without the fix, the feature you intended to use is silently disabled. Wrong shape. The validator catches obvious type mistakes for the small set of fields where the wrong shape silently disables a feature instead of throwing:
HaloAgents: 1 config issue:
  • Invalid manualOnPaths (expected an array of glob patterns, got string). Ignored.
Fix the shape (e.g. wrap a single pattern in an array: manualOnPaths: ["/app/*"]).

Reporting a bug

When something genuinely seems wrong, the most useful bug report includes:
  1. Output of HaloAgents.getInstance().debug() (full snapshot, structured object).
  2. The pathname where the issue reproduces.
  3. Browser console errors (red), if any.
  4. What you expected vs. what happened.
Email this to [email protected] or open a ticket from the Help & Support page.