fix(memory): use start_line/end_line from QMD/mcporter response#47960
fix(memory): use start_line/end_line from QMD/mcporter response#47960wdskuki wants to merge 3 commits into
Conversation
Greptile SummaryThis PR fixes incorrect line numbers in memory search results when using mcporter for semantic search. The root cause was that Key changes:
Confidence Score: 5/5Safe to merge — the fix is well-scoped, properly validated, and all previously-flagged concerns are addressed The main memory fix correctly validates start_line/end_line with both a typeof === 'number' guard and > 0 guard at the usage site, mirroring the same validation pattern used in parseMcporterResults. The previous thread's concern about missing validation is resolved. No new P1 or P0 issues are introduced. No files require special attention Reviews (2): Last reviewed commit: "Address review comments: remove redundan..." | Re-trigger Greptile |
| if (entry.start_line !== undefined && entry.end_line !== undefined) { | ||
| startLine = entry.start_line; | ||
| endLine = entry.end_line; | ||
| } else { |
There was a problem hiding this comment.
Missing runtime type validation for line numbers
parseMcporterResults (lower in this same file) carefully validates that start_line/end_line are actually numbers (typeof x === "number") before storing them. The search loop here skips that validation and accepts any non-undefined value directly from the QmdQueryResult entry.
parseQmdQueryResultArray does an unchecked as QmdQueryResult[] cast from JSON, so at runtime entry.start_line could be a string like "10" or even 0 / a negative number — all of which would pass the !== undefined guard and silently produce incorrect line numbers. Consider mirroring the same guards used in parseMcporterResults:
| if (entry.start_line !== undefined && entry.end_line !== undefined) { | |
| startLine = entry.start_line; | |
| endLine = entry.end_line; | |
| } else { | |
| if ( | |
| typeof entry.start_line === "number" && | |
| entry.start_line > 0 && | |
| typeof entry.end_line === "number" && | |
| entry.end_line > 0 | |
| ) { | |
| startLine = entry.start_line; | |
| endLine = entry.end_line; | |
| } else { |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/memory/qmd-manager.ts
Line: 852-855
Comment:
**Missing runtime type validation for line numbers**
`parseMcporterResults` (lower in this same file) carefully validates that `start_line`/`end_line` are actually numbers (`typeof x === "number"`) before storing them. The search loop here skips that validation and accepts any non-`undefined` value directly from the `QmdQueryResult` entry.
`parseQmdQueryResultArray` does an unchecked `as QmdQueryResult[]` cast from JSON, so at runtime `entry.start_line` could be a string like `"10"` or even `0` / a negative number — all of which would pass the `!== undefined` guard and silently produce incorrect line numbers. Consider mirroring the same guards used in `parseMcporterResults`:
```suggestion
if (
typeof entry.start_line === "number" &&
entry.start_line > 0 &&
typeof entry.end_line === "number" &&
entry.end_line > 0
) {
startLine = entry.start_line;
endLine = entry.end_line;
} else {
```
How can I resolve this? If you propose a fix, please make it concise.| if (this.closePromise) { | ||
| return this.closePromise; | ||
| } | ||
| this.closed = true; | ||
| this.closePromise = this._doClose(finalText); | ||
| return this.closePromise; |
There was a problem hiding this comment.
Concurrent close() calls with different finalText silently drops the second value
When a second concurrent caller passes a different finalText, the guard returns the in-flight promise created with the first caller's finalText. The second finalText is silently ignored. If this is intentional (e.g. only the first closer's text matters), it's worth documenting so future readers don't assume both values are merged.
| if (this.closePromise) { | |
| return this.closePromise; | |
| } | |
| this.closed = true; | |
| this.closePromise = this._doClose(finalText); | |
| return this.closePromise; | |
| if (this.closePromise) { | |
| // NOTE: If concurrent callers pass different finalText values, only | |
| // the first caller's finalText is used. Subsequent callers share | |
| // the same close operation. | |
| return this.closePromise; | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/feishu/src/streaming-card.ts
Line: 332-337
Comment:
**Concurrent `close()` calls with different `finalText` silently drops the second value**
When a second concurrent caller passes a different `finalText`, the guard returns the in-flight promise created with the *first* caller's `finalText`. The second `finalText` is silently ignored. If this is intentional (e.g. only the first closer's text matters), it's worth documenting so future readers don't assume both values are merged.
```suggestion
if (this.closePromise) {
// NOTE: If concurrent callers pass different finalText values, only
// the first caller's finalText is used. Subsequent callers share
// the same close operation.
return this.closePromise;
}
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e57fe0c22c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| " original_stat = os.lstat(basename, dir_fd=parent_fd)", | ||
| " original_mode = stat.S_IMODE(original_stat.st_mode)", |
There was a problem hiding this comment.
Avoid inheriting symlink mode on atomic writes
If the destination entry is a symlink, os.lstat(..., dir_fd=...) returns the symlink mode (commonly 0777), and that value is then copied to the temp file via os.fchmod before os.replace swaps in a regular file. In that case, writing through a symlink path can unexpectedly produce a world-writable/executable file, which is a permission regression from the previous default 0600 behavior. Mode inheritance should be limited to regular files (or explicitly skip symlink modes).
Useful? React with 👍 / 👎.
e57fe0c to
e014b66
Compare
openclaw#43704) When the agent produces multiple independent final replies, the streaming card incorrectly merged content from the second reply into the first card. This fix uses a closePromise pattern to prevent concurrent close() calls from executing simultaneously. The first close() call stores its promise and subsequent calls return the same promise, ensuring exactly one execution. Changes: - Replace finalDelivered flag with closePromise pattern - Refactor close() to use _doClose() for actual logic - Prevent race condition on concurrent close() calls Fixes openclaw#43704
…-tool (openclaw#44077) When editing a workspace file from the sandbox, the file permissions were being reset to 0600 (from the temp file creation), causing normal OpenClaw file tools to fail with EACCES on the host side. This fix preserves the original file permissions by: 1. Reading the original file's mode before creating temp file 2. Applying the original mode to the temp file before atomic replace 3. Gracefully handling cases where the file doesn't exist or fchmod fails Changes: - Modified write_atomic() to preserve original file permissions - Added try/except blocks for safe permission handling Fixes openclaw#44077
e014b66 to
ed07d87
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ed07d8751a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (!this.state || this.closed) { | ||
| return; | ||
| } | ||
| if (this.closePromise) { | ||
| return this.closePromise; |
There was a problem hiding this comment.
Check in-flight close promise before closed guard
close() sets this.closed = true before assigning this.closePromise, but subsequent calls check this.closed first and return immediately. In the common concurrent-close scenario (two callers invoking close() around the same time), the second caller gets undefined instead of the in-flight promise, so it can continue before the final card update/note/settings PATCH completes. Move the closePromise check ahead of the closed early return so repeated calls can reliably await shutdown.
Useful? React with 👍 / 👎.
…xt ignored logging
ed07d87 to
3cddbe1
Compare
|
@greptile Good point on the runtime validation. I'll mirror the Will push an update with proper validation for |
|
Thanks for the original fix. I reopened this as a maintainer refresh on current main in #58181 so we can land the same behavior on a branch we control. |
When using mcporter for memory search, the snippet may not contain the @@ -start,count header that extractSnippetLines expects. This causes search results to show incorrect line numbers (always starting from line 1).
This fix:
Fixes #47730