fix(control-ui): force-refresh agent files#35488
fix(control-ui): force-refresh agent files#35488trip-null wants to merge 3 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes the "Core Files → Refresh" button so that, after reloading the file-list metadata, it also force-fetches the content of the currently active file — ensuring the textarea reflects any out-of-band edits rather than staying on the first-loaded version. Key changes
Issue found
Confidence Score: 4/5
Last reviewed commit: 9b0c8a7 |
ui/src/ui/app-render.ts
Outdated
| if (active && resolvedAgentId === agentId) { | ||
| await loadAgentFileContent(state, agentId, active, { | ||
| force: true, | ||
| preserveDraft: false, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Stale active used after file list may have removed it
loadAgentFiles can set state.agentFileActive = null when the previously active file no longer appears in the refreshed list (e.g., it was deleted externally between the Refresh clicks). However, because active was captured before loadAgentFiles ran, the if (active && ...) guard remains truthy even for a file that was just removed.
This causes an unnecessary agents.files.get call for a non-existent file. Depending on gateway behaviour the call either returns null (harmless, state untouched) or rejects (sets state.agentFilesError with a spurious error message visible to the user).
Suggested fix — guard on whether the file is still present in the refreshed list:
if (active && resolvedAgentId === agentId && state.agentFilesList?.files.some((f) => f.name === active)) {
await loadAgentFileContent(state, agentId, active, {
force: true,
preserveDraft: false,
});
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/ui/app-render.ts
Line: 645-650
Comment:
**Stale `active` used after file list may have removed it**
`loadAgentFiles` can set `state.agentFileActive = null` when the previously active file no longer appears in the refreshed list (e.g., it was deleted externally between the Refresh clicks). However, because `active` was captured *before* `loadAgentFiles` ran, the `if (active && ...)` guard remains truthy even for a file that was just removed.
This causes an unnecessary `agents.files.get` call for a non-existent file. Depending on gateway behaviour the call either returns `null` (harmless, state untouched) or rejects (sets `state.agentFilesError` with a spurious error message visible to the user).
Suggested fix — guard on whether the file is still present in the refreshed list:
```
if (active && resolvedAgentId === agentId && state.agentFilesList?.files.some((f) => f.name === active)) {
await loadAgentFileContent(state, agentId, active, {
force: true,
preserveDraft: false,
});
}
```
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: 9b0c8a742f
ℹ️ 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".
ui/src/ui/app-render.ts
Outdated
| const active = state.agentFileActive; | ||
| await loadAgentFiles(state, agentId); | ||
| if (active && resolvedAgentId === agentId) { | ||
| await loadAgentFileContent(state, agentId, active, { |
There was a problem hiding this comment.
Skip reload when refresh cleared the active file
onLoadFiles snapshots state.agentFileActive before await loadAgentFiles(...), then unconditionally uses that snapshot afterward. If an external edit deleted/renamed the active file, loadAgentFiles clears state.agentFileActive, but this code still calls loadAgentFileContent for the stale filename, which surfaces a missing-file error during an otherwise successful refresh and can repopulate stale cached content. Re-check current active file (or refreshed list membership) after the list reload before issuing agents.files.get.
Useful? React with 👍 / 👎.
9b0c8a7 to
75f5d97
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 75f5d97311
ℹ️ 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".
ui/src/ui/app-render.ts
Outdated
| onLoadFiles: async (agentId) => { | ||
| const activeBefore = state.agentFileActive; | ||
| await loadAgentFiles(state, agentId); | ||
| if (resolvedAgentId !== agentId) { |
There was a problem hiding this comment.
Guard refresh follow-up with current selected agent
This post-refresh guard uses resolvedAgentId captured when the render callback was created, not the current selection after await loadAgentFiles(...). If a user clicks Refresh for agent A and switches to agent B before the list request returns, this stale check still passes and the code can call loadAgentFileContent for A, repopulating file caches after onSelectAgent cleared them. Since agentFileContents/agentFileDrafts are keyed only by filename in ui/src/ui/controllers/agent-files.ts, selecting the same core filename in B (for example MEMORY.md) can reuse A’s cached content and lead to overwriting B with the wrong text on save.
Useful? React with 👍 / 👎.
75f5d97 to
b140946
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b140946436
ℹ️ 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".
| await loadAgentFileContent(state, agentId, active, { | ||
| force: true, | ||
| preserveDraft: false, | ||
| }); |
There was a problem hiding this comment.
Revalidate agent selection after forced content reload
This forced agents.files.get runs asynchronously after the pre-check at onLoadFiles, but the selected agent can still change before the request resolves; onSelectAgent clears agentFileContents/agentFileDrafts and then loadAgentFiles for the new agent is skipped while agentFilesLoading is true, so when the old request completes it repopulates filename-keyed caches with the previous agent’s content. Because loadAgentFileContent short-circuits on Object.hasOwn(state.agentFileContents, name), selecting the same filename (for example MEMORY.md) in the new agent can show stale content and save it to the wrong workspace.
Useful? React with 👍 / 👎.
b140946 to
dc6a0ae
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc6a0ae14d
ℹ️ 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".
ui/src/ui/app-render.ts
Outdated
| const { [active]: _removed, ...restContents } = state.agentFileContents; | ||
| const { [active]: _removedDraft, ...restDrafts } = state.agentFileDrafts; | ||
| state.agentFileContents = restContents; | ||
| state.agentFileDrafts = restDrafts; |
There was a problem hiding this comment.
Avoid deleting new agent cache entries by filename
This cleanup runs after the forced refresh request for the previous agent resolves, and it deletes cache entries solely by active filename when the selected agent changed. Because agentFileContents/agentFileDrafts are filename-keyed, if the newly selected agent has already loaded the same filename (for example MEMORY.md) before this stale request completes, these lines remove the new agent’s valid content and can leave the editor blank; a subsequent save can then write incorrect text. The cleanup should be scoped by agent identity (or per-agent cache keys), not just filename.
Useful? React with 👍 / 👎.
dc6a0ae to
52518b0
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 52518b0768
ℹ️ 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".
| const active = state.agentFileActive ?? activeBefore; | ||
| if (active && list?.some((file) => file.name === active)) { | ||
| await loadAgentFileContent(state, agentId, active, { |
There was a problem hiding this comment.
Skip forced content fetch after list refresh errors
After onLoadFiles awaits loadAgentFiles, it always proceeds to loadAgentFileContent for the active file when the previous list is still present. If agents.files.list fails (for example transiently) but agents.files.get succeeds, this follow-up call clears agentFilesError and leaves users with a stale file list while hiding the refresh failure, so they can keep editing against outdated metadata without any visible error.
Useful? React with 👍 / 👎.
52518b0 to
901f4f7
Compare
|
Reopened with the guarded |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 24cef03f51
ℹ️ 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 (state.agentFilesList?.agentId && state.agentFilesList.agentId !== agentId) { | ||
| return; |
There was a problem hiding this comment.
Reject stale file payloads after agent switches
This guard only blocks stale agents.files.get responses when state.agentFilesList is already set to a different agent, but onSelectAgent clears state.agentFilesList to null before loading the next agent (ui/src/ui/app-render.ts), so a forced refresh fetch from the previous agent can still land during that window and repopulate filename-keyed caches with old content. In that case, opening/saving the same filename (for example MEMORY.md) under the newly selected agent can use the previous agent’s text.
Useful? React with 👍 / 👎.
24cef03 to
a297866
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 652245370e
ℹ️ 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".
| await loadAgentFileContent(state, agentId, active, { | ||
| force: true, | ||
| preserveDraft: false, | ||
| }); |
There was a problem hiding this comment.
Block saves during forced refresh reload
This new refresh path always issues loadAgentFileContent(..., { force: true, preserveDraft: false }) for the active file, but saves are still allowed while that fetch is in flight because save logic does not check agentFilesLoading. If a user refreshes with unsaved edits and clicks Save before the forced agents.files.get returns, the later get response can overwrite agentFileContents/agentFileDrafts with pre-save disk content, so the editor shows stale text and a subsequent save can revert the file. Please gate save actions (or drop stale get responses) while the forced refresh reload is pending.
Useful? React with 👍 / 👎.
Summary
onLoadFilesnow forcesagents.files.getfor the active file (dropping stale drafts so the textarea updates).Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
Core Files → Refresh now reloads the textarea with the latest on-disk content after CLI edits (instead of staying stale). No other UI changes.
Security Impact (required)
Repro + Verification
Environment
Steps
MEMORY.md.echo "agent-web refresh test" >> /opt/agent-workspace/agent_web/MEMORY.mdExpected
The text area shows the appended line (or provides a way to reload file contents).
Actual
Before the fix, the textarea stayed on the original content until the entire Control UI was hard-refreshed. After this PR, the new line appears immediately.
Evidence
Snippet:
Human Verification (required)
Compatibility / Migration
Failure Recovery (if this breaks)
ui/src/ui/app-render.ts.Risks and Mitigations