Releases: FreshLabDev/branchy
Release list
v1.1.0
Stable Telegram Bot API 10.2 release. Promoted from v1.1.0-alpha.3 after a
40-hour error-free production soak and live release-notification tests covering
mixed media, the 50-media boundary, unsafe input sanitizing, and rejected-media
fallback.
Added
- Private group
/start. The group-scoped command is ephemeral: Branchy's
DM prompt is visible only to the invoking user. Registration retries after
transient Telegram failures without delaying HTTP startup or long polling.
Changed
- GitHub PR and release bodies are rendered as sanitized Bot API Rich HTML.
GFM headings, lists, tables, quotes, links, code, task lists, and safe inline
HTML remain available. - Up to 50 valid HTTP(S) images, videos, or audio items remain visible as Rich
Message blocks. Overflow media remains reachable as links. - New outbox jobs retain both preferred Rich HTML and bounded classic HTML, so
content failures and rollback paths do not lose the notification.
Fixed
- Rich output is structurally bounded by message size, block count, nesting
depth, media count, and table width, with balanced HTML after truncation. - Telegram content or method errors fall back through Rich HTML without media,
classic HTML, and plain text. Temporary transport, server, and rate-limit
failures keep normal retry behavior without risking duplicate fallback sends. - Notification headers preserve the visual line break between repository name
and event details. - Ephemeral group replies are sent before the best-effort
core.touch, so a
slow database cannot consume Telegram's reply window or cause duplicates.
Security
- Unsafe tags, Telegram-specific tags, event attributes, mentions, malformed
structures, and non-HTTP(S) URL schemes are removed before delivery. - Build and CI toolchains require Go 1.26.5, including the standard-library fix
forGO-2026-5856incrypto/tls.
Migrations
008_notification_job_payload_format.sqladdsrich_textand versioned
payload metadata. Existing alpha jobs remain identifiable and are sanitized
before delivery; rollback-era workers continue using classictext.
Operations
- No new environment variables, OAuth scopes, or webhook-event requirements.
AUTO_MIGRATE=trueapplies migration008during upgrade fromv1.0.3.- WS04 production Compose no longer declares the retired standalone
branchy-postgresservice; sharedcore-postgresremains the only durable
store. Historical volume data was preserved for rollback.
v1.1.0-alpha.3
Added
- Private group
/start. The group-scoped command now uses Bot API 10.2
ephemeral commands and replies only to the invoking user with the DM prompt.
Branchy no longer posts a public group fallback for/start. Command scope
registration and bot username discovery retry after transient Telegram
failures without delaying HTTP startup or long polling.
Changed
- GitHub bodies are rendered to safe Rich HTML inside Branchy. GFM
headings, lists, tables, quotes, links, code, task lists, and media remain
available. Safe inline GFM HTML such as underline, superscript, and subscript
is retained, while unsafe or Telegram-specific tags, attributes, and URL
schemes are removed before Telegram sees the payload. - Up to 50 valid HTTP(S) images, videos, or audio items remain visible as Rich
Message blocks, including linked Markdown images and supported raw
<img>/<video>/<audio>media. Media beyond the Telegram limit remains
available as links.
Fixed
- Rich content is truncated structurally with balanced HTML and explicit caps
for message size, block count, nesting depth, media, and table columns. - A final HTML5 bounds pass no longer leaks implicit
<tbody>,<thead>, or
<tfoot>tags that Telegram Rich HTML does not support. - Telegram content or method errors now fall back through Rich HTML without
media, bounded classic HTML, and plain text. Every stage receives a fresh
timeout; rate limits and temporary transport/server failures still retry
without risking duplicate fallback sends. - Ephemeral group
/startreplies are sent before the best-effortcore.touch,
so a slow or unavailable database cannot consume Telegram's 15-second reply
window or trigger duplicate private replies.
Security
- PR authors and release body contributors can no longer inject
Telegram-specific rich tags, unsafe links, mentions, or malformed structures
through webhook body Markdown. - Build and CI toolchains now require Go 1.26.5, which includes the standard
library fix forGO-2026-5856incrypto/tls.
Migrations
008_notification_job_payload_format.sqladds versioned payload metadata and
rich_text. Existingv1.1.0-alpha.1/alpha.2rows remain identifiable as
Rich Markdown but are converted through the safe Rich HTML sanitizer before
delivery. New jobs keep classic HTML intextfor fallback and rollback.
v1.1.0-alpha.2
Fixed
- Notification headers keep a line break under the repo name. Rich Markdown
collapses a single\n, so the event line (Release · …, commit summary,
PR action) sat on the same visual row as📦 repo. Headers now use a blank
line between the title row and the event row.
v1.1.0-alpha.1
Changed
- GitHub notifications use Telegram Rich Messages. Delivery switched from
classic HTMLsendMessageto Bot APIsendRichMessage(Rich Markdown).
Headers still use escaped HTML tags; PR and release bodies are passed as
GitHub Flavored Markdown so Telegram natively renders headings, lists, tables,
quotes, code, and media (). - Long bodies use
<details>. Short notes stay Markdown blockquotes; long
or truncated notes collapse under a collapsible details summary instead of
classic<blockquote expandable>. - Soft body caps raised for the rich-message limit (PR ~2500 runes, release
~10000 runes) while keeping group notifications scannable. - Bot UI (
/start, settings, OAuth replies) is unchanged and still uses HTML
parse mode.
Breaking
- Hard cutover for the outbox payload format.
notification_jobs.textis
now Rich Markdown. Pending jobs enqueued as classic HTML before this deploy
can fail permanently; drain or fail pending rows before upgrading.
Operations
- No database migration.
- Ensure the bot can send media in destination groups if release notes include
remote images (Telegram may embed media blocks from Markdown).
v1.0.3
Fixed
- Local
docker-composestack could not boot after the core consolidation.
Migration001now references the sharedcore.person/core.chat, but the
bundled dev Postgres had nocoreschema, sodocker-compose upcrash-looped
(schema "core" does not exist). Addeddeploy/core-init.sql(a minimal local
coreschema +core.touch) mounted viadocker-entrypoint-initdb.d, and set
search_path=branchyon the composeDATABASE_URL. Production is unaffected
(it uses the real shared core-postgres); this only fixes local development.
v1.0.2
Changed
- Shared
coredatabase. Branchy's data now lives in the shared
core-postgresunder abranchyschema, and identity/presence (Telegram
users and chats) are delegated to the centralcorehub (core.person/
core.chat), keyed on the global Telegram id. Domain tables reference
core.person(telegram_user_id)directly; the localtelegram_usersand
telegram_chatstables are dropped. Chat-specific state (bot status, active,
added-by) moves to a smallchat_statetable that FKs intocore.chat.
Identity/presence is upserted via the sharedcore.touchbefore any dependent
insert. The bot connects to core-postgres withsearch_path=branchy; its
dedicatedbranchy-postgresis retired.
v1.0.1
Fixed
- In a group, branchy no longer answers a
/startthat was addressed to a
different bot (/start@otherbot, which Telegram delivers to every bot in the
group). It now responds only to the bare/startor its own
/start@<botname>.
v1.0.0
First stable release. Branchy watches your GitHub repositories and delivers
push, pull-request and release notifications to Telegram — set up and managed
entirely through the bot, with GitHub OAuth, per-event and branch filters, and
reliable at-least-once delivery through a Postgres outbox.
Promoted from v1.0.0-rc.1 unchanged, after a clean production soak (no errors,
a real release notification delivered end-to-end). This tag marks the 1.0
milestone after a hardening program across the alpha series and two independent
review passes.
v1.0.0-rc.1
Release candidate. A second independent review pass closed the last two
lost-notification gaps in the delivery path; everything else was clean.
Reliability
- Release and pull-request notification bodies are now budgeted by their
visible rendered length, not the raw Markdown slice. Rendering can expand
the visible text (a Markdown image becomes anImage:prefix), so an
image-heavy body could previously exceed Telegram's 4096-character limit and
be rejected outright — silently losing the notification with no retry. The
body is re-rendered to fit, and titles are bounded. - Subscriptions are matched to incoming webhooks by the stable
github_repo_idinstead of the repository's full name. A GitHub repo
rename no longer silently drops every notification for that repo (the hook
keeps firing under the same id with a new name); the cached name is refreshed.
Fixed
/start@botname— the form Telegram delivers in groups — is now recognized,
so the "open me in a DM" prompt fires there.- A fatal startup error (e.g. the HTTP port already in use) is no longer masked
as a clean exit when a shutdown signal arrives at the same instant. - A notification job that exhausts its attempts purely through worker
lease-expiry is now logged instead of being a silent terminal drop.
v1.0.0-alpha.4
A multi-dimension review pass: two delivery-path correctness blockers closed
plus a cluster of latent outbox/idempotency hardening, and a round of
notification and UX polish.
Security
- The webhook rate limiter now runs after signature verification, so an
unsigned flood can no longer drain the shared token bucket and return 429 to
real GitHub deliveries. Unauthenticated traffic is rejected as 401 without
touching the limiter.
Reliability
- Recording a delivery and enqueuing its notification jobs is now a single
atomic transaction. Previously a crash between the two could leave an
idempotency marker with no jobs, so a GitHub retry was ignored as a duplicate
and the notification was lost. - Notification job terminal/retry updates are fenced on
status = 'processing';
a stale write from a re-leased job is a safe no-op instead of overwriting a
job another worker already finalized. - A successful send is recorded under an uncancelable context, so a graceful
shutdown immediately after delivery no longer leaves the jobprocessingand
re-sends it (a duplicate) on restart. - A group→supergroup upgrade now auto-pauses the subscription instead of
enqueuing jobs to the dead chat id forever (migrate_to_chat_idis detected). - A failed Telegram update is re-delivered (the poll offset is not advanced)
with a short backoff instead of being silently dropped; a persistently failing
update is given up on after a few attempts so it cannot stall the poll loop. - Migrations take a session-level advisory lock, so two instances starting
together cannot double-run a non-idempotent migration. PUBLIC_BASE_URLis validated as an absolute http(s) URL at startup instead of
failing far downstream on a malformed value.
Changed
- Push notifications show as many commits as fit a text budget instead of a
fixed cap of 10; since GitHub caps a push payload at ~20 commits, real pushes
now list every commit, with "+N more" only for a pathologically long list. - PR and release bodies collapse into an expandable quote only when long
(over ~600 visible characters or ~10 lines, or truncated); short and medium
notes render in full in a plain quote. - Re-subscribing to a previously auto-paused configuration clears the stale
pause_reason, so the subscription is no longer shown active with a pause
warning.
Fixed
- Deleting a subscription now asks for confirmation first, so a single mistaken
tap can no longer destroy a subscription and its webhook. - The bot registers its command menu (
setMyCommands) and replies to
unrecognized private-chat input with a hint, instead of staying silent and
reading as a dead bot. - A failed test notification keeps the subscription screen (surfaced as a toast)
instead of ejecting the user to the home menu. - Markdown inside a GitHub blockquote (links, emphasis, inline code) renders as
formatted text instead of raw source. - A declined GitHub OAuth consent surfaces the real reason instead of a
misleading "missing code or state".