> For the complete documentation index, see [llms.txt](https://docs.trover.tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trover.tech/engineering/verify-2026-08-31-jobid.md).

# Verify: BullMQ job ids, dex-host /nfts rewrite

## What the 500 actually was

`POST /nft-market/collections/:slug/resync` returned `{"message":"Custom Id cannot contain :"}`. BullMQ reserves `:` for its own key namespacing and rejects custom job ids containing one — with a narrow legacy exemption for ids that split into exactly **three** parts, which BullMQ's own source marks for removal in the next breaking change.

That exemption is why this went unnoticed. Whether an id worked depended on how many segments it happened to have:

| job id                                 | segments | result                                                                              |
| -------------------------------------- | -------- | ----------------------------------------------------------------------------------- |
| `nft:<actionId>`                       | 2        | **threw** — every NFT quote 500'd                                                   |
| `nft-sync:<chain>:<slug>:<bucket>`     | 4        | **threw** into `.catch(() => undefined)` — the API never enqueued a collection sync |
| `nft-resync:<chain>:<slug>:<ts>`       | 4        | **threw** — the 500 you hit                                                         |
| `nft-execute:<id>:<ts>`                | 3        | worked by luck                                                                      |
| `dex-order-wake:<id>:<ts>`             | 3        | worked by luck                                                                      |
| `nft-canary-reconcile:<slug>:<bucket>` | 3        | worked by luck                                                                      |

The `nft-sync` one matters most: `trackCollection` is called on every collection page load and search/discovery response, and every caller wraps it in `.catch(() => undefined)` so the page never fails. So full collection syncs have not been enqueued from the API at all, silently — which is a large part of why stats sat empty and collections stayed `queued`/`failed`.

All six now compose ids through `queueJobId()` in `@trover/core`, which joins with `-` and strips anything outside `[A-Za-z0-9_-]`. `trackCollection` also logs an enqueue failure before rethrowing, so a future regression is visible instead of silent.

## /nfts/discover 404 on dex.trover.tech

The middleware rewrote **every** `/nfts/*` path to `/dex/nfts/*` on the dex host, but `/dex/nfts` contains only `[chain]/[slug]` — the trading terminal is the only page with a dex variant. So `/nfts/discover` was rewritten to a route that does not exist. The rewrite is now scoped to collection pages, so Discover (and any future `/nfts/*` page) is served normally.

## Not ours

```
RangeError: Maximum call stack size exceeded
  at injectLeap.js … at proxy-injected-providers.js
```

Both files are browser wallet extensions — Leap, and a multi-wallet proxy shim — recursing on each other while wrapping `window.ethereum`. Nothing in the app appears in that stack. Disabling one of the competing wallet extensions stops it; it does not affect Trover's own signing paths.

## Deploy

**No migration.** api + worker changed; the middleware and Discover fix ship with the Vercel build on push.

```bash
cd ~/trover
git pull --ff-only origin main
docker compose -f docker-compose.yml -f docker-compose.production.yml build api worker
docker compose -f docker-compose.yml -f docker-compose.production.yml up -d --force-recreate api worker
```

## Verify

```bash
# 1. The resync that 500'd:
curl -i -X POST "https://api.trover.tech/v1/nft-market/collections/proofmachines/resync?chain=robinhood"
#    expect 202 {"queued":true,...}

# 2. Collection syncs are actually enqueued now — hit a collection, then watch:
curl -s "https://api.trover.tech/v1/nft-market/collections/proofmachines?chain=robinhood" > /dev/null
docker compose logs --tail=200 worker | grep -i "nft market sync\|Failed to enqueue"
#    "Failed to enqueue NFT collection sync" should NOT appear.

# 3. Discover on the dex host (this returned 404):
curl -sL -o /dev/null -w "%{http_code} %{url_effective}\n" "https://dex.trover.tech/nfts/discover"
#    expect 200; before the fix it followed to /ru/nfts/discover and 404'd.

# 4. NFT quote no longer 500s (needs a session):
#    open a collection terminal and request a buy quote.
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.trover.tech/engineering/verify-2026-08-31-jobid.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
