diff --git a/container/agent-runner/src/integration.test.ts b/container/agent-runner/src/integration.test.ts
index 3447c3894..f309cc381 100644
--- a/container/agent-runner/src/integration.test.ts
+++ b/container/agent-runner/src/integration.test.ts
@@ -74,6 +74,44 @@ describe('poll loop integration', () => {
await loopPromise.catch(() => {});
});
+ it('should resolve thread_id per-destination, not from global routing', async () => {
+ // Seed a second destination
+ getInboundDb()
+ .prepare(
+ `INSERT INTO destinations (name, display_name, type, channel_type, platform_id, agent_group_id)
+ VALUES ('slack-test', 'Slack Test', 'channel', 'slack', 'chan-2', NULL)`,
+ )
+ .run();
+
+ // Insert messages from each destination with distinct thread IDs
+ insertMessage('m-discord', { sender: 'Alice', text: 'from discord' }, { platformId: 'chan-1', channelType: 'discord', threadId: 'discord-thread-1' });
+ insertMessage('m-slack', { sender: 'Bob', text: 'from slack' }, { platformId: 'chan-2', channelType: 'slack', threadId: 'slack-thread-99' });
+
+ // Agent replies to both destinations
+ const provider = new MockProvider({}, () =>
+ 'reply-dreply-s',
+ );
+ const controller = new AbortController();
+ const loopPromise = runPollLoopWithTimeout(provider, controller.signal, 2000);
+
+ await waitFor(() => getUndeliveredMessages().length >= 2, 2000);
+ controller.abort();
+
+ const out = getUndeliveredMessages();
+ const discordOut = out.find((m) => m.platform_id === 'chan-1');
+ const slackOut = out.find((m) => m.platform_id === 'chan-2');
+
+ expect(discordOut).toBeDefined();
+ expect(discordOut!.thread_id).toBe('discord-thread-1');
+ expect(discordOut!.in_reply_to).toBe('m-discord');
+
+ expect(slackOut).toBeDefined();
+ expect(slackOut!.thread_id).toBe('slack-thread-99');
+ expect(slackOut!.in_reply_to).toBe('m-slack');
+
+ await loopPromise.catch(() => {});
+ });
+
it('should process messages arriving after loop starts', async () => {
const provider = new MockProvider({}, () => 'Processed');
const controller = new AbortController();
diff --git a/container/agent-runner/src/poll-loop.ts b/container/agent-runner/src/poll-loop.ts
index 076d29d5e..35abb8399 100644
--- a/container/agent-runner/src/poll-loop.ts
+++ b/container/agent-runner/src/poll-loop.ts
@@ -478,8 +478,8 @@ function resolveDestinationThread(
)
.get(channelType, platformId) as { thread_id: string | null; id: string } | undefined;
if (row) return { threadId: row.thread_id, inReplyTo: row.id };
- } catch {
- // Fall through — DB may not have these columns on older sessions
+ } catch (err) {
+ log(`resolveDestinationThread error: ${err instanceof Error ? err.message : String(err)}`);
}
return null;
}
diff --git a/src/group-init.ts b/src/group-init.ts
index 0e6aeb15b..b3251506e 100644
--- a/src/group-init.ts
+++ b/src/group-init.ts
@@ -89,7 +89,6 @@ export function initGroupFilesystem(group: AgentGroup, opts?: { instructions?: s
// Skills directory — created empty here; symlinks are synced at spawn
// time by container-runner.ts based on container.json skills selection.
- // (ensurePreCompactHook is defined after the main function.)
const skillsDst = path.join(claudeDir, 'skills');
if (!fs.existsSync(skillsDst)) {
fs.mkdirSync(skillsDst, { recursive: true });