Skip to content

Commit 3bed73d

Browse files
fix(config): migrate removed telegram groupMentionsOnly key (openclaw#55336)
Merged via squash. Prepared head SHA: 23731e2 Co-authored-by: jameslcowan <112015792+jameslcowan@users.noreply.github.com> Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com> Reviewed-by: @jalehman
1 parent 8dfbcaa commit 3bed73d

5 files changed

Lines changed: 185 additions & 0 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ Docs: https://docs.openclaw.ai
130130
- TTS: Restore 3.28 schema compatibility and fallback observability. (#57953) Thanks @joshavant.
131131
- Telegram/forum topics: restore reply routing to the active topic and keep ACP `sessions_spawn(..., thread=true, mode="session")` bound to that same topic instead of falling back to root chat or losing follow-up routing. (#56060) Thanks @one27001.
132132
- Config/SecretRef + Control UI: harden SecretRef redaction round-trip restore, block unsafe raw fallback (force Form mode when raw is unavailable), and preflight submitted-config SecretRefs before config write RPC persistence. (#58044) Thanks @joshavant.
133+
- Config/Telegram: migrate removed `channels.telegram.groupMentionsOnly` into `channels.telegram.groups["*"].requireMention` on load so legacy configs no longer crash at startup. (#55336) thanks @jameslcowan.
133134

134135
## 2026.3.28
135136

src/config/config.legacy-config-detection.accepts-imessage-dmpolicy.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,25 @@ describe("legacy config detection", () => {
214214
expect(res.changes).toEqual([]);
215215
expect(res.config).toBeNull();
216216
});
217+
218+
it("flags channels.telegram.groupMentionsOnly as legacy in snapshot", async () => {
219+
await withSnapshotForConfig(
220+
{ channels: { telegram: { groupMentionsOnly: true } } },
221+
async (ctx) => {
222+
expect(ctx.snapshot.valid).toBe(true);
223+
expect(
224+
ctx.snapshot.legacyIssues.some(
225+
(issue) => issue.path === "channels.telegram.groupMentionsOnly",
226+
),
227+
).toBe(true);
228+
const parsed = ctx.parsed as {
229+
channels?: { telegram?: { groupMentionsOnly?: boolean } };
230+
};
231+
expect(parsed.channels?.telegram?.groupMentionsOnly).toBe(true);
232+
},
233+
);
234+
});
235+
217236
it("does not rewrite removed messages.tts.enabled migrations", async () => {
218237
const res = migrateLegacyConfig({
219238
messages: { tts: { enabled: true } },

src/config/config.legacy-config-detection.rejects-routing-allowfrom.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,17 @@ describe("legacy config detection", () => {
242242
expect(res.issues[0]?.message).toContain('"telegram"');
243243
}
244244
});
245+
it("rejects channels.telegram.groupMentionsOnly", async () => {
246+
const res = validateConfigObject({
247+
channels: { telegram: { groupMentionsOnly: true } },
248+
});
249+
expect(res.ok).toBe(false);
250+
if (!res.ok) {
251+
expect(res.issues.some((issue) => issue.path === "channels.telegram.groupMentionsOnly")).toBe(
252+
true,
253+
);
254+
}
255+
});
245256
it("rejects gateway.token", async () => {
246257
const res = validateConfigObject({
247258
gateway: { token: "legacy-token" },

src/config/legacy-migrate.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,89 @@ describe("legacy migrate mention routing", () => {
7777
expect(res.changes).toEqual([]);
7878
expect(res.config).toBeNull();
7979
});
80+
81+
it("moves channels.telegram.groupMentionsOnly into groups.*.requireMention", () => {
82+
const res = migrateLegacyConfig({
83+
channels: {
84+
telegram: {
85+
groupMentionsOnly: true,
86+
},
87+
},
88+
});
89+
90+
expect(res.changes).toContain(
91+
'Moved channels.telegram.groupMentionsOnly → channels.telegram.groups."*".requireMention.',
92+
);
93+
expect(res.config?.channels?.telegram?.groups?.["*"]?.requireMention).toBe(true);
94+
expect(
95+
(res.config?.channels?.telegram as { groupMentionsOnly?: unknown } | undefined)
96+
?.groupMentionsOnly,
97+
).toBeUndefined();
98+
});
99+
100+
it('keeps explicit channels.telegram.groups."*".requireMention when migrating groupMentionsOnly', () => {
101+
const res = migrateLegacyConfig({
102+
channels: {
103+
telegram: {
104+
groupMentionsOnly: true,
105+
groups: {
106+
"*": {
107+
requireMention: false,
108+
},
109+
},
110+
},
111+
},
112+
});
113+
114+
expect(res.changes).toContain(
115+
'Removed channels.telegram.groupMentionsOnly (channels.telegram.groups."*" already set).',
116+
);
117+
expect(res.config?.channels?.telegram?.groups?.["*"]?.requireMention).toBe(false);
118+
expect(
119+
(res.config?.channels?.telegram as { groupMentionsOnly?: unknown } | undefined)
120+
?.groupMentionsOnly,
121+
).toBeUndefined();
122+
});
123+
124+
it("does not overwrite invalid channels.telegram.groups when migrating groupMentionsOnly", () => {
125+
const res = migrateLegacyConfig({
126+
channels: {
127+
telegram: {
128+
groupMentionsOnly: true,
129+
groups: [],
130+
},
131+
},
132+
});
133+
134+
expect(res.config).toBeNull();
135+
expect(res.changes).toContain(
136+
"Skipped channels.telegram.groupMentionsOnly migration because channels.telegram.groups already has an incompatible shape; fix remaining issues manually.",
137+
);
138+
expect(res.changes).toContain(
139+
"Migration applied, but config still invalid; fix remaining issues manually.",
140+
);
141+
});
142+
143+
it('does not overwrite invalid channels.telegram.groups."*" when migrating groupMentionsOnly', () => {
144+
const res = migrateLegacyConfig({
145+
channels: {
146+
telegram: {
147+
groupMentionsOnly: true,
148+
groups: {
149+
"*": false,
150+
},
151+
},
152+
},
153+
});
154+
155+
expect(res.config).toBeNull();
156+
expect(res.changes).toContain(
157+
"Skipped channels.telegram.groupMentionsOnly migration because channels.telegram.groups already has an incompatible shape; fix remaining issues manually.",
158+
);
159+
expect(res.changes).toContain(
160+
"Migration applied, but config still invalid; fix remaining issues manually.",
161+
);
162+
});
80163
});
81164

82165
describe("legacy migrate tts provider shape", () => {

src/config/legacy.migrations.runtime.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,36 @@ function migrateLegacyTtsConfig(
215215
}
216216
}
217217

218+
function resolveCompatibleDefaultGroupEntry(section: Record<string, unknown>): {
219+
groups: Record<string, unknown>;
220+
entry: Record<string, unknown>;
221+
} | null {
222+
const existingGroups = section.groups;
223+
if (existingGroups !== undefined && !getRecord(existingGroups)) {
224+
return null;
225+
}
226+
const groups = getRecord(existingGroups) ?? {};
227+
const defaultKey = "*";
228+
const existingEntry = groups[defaultKey];
229+
if (existingEntry !== undefined && !getRecord(existingEntry)) {
230+
return null;
231+
}
232+
const entry = getRecord(existingEntry) ?? {};
233+
return { groups, entry };
234+
}
235+
218236
const MEMORY_SEARCH_RULE: LegacyConfigRule = {
219237
path: ["memorySearch"],
220238
message:
221239
"top-level memorySearch was moved; use agents.defaults.memorySearch instead (auto-migrated on load).",
222240
};
223241

242+
const GROUP_MENTIONS_ONLY_RULE: LegacyConfigRule = {
243+
path: ["channels", "telegram", "groupMentionsOnly"],
244+
message:
245+
'channels.telegram.groupMentionsOnly was removed; use channels.telegram.groups."*".requireMention instead (auto-migrated on load).',
246+
};
247+
224248
const GATEWAY_BIND_RULE: LegacyConfigRule = {
225249
path: ["gateway", "bind"],
226250
message:
@@ -307,6 +331,53 @@ export const LEGACY_CONFIG_MIGRATIONS_RUNTIME: LegacyConfigMigrationSpec[] = [
307331
);
308332
},
309333
}),
334+
defineLegacyConfigMigration({
335+
// v2026.2.23 replaced channels.telegram.groupMentionsOnly with
336+
// channels.telegram.groups."*".requireMention. Existing configs crash on
337+
// startup because gateway auto-migration only runs for registered legacy
338+
// keys, and this removed key previously fell through as an unknown field.
339+
id: "channels.telegram.groupMentionsOnly->channels.telegram.groups.*.requireMention",
340+
describe:
341+
"Move channels.telegram.groupMentionsOnly to channels.telegram.groups.*.requireMention",
342+
legacyRules: [GROUP_MENTIONS_ONLY_RULE],
343+
apply: (raw, changes) => {
344+
const channels = ensureRecord(raw, "channels");
345+
const telegram = getRecord(channels.telegram);
346+
if (!telegram || telegram.groupMentionsOnly === undefined) {
347+
return;
348+
}
349+
350+
const groupMentionsOnly = telegram.groupMentionsOnly;
351+
const defaultGroupEntry = resolveCompatibleDefaultGroupEntry(telegram);
352+
const defaultKey = "*";
353+
354+
if (!defaultGroupEntry) {
355+
changes.push(
356+
"Skipped channels.telegram.groupMentionsOnly migration because channels.telegram.groups already has an incompatible shape; fix remaining issues manually.",
357+
);
358+
return;
359+
}
360+
361+
const { groups, entry } = defaultGroupEntry;
362+
363+
if (entry.requireMention === undefined) {
364+
entry.requireMention = groupMentionsOnly;
365+
groups[defaultKey] = entry;
366+
telegram.groups = groups;
367+
changes.push(
368+
'Moved channels.telegram.groupMentionsOnly → channels.telegram.groups."*".requireMention.',
369+
);
370+
} else {
371+
changes.push(
372+
'Removed channels.telegram.groupMentionsOnly (channels.telegram.groups."*" already set).',
373+
);
374+
}
375+
376+
delete telegram.groupMentionsOnly;
377+
channels.telegram = telegram;
378+
raw.channels = channels;
379+
},
380+
}),
310381
defineLegacyConfigMigration({
311382
id: "memorySearch->agents.defaults.memorySearch",
312383
describe: "Move top-level memorySearch to agents.defaults.memorySearch",

0 commit comments

Comments
 (0)