From 6167a53bbe076fc8117e2a4e35982a60321cb075 Mon Sep 17 00:00:00 2001 From: SYNITY Date: Mon, 13 Jul 2026 08:25:59 +0700 Subject: [PATCH] fix(bitrix24): mount shared webhook route independent of bot readiness [B24:2794] (#1432) /bitrix24/* only mounted when a bitrix24 Channel loaded successfully via WebhookHandlers(), which requires portal + bot_code + bot_name already set. But completing portal OAuth requires /bitrix24/install to be reachable, and the UI won't let you pick an uninstalled portal when creating a bot -- deadlock on any fresh deployment: no bot can load until OAuth completes, OAuth can't complete without the route mounted. Claim and mount the shared bitrix24.WebhookRouter() singleton directly at boot, independent of whether any Channel loaded. ClaimWebhookRoute is idempotent (first-claim-wins via CompareAndSwap), so this is a no-op once a real Channel has already claimed the route. Co-authored-by: DangTinh311 --- cmd/gateway_lifecycle.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmd/gateway_lifecycle.go b/cmd/gateway_lifecycle.go index 1cc97ee8..1a3a37b8 100644 --- a/cmd/gateway_lifecycle.go +++ b/cmd/gateway_lifecycle.go @@ -10,6 +10,7 @@ import ( "github.com/nextlevelbuilder/goclaw/internal/bus" "github.com/nextlevelbuilder/goclaw/internal/cache" "github.com/nextlevelbuilder/goclaw/internal/channels" + "github.com/nextlevelbuilder/goclaw/internal/channels/bitrix24" "github.com/nextlevelbuilder/goclaw/internal/config" "github.com/nextlevelbuilder/goclaw/internal/edition" "github.com/nextlevelbuilder/goclaw/internal/heartbeat" @@ -259,6 +260,21 @@ func (d *gatewayDeps) runLifecycle( slog.Info("webhook route mounted on gateway", "path", route.Path) } + // Bitrix24: also claim+mount the shared webhook router directly, even if + // no channel_instances row has finished setup yet (bot_code/bot_name + // still empty, or the portal hasn't completed OAuth). /bitrix24/install + // is what completes portal OAuth — gating the route behind a + // fully-configured bot creates a deadlock where an admin can never + // finish installing the first portal on a fresh gateway. ClaimWebhookRoute + // is idempotent (first-claim-wins via CompareAndSwap), so this is a no-op + // if a bitrix24 Channel already claimed the route in the loop above. + if router := bitrix24.WebhookRouter(); router != nil { + if path, handler := router.ClaimWebhookRoute(); path != "" && handler != nil { + mux.Handle(path, handler) + slog.Info("webhook route mounted on gateway", "path", path) + } + } + tsCleanup := initTailscale(ctx, d.cfg, mux) if tsCleanup != nil { defer tsCleanup()