Problem
When cron jobs run through ChannelBridge, the ChannelMessage built by buildCronChannelMessage() does not include any job-specific identifier in its key fields. As a result:
-
REMOTECLAW_SESSION_KEY env var — ChannelBridge passes formatSessionKeyString(buildSessionKey(message)) to the CLI subprocess. Without a distinguishing field, all cron jobs get the same session key string (e.g., "<delivery-channel>:system:_"), making it impossible for the MCP server or debugging tools to tell which cron job is running.
-
replyToId is unset — buildSessionKey() maps message.replyToId to SessionKey.threadId. Since cron messages don't set replyToId, the threadId component is always "_".
Note: The functional session-isolation bug (cross-job session contamination) is already mitigated by createSessionMapAdapter() in run.ts, which bypasses SessionMap's key-based lookup entirely and bridges directly to the per-job cron session store. This issue addresses the observability gap and ensures the message metadata is semantically correct.
Current State
buildCronChannelMessage() (src/cron/isolated-agent/run.ts ~line 101)
function buildCronChannelMessage(params: {
job: CronJob;
commandBody: string;
resolvedDelivery: { channel?: string; to?: string; accountId?: string };
timestamp: number;
messageToolHints: string[] | undefined;
}): ChannelMessage {
return {
id: params.job.id ?? crypto.randomUUID(),
text: params.commandBody,
from: params.resolvedDelivery.accountId ?? "system",
channelId: params.resolvedDelivery.to ?? "",
provider: params.resolvedDelivery.channel ?? "cron",
timestamp: params.timestamp,
messageToolHints: params.messageToolHints?.length ? params.messageToolHints : undefined,
};
}
No replyToId is set → buildSessionKey() produces threadId: undefined → session key ends with ":_" for every cron job.
buildSessionKey() (src/middleware/channel-bridge.ts ~line 204)
export function buildSessionKey(message: ChannelMessage): SessionKey {
return {
channelId: message.channelId,
userId: message.from,
threadId: message.replyToId, // undefined for cron → "_"
};
}
formatKey() (src/middleware/session-map.ts ~line 110)
function formatKey(key: SessionKey): string {
return `${key.channelId}:${key.userId}:${key.threadId ?? "_"}`;
}
Session adapter (already correct)
createSessionMapAdapter() in run.ts correctly bridges per-job session IDs from the cron session store to the SessionMap interface, so the functional collision doesn't happen. setCliSessionId() / getCliSessionId() persist CLI session IDs on the per-job cronSession.sessionEntry.
Required Changes
1. Set replyToId to the cron job ID (~1 line)
In buildCronChannelMessage(), add:
replyToId: `cron:${params.job.id}`,
This flows through buildSessionKey() → threadId: "cron:daily-review" → REMOTECLAW_SESSION_KEY becomes e.g. "telegram:system:cron:daily-review".
2. Add unit test for distinct session keys (~20 lines)
In src/cron/isolated-agent/run.channel-bridge.test.ts, add a test that:
- Builds two
ChannelMessage objects for different cron jobs via buildCronChannelMessage()
- Passes each through
buildSessionKey()
- Asserts the resulting
SessionKey.threadId values are different
- Asserts the full formatted keys are different
Acceptance Criteria
Files to Touch
| File |
Change |
src/cron/isolated-agent/run.ts |
Add replyToId to buildCronChannelMessage() return |
src/cron/isolated-agent/run.channel-bridge.test.ts |
Add session key distinction test |
Context
- ChannelMessage type:
src/middleware/types.ts (line ~163) — replyToId?: string | undefined already exists
- Session adapter:
createSessionMapAdapter() in src/cron/isolated-agent/run.ts (~line 75) — handles functional isolation; this issue fixes metadata/observability
Problem
When cron jobs run through
ChannelBridge, theChannelMessagebuilt bybuildCronChannelMessage()does not include any job-specific identifier in its key fields. As a result:REMOTECLAW_SESSION_KEYenv var — ChannelBridge passesformatSessionKeyString(buildSessionKey(message))to the CLI subprocess. Without a distinguishing field, all cron jobs get the same session key string (e.g.,"<delivery-channel>:system:_"), making it impossible for the MCP server or debugging tools to tell which cron job is running.replyToIdis unset —buildSessionKey()mapsmessage.replyToIdtoSessionKey.threadId. Since cron messages don't setreplyToId, thethreadIdcomponent is always"_".Current State
buildCronChannelMessage()(src/cron/isolated-agent/run.ts~line 101)No
replyToIdis set →buildSessionKey()producesthreadId: undefined→ session key ends with":_"for every cron job.buildSessionKey()(src/middleware/channel-bridge.ts~line 204)formatKey()(src/middleware/session-map.ts~line 110)Session adapter (already correct)
createSessionMapAdapter()inrun.tscorrectly bridges per-job session IDs from the cron session store to theSessionMapinterface, so the functional collision doesn't happen.setCliSessionId()/getCliSessionId()persist CLI session IDs on the per-jobcronSession.sessionEntry.Required Changes
1. Set
replyToIdto the cron job ID (~1 line)In
buildCronChannelMessage(), add:This flows through
buildSessionKey()→threadId: "cron:daily-review"→REMOTECLAW_SESSION_KEYbecomes e.g."telegram:system:cron:daily-review".2. Add unit test for distinct session keys (~20 lines)
In
src/cron/isolated-agent/run.channel-bridge.test.ts, add a test that:ChannelMessageobjects for different cron jobs viabuildCronChannelMessage()buildSessionKey()SessionKey.threadIdvalues are differentAcceptance Criteria
buildCronChannelMessage()setsreplyToId:cron:${params.job.id}``REMOTECLAW_SESSION_KEYvalues in the CLI subprocess environmentpnpm buildpassesFiles to Touch
src/cron/isolated-agent/run.tsreplyToIdtobuildCronChannelMessage()returnsrc/cron/isolated-agent/run.channel-bridge.test.tsContext
src/middleware/types.ts(line ~163) —replyToId?: string | undefinedalready existscreateSessionMapAdapter()insrc/cron/isolated-agent/run.ts(~line 75) — handles functional isolation; this issue fixes metadata/observability