fix(synology-chat): prevent restart loop in startAccount#23074
fix(synology-chat): prevent restart loop in startAccount#23074steipete merged 4 commits intoopenclaw:mainfrom
Conversation
| if (!account.enabled) { | ||
| log?.info?.(`Synology Chat account ${accountId} is disabled, skipping`); | ||
| return { stop: () => {} }; | ||
| } |
There was a problem hiding this comment.
returning synchronous { stop: () => {} } will cause the same restart loop for disabled accounts
the gateway wraps all startAccount returns in Promise.resolve(), so returning { stop: () => {} } resolves immediately and triggers auto-restart logic (src/gateway/server-channels.ts:203-228)
| if (!account.enabled) { | |
| log?.info?.(`Synology Chat account ${accountId} is disabled, skipping`); | |
| return { stop: () => {} }; | |
| } | |
| log?.info?.(`Synology Chat account ${accountId} is disabled, skipping`); | |
| return new Promise<void>(() => {}); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/synology-chat/src/channel.ts
Line: 211-214
Comment:
returning synchronous `{ stop: () => {} }` will cause the same restart loop for disabled accounts
the gateway wraps all `startAccount` returns in `Promise.resolve()`, so returning `{ stop: () => {} }` resolves immediately and triggers auto-restart logic (src/gateway/server-channels.ts:203-228)
```suggestion
log?.info?.(`Synology Chat account ${accountId} is disabled, skipping`);
return new Promise<void>(() => {});
}
```
How can I resolve this? If you propose a fix, please make it concise.| if (!account.token || !account.incomingUrl) { | ||
| log?.warn?.( | ||
| `Synology Chat account ${accountId} not fully configured (missing token or incomingUrl)`, | ||
| ); | ||
| return { stop: () => {} }; | ||
| } |
There was a problem hiding this comment.
same restart loop issue for unconfigured accounts
| if (!account.token || !account.incomingUrl) { | |
| log?.warn?.( | |
| `Synology Chat account ${accountId} not fully configured (missing token or incomingUrl)`, | |
| ); | |
| return { stop: () => {} }; | |
| } | |
| log?.warn?.( | |
| `Synology Chat account ${accountId} not fully configured (missing token or incomingUrl)`, | |
| ); | |
| return new Promise<void>(() => {}); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/synology-chat/src/channel.ts
Line: 216-221
Comment:
same restart loop issue for unconfigured accounts
```suggestion
log?.warn?.(
`Synology Chat account ${accountId} not fully configured (missing token or incomingUrl)`,
);
return new Promise<void>(() => {});
}
```
How can I resolve this? If you propose a fix, please make it concise.| log: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, | ||
| }; | ||
| const result = await plugin.gateway.startAccount(ctx); | ||
| expect(typeof result.stop).toBe("function"); |
There was a problem hiding this comment.
test expects { stop: function } but the fix changes this to return a pending Promise
after the fix, disabled accounts should return new Promise<void>(() => {}) (never-resolving promise), not { stop: () => {} }
update test to verify the return is a Promise that stays pending
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/synology-chat/src/channel.test.ts
Line: 323
Comment:
test expects `{ stop: function }` but the fix changes this to return a pending Promise
after the fix, disabled accounts should return `new Promise<void>(() => {})` (never-resolving promise), not `{ stop: () => {} }`
update test to verify the return is a Promise that stays pending
How can I resolve this? If you propose a fix, please make it concise.| log: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, | ||
| }; | ||
| const result = await plugin.gateway.startAccount(ctx); | ||
| expect(typeof result.stop).toBe("function"); |
There was a problem hiding this comment.
same issue - test expects { stop: function } but should expect a pending Promise
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/synology-chat/src/channel.test.ts
Line: 336
Comment:
same issue - test expects `{ stop: function }` but should expect a pending Promise
How can I resolve this? If you propose a fix, please make it concise.ded2228 to
102b919
Compare
07442e9 to
005e1a2
Compare
|
Rebased on latest
All 22 tests pass locally (vitest 4.0.18, 525ms). |
005e1a2 to
ae2d828
Compare
|
Friendly ping — this PR has been open for a few days now. CI is fully green, mergeable, and Greptile review comments have been addressed (see my previous comment). Would appreciate a maintainer review when you get a chance. Happy to address any feedback. Thanks! |
067d31c to
65dc370
Compare
startAccount must return a Promise that stays pending while the channel
is running. The gateway wraps the return value in Promise.resolve(), and
when it resolves, the gateway thinks the channel crashed and auto-restarts
with exponential backoff (5s → 10s → 20s..., up to 10 attempts).
Replace the synchronous { stop } return with a Promise<void> that resolves
only when ctx.abortSignal fires, keeping the channel alive until shutdown.
Tested on Synology DS923+ with DSM 7.2 — single startup, no restart loop.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
startAccount returns `void | { stop: () => void }` — TypeScript requires
a type guard before accessing .stop on the union type. Added proper checks
in both integration and unit tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ws compat Replace EventEmitter + process.nextTick with Readable stream for request body simulation. The process.nextTick approach caused the test to hang on Windows CI (120s timeout) because events were not reliably delivered to readBody() listeners. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
65dc370 to
36ddef7
Compare
) * fix(synology-chat): prevent restart loop in startAccount startAccount must return a Promise that stays pending while the channel is running. The gateway wraps the return value in Promise.resolve(), and when it resolves, the gateway thinks the channel crashed and auto-restarts with exponential backoff (5s → 10s → 20s..., up to 10 attempts). Replace the synchronous { stop } return with a Promise<void> that resolves only when ctx.abortSignal fires, keeping the channel alive until shutdown. Tested on Synology DS923+ with DSM 7.2 — single startup, no restart loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(synology-chat): add type guards for startAccount return value startAccount returns `void | { stop: () => void }` — TypeScript requires a type guard before accessing .stop on the union type. Added proper checks in both integration and unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(synology-chat): use Readable stream in integration test for Windows compat Replace EventEmitter + process.nextTick with Readable stream for request body simulation. The process.nextTick approach caused the test to hang on Windows CI (120s timeout) because events were not reliably delivered to readBody() listeners. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: stabilize synology gateway account lifecycle (openclaw#23074) (thanks @druide67) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
) * fix(synology-chat): prevent restart loop in startAccount startAccount must return a Promise that stays pending while the channel is running. The gateway wraps the return value in Promise.resolve(), and when it resolves, the gateway thinks the channel crashed and auto-restarts with exponential backoff (5s → 10s → 20s..., up to 10 attempts). Replace the synchronous { stop } return with a Promise<void> that resolves only when ctx.abortSignal fires, keeping the channel alive until shutdown. Tested on Synology DS923+ with DSM 7.2 — single startup, no restart loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(synology-chat): add type guards for startAccount return value startAccount returns `void | { stop: () => void }` — TypeScript requires a type guard before accessing .stop on the union type. Added proper checks in both integration and unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(synology-chat): use Readable stream in integration test for Windows compat Replace EventEmitter + process.nextTick with Readable stream for request body simulation. The process.nextTick approach caused the test to hang on Windows CI (120s timeout) because events were not reliably delivered to readBody() listeners. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: stabilize synology gateway account lifecycle (openclaw#23074) (thanks @druide67) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
) * fix(synology-chat): prevent restart loop in startAccount startAccount must return a Promise that stays pending while the channel is running. The gateway wraps the return value in Promise.resolve(), and when it resolves, the gateway thinks the channel crashed and auto-restarts with exponential backoff (5s → 10s → 20s..., up to 10 attempts). Replace the synchronous { stop } return with a Promise<void> that resolves only when ctx.abortSignal fires, keeping the channel alive until shutdown. Tested on Synology DS923+ with DSM 7.2 — single startup, no restart loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(synology-chat): add type guards for startAccount return value startAccount returns `void | { stop: () => void }` — TypeScript requires a type guard before accessing .stop on the union type. Added proper checks in both integration and unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(synology-chat): use Readable stream in integration test for Windows compat Replace EventEmitter + process.nextTick with Readable stream for request body simulation. The process.nextTick approach caused the test to hang on Windows CI (120s timeout) because events were not reliably delivered to readBody() listeners. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: stabilize synology gateway account lifecycle (openclaw#23074) (thanks @druide67) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
) * fix(synology-chat): prevent restart loop in startAccount startAccount must return a Promise that stays pending while the channel is running. The gateway wraps the return value in Promise.resolve(), and when it resolves, the gateway thinks the channel crashed and auto-restarts with exponential backoff (5s → 10s → 20s..., up to 10 attempts). Replace the synchronous { stop } return with a Promise<void> that resolves only when ctx.abortSignal fires, keeping the channel alive until shutdown. Tested on Synology DS923+ with DSM 7.2 — single startup, no restart loop. * fix(synology-chat): add type guards for startAccount return value startAccount returns `void | { stop: () => void }` — TypeScript requires a type guard before accessing .stop on the union type. Added proper checks in both integration and unit tests. * fix(synology-chat): use Readable stream in integration test for Windows compat Replace EventEmitter + process.nextTick with Readable stream for request body simulation. The process.nextTick approach caused the test to hang on Windows CI (120s timeout) because events were not reliably delivered to readBody() listeners. * fix: stabilize synology gateway account lifecycle (openclaw#23074) (thanks @druide67) --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
Summary
startAccountreturns a synchronous{ stop }object. The gateway wraps the return value inPromise.resolve(), which resolves immediately. The gateway interprets this as a crash and triggers auto-restart.Promise<void>that stays pending untilctx.abortSignalfires, keeping the channel alive until the gateway explicitly requests shutdown.Change Type
Scope
What Changed
extensions/synology-chat/src/channel.ts:gateway.startAccountnow returns a pending Promise that resolves onabortSignal, instead of a synchronous{ stop }objectSecurity Impact
Repro + Verification
auto-restart attempt 1/10,2/10, etc. with exponential backoffHuman Verification
abortSignalCompatibility
🤖 Generated with Claude Code
Greptile Summary
Fixed restart loop in Synology Chat channel by returning a pending Promise from
startAccountinstead of a synchronous object. The gateway wraps all returns inPromise.resolve(), so the old synchronous{ stop }return resolved immediately, triggering exponential backoff restarts.Critical Issues:
{ stop: () => {} }, which will cause the same restart loop{ stop: function }but the fix changes the return type to a pending PromiseWhat works:
abortSignalfiresstartAccountreturns a Promise that stays pending while the channel runsConfidence Score: 2/5
{ stop: () => {} }that causes restart loops. Additionally, the tests are now incorrect and would fail to catch the incomplete fix since they still expect the old return type.Last reviewed commit: ded2228
(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!