Skip to content

v1.3.34.2 -- Telegram channels: legacy-default migration + diagnostic CLI

A continuation of the Telegram-notifications fix chain. v1.3.34.1 changed the default template + parse_mode but did NOT migrate channels that were already created with the old default persisted into their template column or parse_mode config key. v1.3.34.2 ships that migration and adds an argos channel inspect CLI for operators who need to verify channel state without sqlite3 or panel-API auth.

Why

Operator dogfood after v1.3.34.1 reported the SAME error post-deploy:

telegram 400: Bad Request: can't parse entities:
 Can't find end of Bold entity at byte offset 21

Byte offset 21 is exactly where the closing * of *config_change* lands in the OLD MarkdownV2-default-rendered output: [INFO] *config_change* -- which proved the worker was still rendering the old body. PHASE 0 diagnostic confirmed the v1.3.34.1 binary was running (the new escapeHTML symbol was in the deployed binary), so the bug was elsewhere. PHASE 1 diagnostic via the new argos channel inspect revealed: the operator's pre-existing Telegram channel had the old MarkdownV2 default body persisted in its template column at create time. Render(ch.Template, ...) falls back to DefaultTemplate(...) only when the stored template is empty; a stored non-empty value (even if it's a literal copy of the old default) wins. v1.3.34.1's code change could only help NEW channels.

Same shape on the parse_mode config key: any value set at create time bypassed v1.3.34.1's empty-parse_mode-fallback logic.

This is strike #10 in the upstream-behaviour pattern, the first one not about a third-party API surface but about panel-internal state: when a default-shape value gets persisted at entity-create time, a later code change to the default does NOT automatically apply to existing rows. The fix has to clear those rows so the empty-fallback path can re-route them.

What ships

Boot-time auto-migration

backend/internal/notifications/migrate_legacy.go (new): MigrateLegacyTelegramChannels runs at panel boot, after schema migrations and before HTTP serving begins. For each type='telegram' row:

  • If template is byte-equal to the new exported constant LegacyTelegramDefaultTemplate (the pre-v1.3.34.1 default literal), the column is set to ''.
  • If config.parse_mode == "MarkdownV2", the key is removed from the config JSON blob (the encrypted bot_token and other fields are preserved -- only the unencrypted parse_mode key is deleted).

Customised templates (one byte different from the legacy literal) are left untouched. The migration is idempotent: a second run on a clean DB touches zero rows. Boot logs a single INFO line:

notifications: legacy Telegram migration complete
  channels_scanned=N templates_cleared=M parse_modes_cleared=K

Wired in backend/cmd/argos/main.go immediately before the VAPID-key bootstrap, so it runs on every panel start.

Seven new unit tests

backend/internal/notifications/migrate_legacy_test.go:

  • TestMigrateClearsExactMatchTemplate -- happy path.
  • TestMigrateClearsParseModeMarkdownV2 -- second surface.
  • TestMigrateLeavesCustomisedTemplateAlone -- the safety guarantee: LegacyTelegramDefaultTemplate + " " is left untouched.
  • TestMigrateLeavesParseModeHTMLAlone -- non-target parse_mode values are not re-mutated.
  • TestMigrateIsIdempotent -- second run is a no-op.
  • TestMigrateIgnoresNonTelegramChannels -- WHERE type='telegram' scope respected.
  • TestMigrateBothSurfacesOnSameChannel -- worst-case row with both stale template AND pinned parse_mode counts as 2 surfaces touched.

All seven pass. Combined with the v1.3.34.1 tests, the notifications package now has 12 passing tests covering the HTML render path, escape functions, MarkdownV2 regression, sender form-body shape, error-message wrapping, and the auto-migration.

argos channel inspect CLI subcommand

backend/cmd/argos/cli_channel.go (new). Reusable diagnostic:

docker compose exec argos /argos channel inspect --type telegram
docker compose exec argos /argos channel inspect             # all types

Prints id, name, enabled, rate_limit_per_minute, the JSON-quoted template (newlines visible as \n), and the config keys with secret fields replaced by ***REDACTED*** (using the same per-type secret-key set as notifications.secretFields()). For Telegram channels two diagnostic annotations are added:

  • template-state: one of empty / LEGACY / customised, so the operator can verify whether the auto-migration would touch the row.
  • parse_mode-state: unset / pinned to MarkdownV2 / pinned to HTML / pinned to <custom>.

Same env contract as the other CLI subcommands: requires ARGOS_DB_PATH (or --db <path>).

Version-string bump

backend/cmd/argos/main.go argosVersion and frontend/package.json version both move from 1.3.33 to 1.3.34.2. v1.3.34 (doc-only) and v1.3.34.1 (code change but deliberately frozen) left a hole: argos --version reported 1.3.33 for all three releases, so the operator could not distinguish between an un-redeployed v1.3.34 panel and a freshly-rebuilt v1.3.34.1 panel. v1.3.34.2 closes that hole permanently. Future rule: never freeze argosVersion when Go source changes -- only freeze for tag-without-rebuild releases like v1.3.27.1 / v1.3.34.

Documentation

docs/features/notifications.md (telegram section):

  • New "Auto-migration of pre-v1.3.34.1 channels (v1.3.34.2+)" subsection documenting the boot scan, the byte-exact match semantics, the boot-log line operators should see, and the manual UI-edit fallback for customised templates.
  • New "Diagnosing channel state without sqlite3" subsection documenting the argos channel inspect CLI subcommand with example output annotations.

scripts/check-no-personal-data.sh clean.

Smoke gate

The smoke for this release is operator-mediated against the production stack:

  1. git pull, make sync-prod, make deploy-prod.
  2. Confirm version bumped:
    docker compose exec argos /argos --help | head -1
    # expected: "argos 1.3.34.2 -- self-hosted edge gateway"
    
  3. Confirm migration ran on boot:
    docker compose logs argos | grep "legacy Telegram migration"
    # expected: one line with channels_scanned=N templates_cleared>=1
    
  4. Confirm channel state via the new CLI:
    docker compose exec argos /argos channel inspect --type telegram
    # expected for the operator's existing channel:
    #   template-state: empty
    #   parse_mode-state: unset
    
  5. Click "Send test" on the existing channel via the panel UI. Verify Telegram receives the test message with bold event-type visible, no 400 error, and the notification_deliveries audit row carries status='sent'.
  6. (Recommended) Trigger a real config_change event (e.g. edit any host) and verify the Telegram channel receives that too.

The unit-tested mock-server smoke (the seven migration tests plus the v1.3.34.1 sender tests) verifies the request-path shape; the operator-mediated smoke is the EFFECT gate.

The release is NOT tagged until step 5 confirms.

Files changed

  • backend/internal/notifications/templates.go (LegacyTelegramDefaultTemplate constant added)
  • backend/internal/notifications/migrate_legacy.go (new)
  • backend/internal/notifications/migrate_legacy_test.go (new)
  • backend/cmd/argos/main.go (migration call wired into boot; argosVersion bumped to 1.3.34.2; help banner mentions the new channel inspect subcommand)
  • backend/cmd/argos/cli_channel.go (new)
  • frontend/package.json (version bumped to 1.3.34.2)
  • docs/features/notifications.md (auto-migration + CLI subsections)
  • docs/release-notes/v1.3.34.2.md (this file)
  • CHANGELOG.md, mkdocs.yml

NOT changed: any frontend behaviour; any DB schema (the migration mutates rows but adds no columns); any smokes under scripts/smoke/; the v1.3.34.1 sender / template behaviour for new channels.

Upgrade

cd ~/argos-edge
git pull
make sync-prod
make deploy-prod        # binary rebuild required (Go source
                        # changes in templates.go +
                        # migrate_legacy.go + cli_channel.go +
                        # main.go)

After deploy-prod finishes, run the smoke-gate steps above.

For operators with an existing Telegram channel that has been silently failing on every notification since v1.3.21: the auto-migration runs on the first boot of the new binary; from that moment forward the channel renders with the v1.3.34.1 HTML default and Telegram accepts the messages. No manual channel reconfiguration is required for byte-exact-default rows. Operators with customised MarkdownV2 templates need to edit the template field manually in the panel UI (clear it to adopt the new default, or update it to use HTML syntax with escapeHTML).

Tenth-strike entry in the upstream-behaviour pattern

The nine-strike pattern documented in memory/project_four_strike_upstream_pattern.md becomes ten with this release. The new strike's lesson:

When changing a default-shape value that gets persisted at entity-create time, ALWAYS pair the code change with a migration that detects + clears exact-match legacy values and leaves customised values alone.

This applies prospectively to any future change of: webhook default body_template, email subject_template, browser_push payload shape, rule throttle defaults, host TLS-mode defaults, or anything else the panel writes a "current default" into at row-create time.