Skip to content

fix(memory): use start_line/end_line from QMD/mcporter response#47960

Closed
wdskuki wants to merge 3 commits into
openclaw:mainfrom
wdskuki:fix/memory-search-snippet-lines
Closed

fix(memory): use start_line/end_line from QMD/mcporter response#47960
wdskuki wants to merge 3 commits into
openclaw:mainfrom
wdskuki:fix/memory-search-snippet-lines

Conversation

@wdskuki

@wdskuki wdskuki commented Mar 16, 2026

Copy link
Copy Markdown

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:

  1. Adds start_line and end_line fields to QmdQueryResult type
  2. Extracts these fields from mcporter response when available
  3. Falls back to parsing @@ -start,count header for direct QMD CLI results

Fixes #47730

@wdskuki
wdskuki requested a review from a team as a code owner March 16, 2026 06:36
@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling channel: feishu Channel integration: feishu size: S labels Mar 16, 2026
@greptile-apps

greptile-apps Bot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes incorrect line numbers in memory search results when using mcporter for semantic search. The root cause was that extractSnippetLines relied on @@ -start,count diff headers that mcporter responses don't include, causing all results to show line 1.

Key changes:

  • Adds start_line/end_line optional fields to QmdQueryResult type
  • parseMcporterResults now extracts and stores these fields from the mcporter response, with proper typeof === \"number\" validation
  • The search result loop now prefers start_line/end_line from the entry (when they are valid positive numbers) and falls back to extractSnippetLines for direct QMD CLI results
  • FeishuStreamingSession.close() is split into a guard + _doClose() to prevent concurrent execution via a closePromise field
  • fs-bridge-mutation-helper.ts adds permission preservation when atomically replacing files in the sandbox

Confidence Score: 5/5

Safe 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

Comment thread src/memory/qmd-manager.ts Outdated
Comment on lines +852 to +855
if (entry.start_line !== undefined && entry.end_line !== undefined) {
startLine = entry.start_line;
endLine = entry.end_line;
} else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
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.

Comment on lines +332 to +337
if (this.closePromise) {
return this.closePromise;
}
this.closed = true;
this.closePromise = this._doClose(finalText);
return this.closePromise;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +92 to +93
" original_stat = os.lstat(basename, dir_fd=parent_fd)",
" original_mode = stat.S_IMODE(original_stat.st_mode)",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@wdskuki
wdskuki force-pushed the fix/memory-search-snippet-lines branch from e57fe0c to e014b66 Compare March 16, 2026 07:48
wdskuki added 2 commits March 16, 2026 16:48
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
@wdskuki
wdskuki force-pushed the fix/memory-search-snippet-lines branch from e014b66 to ed07d87 Compare March 16, 2026 08:48

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines 393 to +397
if (!this.state || this.closed) {
return;
}
if (this.closePromise) {
return this.closePromise;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@wdskuki
wdskuki force-pushed the fix/memory-search-snippet-lines branch from ed07d87 to 3cddbe1 Compare March 18, 2026 00:18
@wdskuki

wdskuki commented Mar 30, 2026

Copy link
Copy Markdown
Author

@greptile Good point on the runtime validation. I'll mirror the typeof === "number" guards from parseMcporterResults in the search loop to ensure type safety.

Will push an update with proper validation for start_line and end_line.

@vincentkoc

Copy link
Copy Markdown
Member

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.

@vincentkoc vincentkoc closed this Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling channel: feishu Channel integration: feishu size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Memory search shows only beginning of files instead of relevant snippets

2 participants