Skip to content

Troubleshooting

Things that go wrong in production, and how to fix them.

Symptom: an upload finished (CF acknowledged), but the asset row stays processing for > 10 minutes.

Diagnosis:

  • Check Media → Uploads: is the asset in that queue?
  • Check API logs for [Cron] media-sync tick: is the cron running?
  • Check CF Stream dashboard: did transcode fail?

Fix:

  • Wait one more media-sync tick (runs every minute). The cron reconciles state from CF.
  • If the asset flips to failed: click Retry in the uploads UI. If it fails again, delete + re-upload from a different source file.
  • If the cron isn’t running: sudo systemctl restart twistcue-api, then check registration logs.

Symptom: a refund executed but underpaidCents > 0: user did not receive full coins back.

Cause: user had already spent the coins before the refund arrived. Ledger cannot take balance below zero; the deduction clamped at what was left.

Fix:

  • Accepted behavior: this is by design.
  • Track underpaidCents in reconciliation. Finance eats the delta on the P&L.
  • If the customer complains: comp them a subscription or grant equivalent goodwill coins via wallet:adjust.

Capped pushes (“21,000 targeted, 18,000 delivered”)

Section titled “Capped pushes (“21,000 targeted, 18,000 delivered”)”

Symptom: push estimate shows fewer deliverable users than targeted.

Cause: the 3/day cap plus category opt-outs.

Diagnosis: the estimate response breaks it down: targeted / capped / categoryOptedOut / deliverable.

Fix:

  • Not a bug: this is the guard rail. See Compose & send a push.
  • To reduce capped counts: run fewer parallel campaigns to the same segments; space sends across days.
  • Never disable the cap. It’s a product invariant.

insufficient_permissions on a route I should have

Section titled “insufficient_permissions on a route I should have”

Symptom: 403 error with { required: ["some:perm"] } on an endpoint you thought your role covered.

Diagnosis:

  • Compare your role’s permissions (visible on /system/roles/:yourRoleId) against the required perm from the error.
  • Remember: superadmin * grants everything; specific roles have finite lists.
  • Support has wallet:adjust but not payments:refund: support requests refunds only.

Fix:

  • Ask a superadmin to grant you the missing permission, or use a different admin account.
  • If you believe the endpoint requires the wrong perm, check packages/contracts/src/rbac.ts and the @RequirePermission decorator on the controller method.

Symptom: an approval row has been pending for hours/days.

Diagnosis:

  • Nobody with the right permission has acted on it.
  • Or: the required approver is out; no other admin holds the perm.
  • Or: the approver is the same person as the requester (approver_must_differ on approve attempts).

Fix:

  • Ping a second admin with the right perm.
  • If nobody else has it: superadmin can approve (superadmin has *).
  • Rejecting is also valid: reject with a reason, requester resubmits later.

Expected. Re-login. If it happens sooner, someone bumped token_version (force-logout of your admin account). Check with a superadmin.

Symptom: the user’s balance on User 360 doesn’t match what they see on mobile.

Diagnosis:

  • Mobile might be cached. Ask user to swipe-refresh.
  • Or: a webhook is stuck (see the transactions view for the user’s pending purchases).
  • Or: a scheduled promo/grant hasn’t fired yet.

Fix:

  • Refresh the ledger view on User 360; it’s authoritative.
  • Compare against the ledger explicitly: SELECT SUM(amount) FROM coin_ledger WHERE user_id = ? AND account LIKE 'wallet_%'.
  • Never manually “correct” the balance. Adjust the ledger via the wallet-adjust flow.

Cause: the deduction would take the user below zero. Server refuses.

Fix:

  • Deduct only what’s there. If you need to reclaim more (fraud), use multiple partial deductions as the wallet refills, or accept the shortfall.
  • For a full clawback context, ban the user: banned users cannot spend the residual either way.

Series won’t publish: “incomplete localization”

Section titled “Series won’t publish: “incomplete localization””

Symptom: publish workflow banner says a target market’s language is missing.

Fix:

  • Fill the missing title_i18n[lang] and synopsis_i18n[lang] for the market.
  • Or narrow the series market_availability to only markets where the localization is complete.
  • Superadmin can force-publish (soft warning, not hard block); audit log will flag it.

Cause: almost always timezone confusion. Server is UTC.

Fix:

  • Check publish_schedules.run_at: should be UTC.
  • Unschedule → reschedule with correct UTC time.

zero-sum breach red banner on Economy monitor

Section titled “zero-sum breach red banner on Economy monitor”

Never ignore this. Means a ledger event was written with rows that don’t sum to zero: a code bug.

Action:

  1. Screenshot the banner.
  2. Find the offending event_id (economy monitor detail; or SELECT event_id, SUM(amount) FROM coin_ledger GROUP BY event_id HAVING SUM(amount) <> 0).
  3. Escalate to engineering immediately with the event_id and time window.

Symptom: 429 too_many_attempts on POST /admin/auth/login.

Cause: 10 attempts per 5 minutes per IP.

Fix: wait 5 minutes or use a different IP. If persistent, someone’s trying to brute-force: check the API logs.