koala73/worldmonitor · error · ConvexError
TICKERS_LIMIT_EXCEEDED
TICKERS_LIMIT_EXCEEDED
Error message
tickers list capped at ${TICKERS_MAX} entries What it means
Thrown by normalizeTickers when the cleaned/deduped ticker list exceeds TICKERS_MAX (50). Input is trimmed, uppercased, filtered to ^[A-Z][A-Z0-9&-]{0,11}(\.[A-Z]{1,3})?$ (plain symbols, share-class, dot-suffix exchange listings), and deduped before the cap check. The cap mirrors the client-side market watchlist limit in src/services/market-watchlist.ts.
Source
Thrown at convex/alertRules.ts:172
* - cap at TICKERS_MAX
*
* NOT a registry check against shared/stocks.json — a shape-valid symbol
* the extractor never emits simply never intersects at the relay. Keeps
* alertRules independently shippable, same rationale as countries.
*/
function normalizeTickers(input: string[]): string[] {
const cleaned: string[] = [];
const seen = new Set<string>();
for (const raw of input) {
if (typeof raw !== "string") continue;
const upper = raw.trim().toUpperCase();
if (!/^[A-Z][A-Z0-9&-]{0,11}(\.[A-Z]{1,3})?$/.test(upper)) continue;
if (seen.has(upper)) continue;
seen.add(upper);
cleaned.push(upper);
}
if (cleaned.length > TICKERS_MAX) {
throw new ConvexError({
code: "TICKERS_LIMIT_EXCEEDED",
message: `tickers list capped at ${TICKERS_MAX} entries`,
});
}
return cleaned;
}
export const getAlertRules = query({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) return [];
return await ctx.db
.query("alertRules")
.withIndex("by_user", (q) => q.eq("userId", identity.subject))
.collect();
},
});View on GitHub (pinned to ffec79ac33)
Solutions
- Trim the tickers array to at most 50 unique valid symbols before submitting.
- Filter client-side to the regex ^[A-Z][A-Z0-9&-]{0,11}(\.[A-Z]{1,3})?$ — invalid shapes (^GSPC, GC=F, cashtags) are silently dropped server-side anyway.
- Reconcile against src/services/market-watchlist.ts which enforces the same 50-entry cap on the client.
Example fix
// before
const tickers = portfolio.holdings.map(h => h.symbol); // 80 symbols
await setAlertRules(ctx, { ..., tickers });
// after
const tickers = portfolio.holdings
.map(h => h.symbol.toUpperCase().trim())
.filter((s, i, arr) => /^[A-Z][A-Z0-9&-]{0,11}(\.[A-Z]{1,3})?$/.test(s) && arr.indexOf(s) === i)
.slice(0, 50);
await setAlertRules(ctx, { ..., tickers }); Defensive patterns
Strategy: validation
Validate before calling
const TICKERS_MAX = 50;
const TICKER_RE = /^[A-Z][A-Z0-9&-]{0,11}(\.[A-Z]{1,3})?$/;
function normalizeAndCapTickers(input) {
const seen = new Set();
const out = [];
for (const raw of input) {
const upper = String(raw).trim().toUpperCase();
if (!TICKER_RE.test(upper) || seen.has(upper)) continue;
seen.add(upper);
out.push(upper);
if (out.length === TICKERS_MAX) break;
}
return out;
} Type guard
const TICKER_RE = /^[A-Z][A-Z0-9&-]{0,11}(\.[A-Z]{1,3})?$/;
function isTickerList(input): input is string[] {
return Array.isArray(input) && input.every(t => typeof t === 'string' && TICKER_RE.test(t.trim().toUpperCase()));
} Try / catch
try {
await setAlertRules(args);
} catch (e) {
if (e instanceof ConvexError && /tickers list capped/.test(String(e.message))) {
// truncate to top 50 and retry
} else throw e;
} Prevention
- Client-side, mirror normalizeTickers (same regex + dedupe + cap 50) before submit.
- Align the UI watchlist with src/services/market-watchlist.ts which caps at 50.
- Reject index/futures symbols (^GSPC, GC=F) in the picker so they aren't counted before being dropped.
When it happens
Trigger: Calling setAlertRules, setAlertRulesForUser, or setNotificationConfigForUser with a tickers array yielding more than 50 unique valid symbols after normalization.
Common situations: Bulk import from a portfolio CSV; a watchlist sync that copies an entire index membership; a patched client bypassing the UI cap.
Related errors
- COUNTRIES_LIMIT_EXCEEDED
- INCOMPATIBLE_DELIVERY
- digestHour must be an integer 0–23
- digestTimezone must be a valid IANA timezone (e.g. America/N
- quietHoursStart must be an integer 0–23
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/5e11cfbb8bf40e60.
Report an issue: GitHub.