fix(core): harden context error text collection#4632
Conversation
| } catch { | ||
| const descriptors = Object.getOwnPropertyDescriptors(value); | ||
| return Object.values(descriptors) | ||
| .filter( |
There was a problem hiding this comment.
[Suggestion] The fallback filter checks 'value' in descriptor (data-descriptor check) but does not verify descriptor.enumerable. The primary path (Object.entries) returns only enumerable own properties, but this fallback returns all own data properties including non-enumerable ones (e.g., Error.stack). This creates a behavioral inconsistency — when a single getter throws, non-enumerable properties that were previously invisible suddenly appear in the collected text.
| .filter( | |
| .filter( | |
| (descriptor): descriptor is PropertyDescriptor & { value: unknown } => | |
| 'value' in descriptor && descriptor.enumerable === true, | |
| ) |
— qwen3.7-max via Qwen Code /review
| try { | ||
| return Object.entries(value).map(([, nested]) => nested); | ||
| } catch { | ||
| const descriptors = Object.getOwnPropertyDescriptors(value); |
There was a problem hiding this comment.
[Suggestion] Object.getOwnPropertyDescriptors(value) can itself throw on exotic objects (e.g., Proxy with throwing getOwnPropertyDescriptor trap). Neither this catch block nor collectStrings catches that failure, so the exception propagates uncaught to the public API. Wrapping the fallback in its own try/catch would make the function fully defensive:
| const descriptors = Object.getOwnPropertyDescriptors(value); | |
| function enumerableValues(value: object): unknown[] { | |
| try { | |
| return Object.values(value); | |
| } catch { | |
| try { | |
| const descriptors = Object.getOwnPropertyDescriptors(value); | |
| return Object.values(descriptors) | |
| .filter( | |
| (descriptor): descriptor is PropertyDescriptor & { value: unknown } => | |
| 'value' in descriptor && descriptor.enumerable === true, | |
| ) | |
| .map((descriptor) => descriptor.value); | |
| } catch { | |
| return []; | |
| } | |
| } | |
| } |
Note: this suggestion also uses Object.values() instead of Object.entries().map() (functionally identical, avoids intermediate tuple allocation) and includes the enumerable filter from the other suggestion.
— qwen3.7-max via Qwen Code /review
| expect(info.isExceeded).toBe(false); | ||
| }); | ||
|
|
||
| it('skips accessor properties that throw while collecting error text', () => { |
There was a problem hiding this comment.
[Suggestion] The existing throwing-accessor test covers Error instances, which exercise the safeReadProperty path. Non-Error plain objects with throwing accessors go through a completely different code path — enumerableValues directly — which has no test coverage. Consider adding a test for that path:
it('skips accessor properties that throw on non-Error objects', () => {
const obj: Record<string, unknown> = {};
Object.defineProperty(obj, 'detail', {
enumerable: true,
get() { throw new TypeError('accessor refused'); },
});
Object.defineProperty(obj, 'message', {
enumerable: true,
value: 'context_length_exceeded: too many tokens',
});
const info = getContextLengthExceededInfo(obj);
expect(info.isExceeded).toBe(true);
expect(info.message).toContain('context_length_exceeded');
});— qwen3.7-max via Qwen Code /review
7dcee0b to
6d9c5d9
Compare
|
Updated the branch for the defensive enumeration follow-up. Changes:
Validation:
|
Verification Report — PR #4632Tested locally on: macOS Darwin 25.4.0 / Node.js v22.17.0 Test Results
SummaryAll 6 test plan items pass cleanly. The fix correctly hardens Verified by wenshao |
wenshao
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running. — qwen3.7-max via Qwen Code /review
BZ-D
left a comment
There was a problem hiding this comment.
LGTM. safeReadProperty + enumerableValues 能优雅地处理 DOMException 风格的 throwing getter,fallback 到 getOwnPropertyDescriptors 读 data property 是合理的 best-effort 策略。
…wenLM#4632) Co-authored-by: Jacob Richman <jacob314@gmail.com>
Summary
Fixes #4609.
The context-length classifier walks provider error objects to find useful text. Some provider/network errors can contain DOMException-like objects whose accessors throw when read outside their expected receiver. In the reported stack, that made the diagnostic helper fail while handling the original API connection error.
This patch makes the text collector skip throwing accessors and fall back to data descriptors when
Object.entries()cannot safely enumerate an object. The original error message is still preserved when it is readable.Testing