Skip to content

fix(control-ui): force-refresh agent files#35488

Open
trip-null wants to merge 3 commits intoopenclaw:mainfrom
trip-null:fix/control-ui-agent-files
Open

fix(control-ui): force-refresh agent files#35488
trip-null wants to merge 3 commits intoopenclaw:mainfrom
trip-null:fix/control-ui-agent-files

Conversation

@trip-null
Copy link
Copy Markdown

Summary

  • Problem: The “Core Files” Refresh button only reloads the file list metadata; the textarea keeps the first version that was loaded.
  • Why it matters: Any edits made via CLI/SSH are invisible and saving from the UI silently overwrites the newer file on disk.
  • What changed: After the file list reload completes, onLoadFiles now forces agents.files.get for the active file (dropping stale drafts so the textarea updates).
  • What did NOT change: Gateway/file APIs, other Control UI panels, and CLI tooling remain untouched.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

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)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Ubuntu 24.04
  • Runtime/container: local npm-global OpenClaw gateway
  • Model/provider: n/a
  • Integration/channel: Control UI → Agents → Core Files
  • Relevant config: default agent_web workspace

Steps

  1. Open Control UI → Agents → Core Files and select MEMORY.md.
  2. In a shell: echo "agent-web refresh test" >> /opt/agent-workspace/agent_web/MEMORY.md
  3. Click Refresh in the Core Files card.

Expected

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

  • Trace/log snippets
  • Failing test/log before + passing after
  • Screenshot/recording
  • Perf numbers (if relevant)

Snippet:

echo "agent-web refresh test" >> /opt/agent-workspace/agent_web/MEMORY.md
# (Refresh previously left the UI unchanged; now the new line appears, and Saving preserves it.)

Human Verification (required)

  • Verified scenarios: CLI edit + Control UI Refresh updates textarea; saving from UI after refresh keeps the new content.
  • Edge cases checked: Refresh with no active file, panel switching back to Files.
  • What you did NOT verify: Multi-user concurrency, other agent workspaces.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • Revert the commit or checkout the previous ui/src/ui/app-render.ts.
  • No config/files need changes; symptoms would be missing updates in Core Files again.

Risks and Mitigations


@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 5, 2026

Greptile Summary

This 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

  • onLoadFiles in app-render.ts is promoted from a synchronous one-liner to an async function that awaits both loadAgentFiles and (if a file is active) loadAgentFileContent with { force: true, preserveDraft: false }.

Issue found

  • The active filename is captured before loadAgentFiles runs. loadAgentFiles can set state.agentFileActive = null when the file no longer appears in the refreshed list (external deletion). Because active is not re-validated against the updated list, loadAgentFileContent is still called for the now-absent file. The result is either a silent no-op (null response) or a spurious error string shown in the UI. Adding a state.agentFilesList?.files.some(f => f.name === active) guard before the second await would close this gap.

Confidence Score: 4/5

  • Safe to merge; the edge case involving a deleted file triggers at most a benign extra API call or a transient error string, not data loss.
  • The core logic is correct: the active filename is snapshotted before the async list reload, await is used properly, and the force/preserveDraft options are appropriate for a hard-refresh action. The only issue is a missing guard that checks whether the snapshotted file is still present in the refreshed list before re-fetching its content, which could yield a spurious error message for externally deleted files. All other behavior is unchanged.
  • ui/src/ui/app-render.ts — see the missing file-existence guard in onLoadFiles after the list reload.

Last reviewed commit: 9b0c8a7

Comment on lines +645 to +650
if (active && resolvedAgentId === agentId) {
await loadAgentFileContent(state, agentId, active, {
force: true,
preserveDraft: false,
});
}
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.

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.

Copy link
Copy Markdown

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

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

Comment on lines +643 to +646
const active = state.agentFileActive;
await loadAgentFiles(state, agentId);
if (active && resolvedAgentId === agentId) {
await loadAgentFileContent(state, agentId, active, {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

@trip-null trip-null force-pushed the fix/control-ui-agent-files branch from 9b0c8a7 to 75f5d97 Compare March 5, 2026 05:17
Copy link
Copy Markdown

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

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

onLoadFiles: async (agentId) => {
const activeBefore = state.agentFileActive;
await loadAgentFiles(state, agentId);
if (resolvedAgentId !== agentId) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

@trip-null trip-null force-pushed the fix/control-ui-agent-files branch from 75f5d97 to b140946 Compare March 5, 2026 05:34
Copy link
Copy Markdown

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

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

Comment on lines +657 to +660
await loadAgentFileContent(state, agentId, active, {
force: true,
preserveDraft: false,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

@trip-null trip-null force-pushed the fix/control-ui-agent-files branch from b140946 to dc6a0ae Compare March 5, 2026 05:45
Copy link
Copy Markdown

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

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

Comment on lines +667 to +670
const { [active]: _removed, ...restContents } = state.agentFileContents;
const { [active]: _removedDraft, ...restDrafts } = state.agentFileDrafts;
state.agentFileContents = restContents;
state.agentFileDrafts = restDrafts;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

@trip-null trip-null force-pushed the fix/control-ui-agent-files branch from dc6a0ae to 52518b0 Compare March 5, 2026 05:54
Copy link
Copy Markdown

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

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

Comment on lines +655 to +657
const active = state.agentFileActive ?? activeBefore;
if (active && list?.some((file) => file.name === active)) {
await loadAgentFileContent(state, agentId, active, {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

@trip-null trip-null force-pushed the fix/control-ui-agent-files branch from 52518b0 to 901f4f7 Compare March 5, 2026 06:08
@trip-null
Copy link
Copy Markdown
Author

Reopened with the guarded onLoadFiles logic we discussed—verified locally (CLI edit refresh, missing-file guard, agent switch while refresh running). Let me know if you need anything else before re-review.

Copy link
Copy Markdown

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

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

Comment on lines +80 to +81
if (state.agentFilesList?.agentId && state.agentFilesList.agentId !== agentId) {
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

@trip-null trip-null force-pushed the fix/control-ui-agent-files branch from 24cef03 to a297866 Compare March 9, 2026 13:36
@openclaw-barnacle openclaw-barnacle bot added cli CLI command changes size: S and removed size: XS labels Mar 9, 2026
Copy link
Copy Markdown

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

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

Comment on lines +673 to +676
await loadAgentFileContent(state, agentId, active, {
force: true,
preserveDraft: false,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui cli CLI command changes size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Control UI - Core Files refresh never shows external edits

1 participant