fix(pairing): handle missing accountId in allowFrom reads (#31369)

* pairing: honor default account in allowFrom read when accountId omitted

* changelog: credit pairing allowFrom fallback fix
This commit is contained in:
Vincent Koc
2026-03-01 23:24:33 -08:00
committed by GitHub
parent e055afd000
commit fbc1585b3f
3 changed files with 26 additions and 6 deletions

View File

@@ -40,6 +40,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Pairing/AllowFrom account fallback: handle omitted `accountId` values in `readChannelAllowFromStore` and `readChannelAllowFromStoreSync` as `default`, while preserving legacy unscoped allowFrom merges for default-account flows. Thanks @Sid-Qin and @vincentkoc.
- Agents/Subagent announce cleanup: keep completion-message runs pending while descendants settle, add a 30 minute hard-expiry backstop to avoid indefinite pending state, and keep retry bookkeeping resumable across deferred wakes. (#23970) Thanks @tyler6204.
- BlueBubbles/Message metadata: harden send response ID extraction, include sender identity in DM context, and normalize inbound `message_id` selection to avoid duplicate ID metadata. (#23970) Thanks @tyler6204.
- Gateway/Control UI method guard: allow POST requests to non-UI routes to fall through when no base path is configured, and add POST regression coverage for fallthrough and base-path 405 behavior. (#23970) Thanks @tyler6204.

View File

@@ -395,4 +395,21 @@ describe("pairing store", () => {
expect(scoped).toEqual(["1002", "1001"]);
});
});
it("uses default-account allowFrom when account id is omitted", async () => {
await withTempStateDir(async (stateDir) => {
await writeAllowFromFixture({ stateDir, channel: "telegram", allowFrom: ["1001"] });
await writeAllowFromFixture({
stateDir,
channel: "telegram",
accountId: DEFAULT_ACCOUNT_ID,
allowFrom: ["1002"],
});
const asyncScoped = await readChannelAllowFromStore("telegram", process.env);
const syncScoped = readChannelAllowFromStoreSync("telegram", process.env);
expect(asyncScoped).toEqual(["1002", "1001"]);
expect(syncScoped).toEqual(["1002", "1001"]);
});
});
});

View File

@@ -225,6 +225,10 @@ function shouldIncludeLegacyAllowFromEntries(normalizedAccountId: string): boole
return !normalizedAccountId || normalizedAccountId === DEFAULT_ACCOUNT_ID;
}
function resolveAllowFromAccountId(accountId?: string): string {
return normalizePairingAccountId(accountId) || DEFAULT_ACCOUNT_ID;
}
function normalizeId(value: string | number): string {
return String(value).trim();
}
@@ -395,10 +399,9 @@ export async function readLegacyChannelAllowFromStore(
export async function readChannelAllowFromStore(
channel: PairingChannel,
env: NodeJS.ProcessEnv = process.env,
accountId: string,
accountId?: string,
): Promise<string[]> {
const normalizedAccountId = accountId.trim().toLowerCase();
const resolvedAccountId = normalizedAccountId || DEFAULT_ACCOUNT_ID;
const resolvedAccountId = resolveAllowFromAccountId(accountId);
if (!shouldIncludeLegacyAllowFromEntries(resolvedAccountId)) {
return await readNonDefaultAccountAllowFrom({
@@ -427,10 +430,9 @@ export function readLegacyChannelAllowFromStoreSync(
export function readChannelAllowFromStoreSync(
channel: PairingChannel,
env: NodeJS.ProcessEnv = process.env,
accountId: string,
accountId?: string,
): string[] {
const normalizedAccountId = accountId.trim().toLowerCase();
const resolvedAccountId = normalizedAccountId || DEFAULT_ACCOUNT_ID;
const resolvedAccountId = resolveAllowFromAccountId(accountId);
if (!shouldIncludeLegacyAllowFromEntries(resolvedAccountId)) {
return readNonDefaultAccountAllowFromSync({