Compare commits

...

10 Commits

Author SHA1 Message Date
Bryan Thompson
7122c51880 Normalize git-subdir source URLs to full HTTPS format
Standardize 12 git-subdir plugin entries from owner/repo shorthand to
full https://github.com/owner/repo.git URLs for consistency with the
existing HTTPS entries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:09:11 -05:00
Daisy S. Hollman
48aa435178 fix(discord): use cached author.id when DMChannel.recipientId is null (#1365)
DMChannel.recipientId can be null when client.channels.fetch() returns
a DM channel with a cold cache. The inbound gate correctly uses
msg.author.id, but fetchAllowedChannel relied on recipientId, so
replies to allowlisted DMs intermittently failed with "channel not
allowlisted" after session restart.

Maintain a channelId→userId map populated during inbound handling and
fall back to it when recipientId is null.

Fixes anthropics/claude-code#40576
Fixes anthropics/claude-code#41647

🏠 Remote-Dev: homespace
2026-04-14 14:46:42 -07:00
Noah Zweben
7e401edac7 fix(telegram): retry polling on all transient errors, not just 409 (#1397)
A single ETIMEDOUT/ECONNRESET/DNS failure during long-polling rejected
bot.start(); the catch block returned and polling stopped permanently.
The MCP server process stayed alive (stdin keeps it running), so outbound
reply/react tools kept working — but the bot was deaf to inbound messages
until a full restart. Users see 'typing...' then nothing, indistinguishable
from the harness-side gate bug.

Now all errors retry with the same capped backoff (max 15s). attempt resets
to 0 in onStart so backoff doesn't accumulate across a long-running session.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-14 12:47:13 -07:00
Bryan Thompson
7f3389d21f Merge pull request #1403 from anthropics/add-dataverse-v2
Add dataverse plugin
2026-04-14 11:59:10 -05:00
Bryan Thompson
560b7e0d38 Merge pull request #1402 from anthropics/add-azure-skills-v2
Add azure-skills plugin
2026-04-14 11:57:51 -05:00
Bryan Thompson
903a6aba48 Merge pull request #1409 from anthropics/add-shopify-ai-toolkit
Add shopify-ai-toolkit plugin
2026-04-14 11:57:45 -05:00
Bryan Thompson
8145923edc Remove SHA pin from azure-skills entry 2026-04-14 11:51:46 -05:00
Bryan Thompson
2b666914e6 Remove SHA pin from dataverse entry 2026-04-14 11:51:40 -05:00
Bryan Thompson
0de7a91403 Add dataverse plugin 2026-04-13 18:00:49 -05:00
Bryan Thompson
c5b7657350 Add azure-skills plugin 2026-04-13 18:00:31 -05:00
4 changed files with 63 additions and 37 deletions

View File

@@ -44,7 +44,7 @@
"description": "AI-first project auditor and re-engineer based on the 9 design principles and 7 design patterns from the TechWolf AI-First Bootcamp",
"source": {
"source": "git-subdir",
"url": "techwolf-ai/ai-first-toolkit",
"url": "https://github.com/techwolf-ai/ai-first-toolkit.git",
"path": "plugins/ai-firstify",
"ref": "main",
"sha": "7f18e11d694b9ae62ea3009fbbc175f08ae913df"
@@ -179,6 +179,16 @@
"category": "database",
"homepage": "https://github.com/AzureCosmosDB/cosmosdb-claude-code-plugin"
},
{
"name": "azure-skills",
"description": "Microsoft Azure MCP integration for cloud resource management, deployments, and Azure services. Manage your Azure infrastructure, monitor applications, and deploy resources directly from Claude Code.",
"category": "deployment",
"source": {
"source": "url",
"url": "https://github.com/microsoft/azure-skills.git"
},
"homepage": "https://github.com/microsoft/azure-skills"
},
{
"name": "base44",
"description": "Build and deploy Base44 full-stack apps with CLI project management and JavaScript/TypeScript SDK development skills",
@@ -199,7 +209,7 @@
"category": "database",
"source": {
"source": "git-subdir",
"url": "Bigdata-com/bigdata-plugins-marketplace",
"url": "https://github.com/Bigdata-com/bigdata-plugins-marketplace.git",
"path": "plugins/bigdata-com",
"ref": "main"
},
@@ -436,6 +446,18 @@
},
"homepage": "https://github.com/astronomer/agents"
},
{
"name": "dataverse",
"description": "Agent skills for building on, analyzing, and managing Microsoft Dataverse — with Dataverse MCP, PAC CLI, and Python SDK.",
"category": "database",
"source": {
"source": "git-subdir",
"url": "https://github.com/microsoft/Dataverse-skills.git",
"path": ".github/plugins/dataverse",
"ref": "main"
},
"homepage": "https://github.com/microsoft/Dataverse-skills"
},
{
"name": "deploy-on-aws",
"description": "Deploy applications to AWS with architecture recommendations, cost estimates, and IaC deployment.",
@@ -481,7 +503,7 @@
"category": "development",
"source": {
"source": "git-subdir",
"url": "expo/skills",
"url": "https://github.com/expo/skills.git",
"path": "plugins/expo",
"ref": "main"
},
@@ -648,7 +670,7 @@
"description": "Build on Solana with Helius — live blockchain tools, expert coding patterns, and autonomous account signup",
"source": {
"source": "git-subdir",
"url": "helius-labs/core-ai",
"url": "https://github.com/helius-labs/core-ai.git",
"path": "helius-plugin",
"ref": "main",
"sha": "05ea4d1128d46618266bbcc23a5e7019c57be0d6"
@@ -763,7 +785,7 @@
"category": "productivity",
"source": {
"source": "git-subdir",
"url": "legalzoom/claude-plugins",
"url": "https://github.com/legalzoom/claude-plugins.git",
"path": "plugins/legalzoom",
"ref": "main",
"sha": "f9fd8a0ca6e1421bc1aacb113a109663a7a6f6d8"
@@ -869,7 +891,7 @@
"category": "database",
"source": {
"source": "git-subdir",
"url": "neondatabase/agent-skills",
"url": "https://github.com/neondatabase/agent-skills.git",
"path": "plugins/neon-postgres",
"ref": "main",
"sha": "54d7a9db2ddd476f84d5d1fd7bac323907858a8b"
@@ -1087,7 +1109,7 @@
"category": "development",
"source": {
"source": "git-subdir",
"url": "pydantic/skills",
"url": "https://github.com/pydantic/skills.git",
"path": "plugins/ai",
"ref": "main"
},
@@ -1133,7 +1155,7 @@
"category": "deployment",
"source": {
"source": "git-subdir",
"url": "railwayapp/railway-skills",
"url": "https://github.com/railwayapp/railway-skills.git",
"path": "plugins/railway",
"ref": "main",
"sha": "d52f3741a6a33a3191d6138eb3d6c3355cb970d1"
@@ -1426,7 +1448,7 @@
"category": "development",
"source": {
"source": "git-subdir",
"url": "stripe/ai",
"url": "https://github.com/stripe/ai.git",
"path": "providers/claude/plugin",
"ref": "main"
},
@@ -1533,7 +1555,7 @@
"category": "development",
"source": {
"source": "git-subdir",
"url": "UI5/plugins-claude",
"url": "https://github.com/UI5/plugins-claude.git",
"path": "plugins/ui5",
"ref": "main",
"sha": "5070dfc1cef711d6efad40beb43750027039d71f"
@@ -1546,7 +1568,7 @@
"category": "development",
"source": {
"source": "git-subdir",
"url": "UI5/plugins-claude",
"url": "https://github.com/UI5/plugins-claude.git",
"path": "plugins/ui5-typescript-conversion",
"ref": "main",
"sha": "5070dfc1cef711d6efad40beb43750027039d71f"
@@ -1600,7 +1622,7 @@
"category": "productivity",
"source": {
"source": "git-subdir",
"url": "zapier/zapier-mcp",
"url": "https://github.com/zapier/zapier-mcp.git",
"path": "plugins/zapier",
"ref": "main",
"sha": "b93007e9a726c6ee93c57a949e732744ef5acbfd"

View File

@@ -222,6 +222,8 @@ type GateResult =
const recentSentIds = new Set<string>()
const RECENT_SENT_CAP = 200
const dmChannelUsers = new Map<string, string>()
function noteSent(id: string): void {
recentSentIds.add(id)
if (recentSentIds.size > RECENT_SENT_CAP) {
@@ -404,7 +406,8 @@ async function fetchAllowedChannel(id: string) {
const ch = await fetchTextChannel(id)
const access = loadAccess()
if (ch.type === ChannelType.DM) {
if (access.allowFrom.includes(ch.recipientId)) return ch
const userId = ch.recipientId ?? dmChannelUsers.get(id)
if (userId && access.allowFrom.includes(userId)) return ch
} else {
const key = ch.isThread() ? ch.parentId ?? ch.id : ch.id
if (key in access.groups) return ch
@@ -823,6 +826,10 @@ async function handleInbound(msg: Message): Promise<void> {
const chat_id = msg.channelId
if (msg.channel.type === ChannelType.DM) {
dmChannelUsers.set(chat_id, msg.author.id)
}
// Permission-reply intercept: if this looks like "yes xxxxx" for a
// pending permission request, emit the structured event instead of
// relaying as chat. The sender is already gate()-approved at this point

View File

@@ -1,7 +1,7 @@
{
"name": "telegram",
"description": "Telegram channel for Claude Code \u2014 messaging bridge with built-in access control. Manage pairing, allowlists, and policy via /telegram:access.",
"version": "0.0.5",
"version": "0.0.6",
"keywords": [
"telegram",
"messaging",

View File

@@ -985,14 +985,17 @@ bot.catch(err => {
process.stderr.write(`telegram channel: handler error (polling continues): ${err.error}\n`)
})
// 409 Conflict = another getUpdates consumer is still active (zombie from a
// previous session, or a second Claude Code instance). Retry with backoff
// until the slot frees up instead of crashing on the first rejection.
// Retry polling with backoff on any error. Previously only 409 was retried —
// a single ETIMEDOUT/ECONNRESET/DNS failure rejected bot.start(), the catch
// returned, and polling stopped permanently while the process stayed alive
// (MCP stdin keeps it running). Outbound tools kept working but the bot was
// deaf to inbound messages until a full restart.
void (async () => {
for (let attempt = 1; ; attempt++) {
try {
await bot.start({
onStart: info => {
attempt = 0
botUsername = info.username
process.stderr.write(`telegram channel: polling as @${info.username}\n`)
void bot.api.setMyCommands(
@@ -1008,28 +1011,22 @@ void (async () => {
return // bot.stop() was called — clean exit from the loop
} catch (err) {
if (shuttingDown) return
if (err instanceof GrammyError && err.error_code === 409) {
if (attempt >= 8) {
process.stderr.write(
`telegram channel: 409 Conflict persists after ${attempt} attempts — ` +
`another poller is holding the bot token (stray 'bun server.ts' process or a second session). Exiting.\n`,
)
return
}
const delay = Math.min(1000 * attempt, 15000)
const detail = attempt === 1
? ' — another instance is polling (zombie session, or a second Claude Code running?)'
: ''
process.stderr.write(
`telegram channel: 409 Conflict${detail}, retrying in ${delay / 1000}s\n`,
)
await new Promise(r => setTimeout(r, delay))
continue
}
// bot.stop() mid-setup rejects with grammy's "Aborted delay" — expected, not an error.
if (err instanceof Error && err.message === 'Aborted delay') return
process.stderr.write(`telegram channel: polling failed: ${err}\n`)
return
const is409 = err instanceof GrammyError && err.error_code === 409
if (is409 && attempt >= 8) {
process.stderr.write(
`telegram channel: 409 Conflict persists after ${attempt} attempts — ` +
`another poller is holding the bot token (stray 'bun server.ts' process or a second session). Exiting.\n`,
)
return
}
const delay = Math.min(1000 * attempt, 15000)
const detail = is409
? `409 Conflict${attempt === 1 ? ' — another instance is polling (zombie session, or a second Claude Code running?)' : ''}`
: `polling error: ${err}`
process.stderr.write(`telegram channel: ${detail}, retrying in ${delay / 1000}s\n`)
await new Promise(r => setTimeout(r, delay))
}
}
})()