Conversation
The whitelist loader (core/taskengine/token_metadata.go) keys cache entries on
TokenMetadata.Id (json:"id") but the legacy files used "address", so every
entry parsed with Id="" and overwrote a single cache slot — only 1 of N
tokens actually became reachable. Rebuilt the four chain catalogs from
studio's app/lib/erc20 (the authoritative UI source), keeping just
{id, name, symbol, decimals} and dropping incomplete or duplicate placeholders.
- ethereum.json: 99 -> 30 entries, all complete
- base.json: 134 -> 23 entries, all complete
- base-sepolia.json: 19 -> 4 entries, all complete (incl. real Circle USDC w/ decimals: 6)
- sepolia.json: 6 -> 6 entries, unchanged
Token enrichment now returns proper name/symbol/decimals for whitelisted
tokens — Base Sepolia balance lookups will no longer surface USDC as
"Unknown Token / UNKNOWN".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a workflow ends with status=success but a branch condition skipped a node, the channel formatters were unconditionally rendering the skip description under "What Went Wrong" — misleading because nothing actually went wrong. The deterministic plain-text body composer already gates on status=="failed"; the channel formatters now match that semantic by switching the heading to "Skipped Steps" when status=="success", and keeping "What Went Wrong" for failed/error runs. Applies to all four formatters (telegram, discord, plaintext, email). Test updated to assert "Skipped Steps" / no "What Went Wrong" for the success-with-branch-skipped case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code ReviewOverviewTwo independent bug fixes:
Both fixes are correct and clearly motivated. Token Whitelist FixAddress case normalization is safe. Malformed placeholder addresses correctly removed. Minor inconsistency: WETH name in // ethereum.json — should probably be:
{
"name": "Wrapped Ether", // not "WETH"
"symbol": "WETH",
"decimals": 18,
"id": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
}
Notification Heading FixLogic is correct and applied consistently. No XSS risk in the email formatter. sb.WriteString(fmt.Sprintf(`<h3 style="...">%s</h3>`, heading))
Test gap: Discord, Plaintext, and Email formatters are not tested for this behaviour. Minor style note. // instead of:
sb.WriteString(fmt.Sprintf("\n**%s:**\n", heading))
// could be:
sb.WriteString("\n**" + heading + ":**\n")
Summary
The two bugs are real and the fixes are sound. The main follow-up worth doing is adding at least one test case per untested formatter for the |
There was a problem hiding this comment.
Pull request overview
This PR fixes token whitelist loading by rebuilding chain token catalogs to use the id field expected by TokenMetadata.Id, and updates notification channel formatters to avoid “What Went Wrong” framing on successful runs where steps were only skipped by branch conditions.
Changes:
- Rebuilt token whitelist JSON catalogs (Ethereum/Base/Sepolia/Base Sepolia) to use
{id, name, symbol, decimals}so whitelist cache keys are populated correctly. - Updated Telegram/Discord/plaintext/email structured-summary formatters to render the skipped-steps section under “Skipped Steps” when
status=="success". - Updated Telegram formatter test expectations for the success-with-branch-skipped scenario.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| token_whitelist/sepolia.json | Migrates entries from address to id and refreshes token set ordering/content. |
| token_whitelist/ethereum.json | Rebuilds mainnet token catalog to the {id,name,symbol,decimals} schema used by the loader. |
| token_whitelist/base.json | Rebuilds Base token catalog to the {id,name,symbol,decimals} schema used by the loader. |
| token_whitelist/base-sepolia.json | Rebuilds Base Sepolia token catalog to the {id,name,symbol,decimals} schema used by the loader. |
| core/taskengine/summarizer_format_test.go | Adjusts Telegram formatter test to expect “Skipped Steps” and to forbid “What Went Wrong” on success-with-skips. |
| core/taskengine/summarizer_format_telegram.go | Switches the errors-section heading based on Summary.Status. |
| core/taskengine/summarizer_format_plaintext.go | Switches the errors-section heading based on Summary.Status (adds fmt import). |
| core/taskengine/summarizer_format_email.go | Switches the errors-section heading in analysisHtml based on Summary.Status. |
| core/taskengine/summarizer_format_discord.go | Switches the errors-section heading based on Summary.Status (adds fmt import). |
Comments suppressed due to low confidence (1)
core/taskengine/summarizer_format_email.go:311
- In the email HTML renderer, when Status=="success" the section heading becomes "Skipped Steps", but each entry is still prefixed with a "✗" marker. That marker reads like a failure indicator and undermines the intent of the relabel. Consider switching the per-line glyph based on status (e.g., use a neutral bullet/skip icon when status is success) so success-with-skips doesn’t look like an error list.
heading := "What Went Wrong"
if s.Status == "success" {
heading = "Skipped Steps"
}
sb.WriteString(`<div style="margin-bottom: 20px;">`)
sb.WriteString(fmt.Sprintf(`<h3 style="margin: 0 0 8px 0; font-size: 16px;">%s</h3>`, heading))
for _, err := range s.Errors {
sb.WriteString("<p style=\"margin: 0 0 4px 0;\">✗ ")
sb.WriteString(formatBackticksForChannel(err, "email"))
sb.WriteString("</p>")
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Errors section — heading depends on status. On a successful run the only | ||
| // entries here are branch-skipped nodes, so use "Skipped Steps" instead. | ||
| if len(s.Errors) > 0 { | ||
| sb.WriteString("\n**What Went Wrong:**\n") | ||
| heading := "What Went Wrong" | ||
| if s.Status == "success" { | ||
| heading = "Skipped Steps" | ||
| } | ||
| sb.WriteString(fmt.Sprintf("\n**%s:**\n", heading)) | ||
| for _, err := range s.Errors { |
| // Errors section — heading depends on status. On a successful run the only | ||
| // entries here are branch-skipped nodes, so use "Skipped Steps" instead. | ||
| if len(s.Errors) > 0 { | ||
| sb.WriteString("\nWhat Went Wrong:\n") | ||
| heading := "What Went Wrong" | ||
| if s.Status == "success" { | ||
| heading = "Skipped Steps" | ||
| } | ||
| sb.WriteString(fmt.Sprintf("\n%s:\n", heading)) |
Reverts 404b882. The original framing was correct after all: when a workflow ends with status=success but the main on-chain logic was skipped by a branch condition (e.g. a balance gate caused a transfer loop to be bypassed), the workflow did NOT deliver its intended outcome. Surfacing those skips under "What Went Wrong" is the right signal to the user, even though no error was thrown. Restores the unconditional "What Went Wrong" heading in all four channel formatters (telegram, discord, plaintext, email) and reverts the test assertion accordingly. No helper extraction is kept since the logic collapses back to a single literal heading per channel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the descriptive-name convention used by every other catalog entry (and every other chain file). Symbol stays "WETH". Same change applied upstream in studio/app/lib/erc20/ethereum.json. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The legacy token_whitelist/ethereum.json had 99 fully-complete entries (all four fields populated) — only the field key was wrong (address vs id), so the wholesale replacement with studio's 30-entry catalog dropped 74 valid tokens (BNB, TONCOIN, USDe, weETH, NEAR, OKB, BUIDL, ONDO, WLD, FDUSD, etc.). Re-merge those 74 legacy-only entries (by lowercased id, studio wins on overlap) so the whitelist still recognises them — they short-circuit the ERC-20 RPC fallback in TokenEnrichmentService.GetTokenMetadata for balance lookups. Net counts after merge: - ethereum.json: 30 -> 104 entries (+74 legacy-only) - sepolia.json: 6 -> 6 (full overlap with studio, no change) - base.json: 23 (unchanged — legacy entries lacked decimals) - base-sepolia.json: 4 (unchanged — same) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LoadWhitelist used to silently accept any token entry. The original "address" → "id" field-key bug went undetected because every entry parsed with id="" and overwrote a single cache slot — visible only later when balance lookups returned "Unknown Token / UNKNOWN". Surface two classes of issue at startup so future regressions show up before they degrade balance lookups: - Empty id → log Warn, skip the entry (don't pollute cache[""]). - decimals=0 → log Warn (still cached, since some tokens may legitimately have 0 decimals; but most will be a missing-field bug). Loader now also reports loaded/skipped counts in the debug summary so sync issues are visible without grepping for individual warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The whitelist loader (core/taskengine/token_metadata.go) keys cache entries on
TokenMetadata.Id (json:"id") but the legacy files used "address", so every
entry parsed with Id="" and overwrote a single cache slot — only 1 of N
tokens actually became reachable. Rebuilt the four chain catalogs from
studio's app/lib/erc20 (the authoritative UI source), keeping just
{id, name, symbol, decimals} and dropping incomplete or duplicate placeholders.
Token enrichment now returns proper name/symbol/decimals for whitelisted
tokens — Base Sepolia balance lookups will no longer surface USDC as
"Unknown Token / UNKNOWN".
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
When a workflow ends with status=success but a branch condition skipped a
node, the channel formatters were unconditionally rendering the skip
description under "What Went Wrong" — misleading because nothing actually
went wrong. The deterministic plain-text body composer already gates on
status=="failed"; the channel formatters now match that semantic by
switching the heading to "Skipped Steps" when status=="success", and
keeping "What Went Wrong" for failed/error runs.
Applies to all four formatters (telegram, discord, plaintext, email).
Test updated to assert "Skipped Steps" / no "What Went Wrong" for the
success-with-branch-skipped case.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com