Bot Paper Mode Fix, Activity Version Bump, and Monitor Crash
Date: 2026-05-05
Summary
Three issues addressed in this update:
- TICK FLIM DEMO bot stuck in paper mode — InstrumentDO had stale cached
is_paper=true; added admin endpoint to force reload. - Activity events bumping bot version unnecessarily — play/stop/budget/schedule/name changes no longer increment
bots.version. - MonitorPanel crash —
price.toFixed()called on string values returned by Neon's serverless driver.
1. Bot Paper Mode Fix
Root Cause
bots.is_paper = truein the DB (already fixed last session viaUPDATE bots SET is_paper = false)- The InstrumentDO caches the bot row in memory (
sub.bot). Whenis_paper=trueis cached,evaluateBotcalls the paper broker adapter even after the DB row was patched. - The only way to force a DO reload is via
notifyInstrumentBotConfigChanged, which sendsPOST /instrument/bot-updatedto the DO, causing it to re-fetch from Neon.
Changes
New admin endpoint — POST /api/bots/:id/runtime-refresh
Protected by x-ingestion-admin-secret header via requireIngestionAccess middleware.
// bots-router.ts — registered BEFORE botsRouter.use('*', requireAuth)
botsRouter.post('/:id/runtime-refresh', requireIngestionAccess, handleAdminBotRuntimeRefresh);
// bots-handlers.ts
export const handleAdminBotRuntimeRefresh = async (c: Context<AppEnv>) => {
const botId = c.req.param('id' as never);
const sql = getSql(c.env);
const rows = await sql`SELECT * FROM bots WHERE id = ${botId} LIMIT 1`;
const bot = rows[0];
if (!bot) throw new AppError('Bot not found', 404);
await syncBotRuntimeRegistrations(c.env, botId, bot);
await notifyInstrumentBotConfigChanged(c.env, botId, bot);
const refreshed = await findAccessibleBotById(c.env, botId, bot.user_id);
return c.json({ ok: true, botId, status: bot.status, is_paper: bot.is_paper, bot: refreshed ?? bot });
};
Invocation (after deploy)
curl -X POST https://api.quantago.co/api/bots/f7657e75-dbfb-4854-9af6-67f538c1362b/runtime-refresh \
-H "x-ingestion-admin-secret: <secret>"
Response confirmed: is_paper: false, status: active, broker_provider: pepperstone, feed_provider: ctrader
2. Activity Version Bump Logic
Problem
Every bot activity event (status change, budget update, schedule change, name change) was bumping bots.version via versionMode: 'increment'. This creates meaningless version history for non-structural changes.
Design: versionMode Values
'increment'— bumpsbots.version(for strategy/parameter changes)'ensure-current'— logs activity without bumping version
Changes
| File | Change |
|---|---|
bots-service.ts · updateBotStatus | versionMode: 'increment' → 'ensure-current' |
bots-service.ts · updateBot options | Added versionMode?: 'ensure-current' | 'increment' to options type |
bots-service.ts · updateBot call | Auto-detects: 'ensure-current' when only name/description fields change, otherwise 'increment' (or explicit from caller) |
bots-budget-handlers.ts · handleUpsertBotBudget | versionMode: 'increment' → 'ensure-current' |
bots-chat-handlers.ts · set_trading_schedule | Added versionMode: 'ensure-current' to updateBot call |
Version bump still happens for:
- Strategy code changes
- Parameter changes (non-schedule/non-name)
- Manual rollbacks
Version bump no longer happens for:
launchBot/stopBot(status changes)- Budget allocation updates
- Trading schedule updates
- Bot name/description renames
3. MonitorPanel price.toFixed Crash
Root Cause
Neon PostgreSQL serverless driver returns NUMERIC/DOUBLE PRECISION columns as strings, not numbers. The BotTickDecisionSlim.price field is typed as number in TypeScript but arrives as string from polling DB queries. Calling .toFixed() on a string throws:
Uncaught TypeError: t.price.toFixed is not a function
The live WebSocket path handled this correctly but the DB polling path did not.
Changes in MonitorPanel.tsx
Fix 1 — at mapping time (line 93):
// Before
price: item.price,
// After
price: typeof item.price === 'number' ? item.price : Number(item.price) || 0,
Fix 2 — mini bar display:
// Before
{decision.price.toFixed(5)}
// After
{(typeof decision.price === 'number' ? decision.price : Number(decision.price) || 0).toFixed(5)}
Fix 3 — decision list:
// Before
{d.price.toFixed(5)}
// After
{(typeof d.price === 'number' ? d.price : Number(d.price) || 0).toFixed(5)}
Files Changed
projects/quantago/services/backend/src/routes/bots/bots-handlers.ts— addedhandleAdminBotRuntimeRefreshprojects/quantago/services/backend/src/routes/bots/bots-router.ts— registered admin route, importedrequireIngestionAccessprojects/quantago/services/backend/src/routes/bots/bots-service.ts— fixedupdateBotStatus+updateBotversionModeprojects/quantago/services/backend/src/routes/bots/bots-budget-handlers.ts— fixed budget versionModeprojects/quantago/services/backend/src/routes/bots/bots-chat-handlers.ts— fixed trading schedule versionModeprojects/quantago/services/frontend/src/routes/bots/MonitorPanel.tsx— fixed 3x price.toFixed crashes
Deployment
- Backend:
npx wrangler deploy→ version76e83b71-c701-4535-9b99-d211f11dfef4 - Admin endpoint called →
{ ok: true, is_paper: false }✅ - Frontend: deployed to
https://001a0b85.quantago-app.pages.dev