diff --git a/core/taskengine/summarizer_format_email.go b/core/taskengine/summarizer_format_email.go
index ba9278f6..f83d6ef3 100644
--- a/core/taskengine/summarizer_format_email.go
+++ b/core/taskengine/summarizer_format_email.go
@@ -335,7 +335,7 @@ func buildFeesSectionHTML(s Summary) string {
if s.Workflow != nil && s.Workflow.IsSimulation {
// Heading omitted — the placeholder line carries enough context on its own.
return `
` +
- `
⛽ (cost estimated at deploy)
` +
+ `
⛽ (see cost estimate before deploy)
` +
`
`
}
diff --git a/core/taskengine/summarizer_format_telegram.go b/core/taskengine/summarizer_format_telegram.go
index 48e7e14e..034b7afb 100644
--- a/core/taskengine/summarizer_format_telegram.go
+++ b/core/taskengine/summarizer_format_telegram.go
@@ -250,11 +250,11 @@ func formatSubjectWithBoldName(subject string) string {
// Format: "⛽ Cost: 0.000003 ETH ($0.01), 1.2 USDC ($1.20)" — native
// token first, comma-separated, USD parenthetical per token. Unpriceable
// tokens render as "$?". For simulations the line collapses to the static
-// "⛽ (cost estimated at deploy)" placeholder. Returns "" when there's
+// "⛽ (see cost estimate before deploy)" placeholder. Returns "" when there's
// nothing to render.
func formatTelegramCostLine(s Summary) string {
if s.Workflow != nil && s.Workflow.IsSimulation {
- return "⛽ (cost estimated at deploy)\n"
+ return "⛽ (see cost estimate before deploy)\n"
}
if s.Fees == nil || len(s.Fees.Total) == 0 {
return ""
diff --git a/core/taskengine/summarizer_format_test.go b/core/taskengine/summarizer_format_test.go
index cf3007e9..74de99d2 100644
--- a/core/taskengine/summarizer_format_test.go
+++ b/core/taskengine/summarizer_format_test.go
@@ -1069,7 +1069,7 @@ func TestFormatTelegramFromStructured_RunnerAndFees(t *testing.T) {
if strings.Contains(out, "(~") || strings.Contains(out, "Value fee:") {
t.Errorf("Telegram should not render gas-units detail or Value fee line, got:\n%s", out)
}
- if strings.Contains(out, "(cost estimated at deploy)") {
+ if strings.Contains(out, "(see cost estimate before deploy)") {
t.Errorf("deployed run should not show simulation placeholder, got:\n%s", out)
}
@@ -1290,7 +1290,7 @@ func mustBigInt(s string) *big.Int {
}
// TestFormatTelegramFromStructured_Simulation_PlaceholderCost confirms simulation
-// runs render the "(cost estimated at deploy)" placeholder instead of fake-precision
+// runs render the "(see cost estimate before deploy)" placeholder instead of fake-precision
// numbers. Sim gas prices are conservative chain defaults, so any specific ETH/gas
// figure would mislead the user.
func TestFormatTelegramFromStructured_Simulation_PlaceholderCost(t *testing.T) {
@@ -1318,7 +1318,7 @@ func TestFormatTelegramFromStructured_Simulation_PlaceholderCost(t *testing.T) {
out := FormatForMessageChannels(summary, "telegram", nil)
- if !strings.Contains(out, "⛽ (cost estimated at deploy)") {
+ if !strings.Contains(out, "⛽ (see cost estimate before deploy)") {
t.Errorf("simulation should render the deploy-time placeholder, got:\n%s", out)
}
for _, banned := range []string{"Cost:", "0.0000105 ETH", "21 K gas", "platform fee"} {
@@ -1729,7 +1729,7 @@ func TestComposeSummary_SimulateTaskFromClientPayload(t *testing.T) {
}
// The simulation Cost placeholder uses italic text; allow that but no other italics.
- if strings.Contains(telegram, "") && !strings.Contains(telegram, "(cost estimated at deploy)") {
+ if strings.Contains(telegram, "") && !strings.Contains(telegram, "(see cost estimate before deploy)") {
t.Errorf("Telegram should not contain italic annotation other than the cost placeholder, got:\n%s", telegram)
}
diff --git a/docs/changes/20260502-notification-cost-line-and-runner.md b/docs/changes/20260502-notification-cost-line-and-runner.md
index e24fee28..0897080c 100644
--- a/docs/changes/20260502-notification-cost-line-and-runner.md
+++ b/docs/changes/20260502-notification-cost-line-and-runner.md
@@ -45,7 +45,7 @@ Single-line, multi-token, native unit first with USD parenthetical per token:
| ETH gas + USDC value-fee | `⛽ Cost: 0.000003 ETH ($0.01), 1.2 USDC ($1.20)` |
| Unpriceable ERC20 | `⛽ Cost: 0.000003 ETH ($0.01), 0.005 PEPE ($?)` |
| Read-only deployed (no on-chain steps) | (line omitted) |
-| Simulation | `⛽ (cost estimated at deploy)` — static, no numbers |
+| Simulation | `⛽ (see cost estimate before deploy)` — static, no numbers |
Telegram drops bullets, gas-units, and value-fee detail; email matches. Breakdowns live on the dashboard. Simulation collapses to the placeholder because sim gas prices are conservative chain defaults rather than real network conditions — any specific number would mislead.
@@ -83,7 +83,7 @@ To make simulation cogs[] non-empty in the first place, the Tenderly client now
- `TestPercentOfRaw` — value-fee percentage math against raw amounts.
- `TestTokenBucketToTokenTotal` — stablecoin shortcut, zero-rounding omission, missing price service path.
- `TestFormatTelegramFromStructured_RunnerAndFees` and `TestFormatTelegramFromStructured_MultiToken_USDPlaceholder` — end-to-end render assertions.
-- Production verification: the next deployed run on Sepolia after `cba1ac8` showed the Telegram block ordering with Runner-and-Network folded onto one line; the simulation summary correctly rendered `⛽ (cost estimated at deploy)` after `4f784c9` (`vm.IsSimulation` flowing through to the formatter).
+- Production verification: the next deployed run on Sepolia after `cba1ac8` showed the Telegram block ordering with Runner-and-Network folded onto one line; the simulation summary correctly rendered `⛽ (see cost estimate before deploy)` after `4f784c9` (`vm.IsSimulation` flowing through to the formatter).
- The aggregator's existing tests pass with the price service possibly nil: executor (`executor.go:321,478`), fee estimator (`fee_estimator.go:195`), and the new `buildTotalsFromVM` all guard against `nil` rather than panicking.
## Cross-repo coordination