Skip to content

feat(agent-server): add include_hidden to file browser endpoints#3740

Merged
xingyaoww merged 2 commits into
mainfrom
feature/file-browser-include-hidden
Jun 19, 2026
Merged

feat(agent-server): add include_hidden to file browser endpoints#3740
xingyaoww merged 2 commits into
mainfrom
feature/file-browser-include-hidden

Conversation

@xingyaoww

@xingyaoww xingyaoww commented Jun 15, 2026

Copy link
Copy Markdown
Member

HUMAN:

  • A human has tested these changes.
image

AGENT:


Why

The agent-server file browser endpoints GET /api/file/search_subdirs and GET /api/file/home unconditionally skip dot-entries (entry.name.startswith(".")). GUIs built on these endpoints (e.g. agent-canvas' workspace picker) therefore cannot browse or select hidden directories such as ~/.config, ~/.ssh, or a hidden repo. There was no server-side way to opt in to seeing them.

This is the backend half of "Option B" for OpenHands/agent-canvas — letting users add a hidden directory as a workspace via a "Show hidden folders" toggle.

Summary

  • Added an optional include_hidden query param (default False) to GET /api/file/search_subdirs. When true, dot-directories are listed; files and symlinks are still skipped.
  • Added the same include_hidden param to GET /api/file/home so hidden top-level directories can appear in favorites.
  • Default behavior is unchanged (include_hidden=False), so this is fully backward-compatible — no existing client is affected.

Issue Number

Relates to OpenHands/agent-canvas hidden-workspace support (Option B).

How to Test

uv run pytest tests/agent_server/test_file_router.py -q

Manual:

# default: hidden dirs omitted
curl "http://localhost:8000/api/file/search_subdirs?path=$HOME"
# opt in: hidden dirs included
curl "http://localhost:8000/api/file/search_subdirs?path=$HOME&include_hidden=true"

Evidence (local run):

  • uv run pytest tests/agent_server/test_file_router.py -q28 passed (incl. new test_search_subdirs_include_hidden_lists_dot_directories and test_get_home_include_hidden_lists_hidden_favorites).
  • uv run ruff check / ruff format --check → clean.
  • Pre-commit (ruff, pycodestyle, pyright, import rules) → all passed on commit.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

Follow-up wiring (separate PRs):

  1. @openhands/typescript-client: forward include_hidden from FileClient.searchSubdirectories (extend FileSearchSubdirsOptions) and accept it on getHome; release a new version.
  2. agent-canvas: bump the client pin and add a "Show hidden folders" toggle that threads includeHidden through useSearchSubdirs / useHomeDirectory.

This PR was created by an AI agent (OpenHands) on behalf of the user.


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:0effd62-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-0effd62-python \
  ghcr.io/openhands/agent-server:0effd62-python

All tags pushed for this build

ghcr.io/openhands/agent-server:0effd62-golang-amd64
ghcr.io/openhands/agent-server:0effd62b7442dedb22430b7e7881e810cd3d907d-golang-amd64
ghcr.io/openhands/agent-server:feature-file-browser-include-hidden-golang-amd64
ghcr.io/openhands/agent-server:0effd62-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:0effd62-golang-arm64
ghcr.io/openhands/agent-server:0effd62b7442dedb22430b7e7881e810cd3d907d-golang-arm64
ghcr.io/openhands/agent-server:feature-file-browser-include-hidden-golang-arm64
ghcr.io/openhands/agent-server:0effd62-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:0effd62-java-amd64
ghcr.io/openhands/agent-server:0effd62b7442dedb22430b7e7881e810cd3d907d-java-amd64
ghcr.io/openhands/agent-server:feature-file-browser-include-hidden-java-amd64
ghcr.io/openhands/agent-server:0effd62-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:0effd62-java-arm64
ghcr.io/openhands/agent-server:0effd62b7442dedb22430b7e7881e810cd3d907d-java-arm64
ghcr.io/openhands/agent-server:feature-file-browser-include-hidden-java-arm64
ghcr.io/openhands/agent-server:0effd62-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:0effd62-python-amd64
ghcr.io/openhands/agent-server:0effd62b7442dedb22430b7e7881e810cd3d907d-python-amd64
ghcr.io/openhands/agent-server:feature-file-browser-include-hidden-python-amd64
ghcr.io/openhands/agent-server:0effd62-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:0effd62-python-arm64
ghcr.io/openhands/agent-server:0effd62b7442dedb22430b7e7881e810cd3d907d-python-arm64
ghcr.io/openhands/agent-server:feature-file-browser-include-hidden-python-arm64
ghcr.io/openhands/agent-server:0effd62-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:0effd62-golang
ghcr.io/openhands/agent-server:0effd62b7442dedb22430b7e7881e810cd3d907d-golang
ghcr.io/openhands/agent-server:feature-file-browser-include-hidden-golang
ghcr.io/openhands/agent-server:0effd62-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:0effd62-java
ghcr.io/openhands/agent-server:0effd62b7442dedb22430b7e7881e810cd3d907d-java
ghcr.io/openhands/agent-server:feature-file-browser-include-hidden-java
ghcr.io/openhands/agent-server:0effd62-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:0effd62-python
ghcr.io/openhands/agent-server:0effd62b7442dedb22430b7e7881e810cd3d907d-python
ghcr.io/openhands/agent-server:feature-file-browser-include-hidden-python
ghcr.io/openhands/agent-server:0effd62-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., 0effd62-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 0effd62-python-amd64) are also available if needed

The folder/file browser endpoints (/api/file/search_subdirs and
/api/file/home) unconditionally skip dot-entries, so GUIs built on
them cannot browse or select hidden directories (e.g. ~/.config).

Add an optional include_hidden query param (default False, so existing
behavior is unchanged) to both endpoints. When true, dot-directories
are listed; files and symlinks are still skipped.

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server
   file_router.py1592882%79–81, 117–119, 132–134, 172–173, 177–178, 186, 188–193, 196–198, 284–285, 289–290, 322
TOTAL327811416856% 

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ QA Report: PASS WITH ISSUES

Functional QA passed: the running agent-server now exposes hidden directories only when include_hidden=true, while default behavior still omits them.

Does this PR achieve its stated goal?

Yes. I started the actual agent-server and made real HTTP requests against /api/file/search_subdirs and /api/file/home before and after the PR. On origin/main, adding include_hidden=true still omitted dot-directories; on commit ad0f28e8, the same requests returned .hidden_repo and .config only with the opt-in flag, while files and symlinks remained excluded.

Phase Result
Environment Setup make build completed and installed the uv workspace dependencies.
CI Status ⚠️ 16 checks succeeded, 12 were still in progress, and Validate PR description failed.
Functional Verification ✅ Real HTTP requests verified both changed endpoints and unchanged defaults.
Functional Verification

Test 1: /api/file/search_subdirs hidden directory opt-in

Step 1 — Reproduce / establish baseline without the fix:
Checked out origin/main, created a real directory containing projects/, .hidden_repo/, README.md, and a symlink, then started the server with:

HOME=/tmp/file-browser-qa-base/home OPENHANDS_SUPPRESS_BANNER=1   uv run agent-server --host 127.0.0.1 --port 8765

Ran:

curl 'http://127.0.0.1:8765/api/file/search_subdirs?path=%2Ftmp%2Ffile-browser-qa-base%2Fsearch'
curl 'http://127.0.0.1:8765/api/file/search_subdirs?path=%2Ftmp%2Ffile-browser-qa-base%2Fsearch&include_hidden=true'

Observed both responses returned only the visible directory:

{
  "items": [
    {"name": "projects", "path": "/tmp/file-browser-qa-base/search/projects"}
  ],
  "next_page_id": null
}

This establishes the prior behavior: even when a client sent include_hidden=true, hidden directories were not listed.

Step 2 — Apply the PR's changes:
Checked out ad0f28e881ee8e6c8182bb4257f31f2370046357 and recreated the same fixture shape under /tmp/file-browser-qa-pr/search.

Step 3 — Re-run with the fix in place:
Ran the equivalent requests against the PR server. Default behavior still omitted hidden entries:

{
  "items": [
    {"name": "projects", "path": "/tmp/file-browser-qa-pr/search/projects"}
  ],
  "next_page_id": null
}

With include_hidden=true, the hidden directory appeared and the file/symlink stayed excluded:

{
  "items": [
    {"name": ".hidden_repo", "path": "/tmp/file-browser-qa-pr/search/.hidden_repo"},
    {"name": "projects", "path": "/tmp/file-browser-qa-pr/search/projects"}
  ],
  "next_page_id": null
}

This confirms the new opt-in behavior works and the backward-compatible default is preserved.

Test 2: /api/file/home hidden favorite opt-in

Step 1 — Reproduce / establish baseline without the fix:
On origin/main, started the server with HOME=/tmp/file-browser-qa-base/home where the home directory contained Documents/, .config/, readme.txt, and a symlink.

Ran:

curl 'http://127.0.0.1:8765/api/file/home'
curl 'http://127.0.0.1:8765/api/file/home?include_hidden=true'

Observed both responses returned only Documents in favorites:

{
  "home": "/tmp/file-browser-qa-base/home",
  "favorites": [
    {"label": "Documents", "path": "/tmp/file-browser-qa-base/home/Documents"}
  ],
  "locations": [{"label": "/", "path": "/"}]
}

This confirms the old endpoint could not opt into hidden home favorites.

Step 2 — Apply the PR's changes:
Checked out ad0f28e881ee8e6c8182bb4257f31f2370046357 and recreated the same fixture shape under /tmp/file-browser-qa-pr/home.

Step 3 — Re-run with the fix in place:
Default behavior still returned only the visible favorite:

{
  "home": "/tmp/file-browser-qa-pr/home",
  "favorites": [
    {"label": "Documents", "path": "/tmp/file-browser-qa-pr/home/Documents"}
  ],
  "locations": [{"label": "/", "path": "/"}]
}

With include_hidden=true, .config appeared before Documents, while the file and symlink stayed excluded:

{
  "home": "/tmp/file-browser-qa-pr/home",
  "favorites": [
    {"label": ".config", "path": "/tmp/file-browser-qa-pr/home/.config"},
    {"label": "Documents", "path": "/tmp/file-browser-qa-pr/home/Documents"}
  ],
  "locations": [{"label": "/", "path": "/"}]
}

This confirms /api/file/home now supports the same hidden-directory opt-in behavior as the search endpoint.

Issues Found

  • 🟡 Non-functional CI issue: Validate PR description is failing with: A human must check A human has tested these changes. and Add a short human-written note between HUMAN: and the human-tested checkbox. Per repo policy, a human author needs to update those human-only PR description fields.

No functional issues found in the changed behavior.

This QA review was created by an AI agent (OpenHands) on behalf of the user.

all-hands-bot commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Review complete.

This review was performed through OpenHands Cloud Automation. You can log in and view the conversation here.

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Taste: 🟢 Good taste — clean, minimal, backward-compatible feature addition.

Summary

This PR adds an optional include_hidden query parameter (default False) to GET /api/file/search_subdirs and GET /api/file/home, letting GUIs opt in to listing dot-directories (e.g. ~/.config, ~/.ssh) without changing the default for existing clients.

Why this is good

  • Real problem, simple solution. The dot-skip was hard-coded; the fix is a single boolean flag that flips the existing condition. No new data structures, no new abstractions — the cleanest possible answer to the request.
  • Backward compatible by construction. Default is False on both endpoints, so no existing client changes behavior. The Python and OpenAPI breakage checks both passed in CI.
  • No new attack surface. The flag only relaxes an existing filter. os.scandir still operates on a server-validated absolute path; entry.is_dir(follow_symlinks=False) still rejects symlinks; absolute-path / exists / is-dir validation is unchanged. A client that wanted ~/.ssh could already set path=$HOME and get the visible list — this just lets them also see the hidden ones.
  • Real tests. Two new tests use a real tmp_path filesystem and the real FastAPI TestClient, asserting on actual JSON output (not mocks). The default-false paths are already covered by the existing tests.
  • Coherent docstrings. Updated inline rather than left as stale documentation.

CI: all 30 checks green (build, test, OpenAPI breakage, Python API breakage, coverage, QA). The only "blocked" state is the missing human-tester checkbox in the PR template, which is a process gate, not a code issue.

Approving.


This review was generated by an AI agent (OpenHands) on behalf of the user through OpenHands Automation. View conversation

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

✅ QA Report: PASS

The file browser API now exposes hidden directories only when include_hidden=true, while preserving the default hidden-directory filtering behavior.

Does this PR achieve its stated goal?

Yes. I ran the real agent-server on the base commit and on the PR commit, then queried the affected HTTP endpoints with a home directory containing visible dirs, hidden dirs, hidden files, normal files, and a symlink. On base, include_hidden=true still omitted .config and .hidden_repo; on the PR commit, the same requests included hidden directories for both /api/file/search_subdirs and /api/file/home, while default requests still returned only projects and files/symlinks remained absent from directory results.

Phase Result
Environment Setup make build completed and installed the uv workspace packages.
CI Status ⚠️ Most checks were green when checked; Validate PR description was failing and this QA workflow was in progress.
Functional Verification ✅ Real HTTP requests to both changed endpoints matched the PR description.
Functional Verification

Test 1: /api/file/search_subdirs hidden-directory opt-in

Step 1 — Reproduce / establish baseline without the fix:
Checked out base ea4e0cc5, created a temporary home containing projects/, .config/, .hidden_repo/, .hidden_file, readme.txt, and a symlink to projects/, then started the real server:

HOME=/tmp/oh-file-browser-home.pLnqy8 uv run python -m openhands.agent_server --host 127.0.0.1 --port 8765

Ran:

curl -sS "http://127.0.0.1:8765/api/file/search_subdirs?path=/tmp/oh-file-browser-home.pLnqy8&include_hidden=true" | jq '{items: [.items[] | {name, path}], next_page_id}'

Output:

{
  "items": [
    {
      "name": "projects",
      "path": "/tmp/oh-file-browser-home.pLnqy8/projects"
    }
  ],
  "next_page_id": null
}

This establishes the prior behavior: even with include_hidden=true, hidden directories were not visible to the file browser endpoint.

Step 2 — Apply the PR's changes:
Checked out PR commit ad0f28e8 and started the real server against the same temporary home:

HOME=/tmp/oh-file-browser-home.pLnqy8 uv run python -m openhands.agent_server --host 127.0.0.1 --port 8766

Step 3 — Re-run with the fix in place:
Default request:

curl -sS "http://127.0.0.1:8766/api/file/search_subdirs?path=/tmp/oh-file-browser-home.pLnqy8" | jq '{items: [.items[] | {name, path}], next_page_id}'

Output:

{
  "items": [
    {
      "name": "projects",
      "path": "/tmp/oh-file-browser-home.pLnqy8/projects"
    }
  ],
  "next_page_id": null
}

Opt-in request:

curl -sS "http://127.0.0.1:8766/api/file/search_subdirs?path=/tmp/oh-file-browser-home.pLnqy8&include_hidden=true" | jq '{items: [.items[] | {name, path}], next_page_id}'

Output:

{
  "items": [
    {
      "name": ".config",
      "path": "/tmp/oh-file-browser-home.pLnqy8/.config"
    },
    {
      "name": ".hidden_repo",
      "path": "/tmp/oh-file-browser-home.pLnqy8/.hidden_repo"
    },
    {
      "name": "projects",
      "path": "/tmp/oh-file-browser-home.pLnqy8/projects"
    }
  ],
  "next_page_id": null
}

This confirms the new opt-in behavior works and the default behavior remains backward-compatible. The hidden file, normal file, and symlink were not returned.

Test 2: /api/file/home hidden favorites opt-in

Step 1 — Reproduce / establish baseline without the fix:
On base ea4e0cc5, with the same temporary home, ran:

curl -sS "http://127.0.0.1:8765/api/file/home?include_hidden=true" | jq '{home, favorites: [.favorites[] | {label, path}], locations}'

Output:

{
  "home": "/tmp/oh-file-browser-home.pLnqy8",
  "favorites": [
    {
      "label": "projects",
      "path": "/tmp/oh-file-browser-home.pLnqy8/projects"
    }
  ],
  "locations": [
    {
      "label": "/",
      "path": "/"
    }
  ]
}

This establishes the old behavior for home favorites: hidden top-level directories were omitted even when the client attempted to request them.

Step 2 — Apply the PR's changes:
Checked out PR commit ad0f28e8 and used the same running PR server setup from Test 1.

Step 3 — Re-run with the fix in place:
Default request:

curl -sS "http://127.0.0.1:8766/api/file/home" | jq '{home, favorites: [.favorites[] | {label, path}], locations}'

Output:

{
  "home": "/tmp/oh-file-browser-home.pLnqy8",
  "favorites": [
    {
      "label": "projects",
      "path": "/tmp/oh-file-browser-home.pLnqy8/projects"
    }
  ],
  "locations": [
    {
      "label": "/",
      "path": "/"
    }
  ]
}

Opt-in request:

curl -sS "http://127.0.0.1:8766/api/file/home?include_hidden=true" | jq '{home, favorites: [.favorites[] | {label, path}], locations}'

Output:

{
  "home": "/tmp/oh-file-browser-home.pLnqy8",
  "favorites": [
    {
      "label": ".config",
      "path": "/tmp/oh-file-browser-home.pLnqy8/.config"
    },
    {
      "label": ".hidden_repo",
      "path": "/tmp/oh-file-browser-home.pLnqy8/.hidden_repo"
    },
    {
      "label": "projects",
      "path": "/tmp/oh-file-browser-home.pLnqy8/projects"
    }
  ],
  "locations": [
    {
      "label": "/",
      "path": "/"
    }
  ]
}

This confirms /api/file/home now supports the same hidden-directory opt-in behavior as search_subdirs, with default behavior unchanged.

Issues Found

None in the changed behavior. CI note: the PR description check was failing because the human-only PR description fields were not completed, which needs a human update rather than an agent edit.

This QA review was generated by an AI agent (OpenHands) on behalf of the user.

@xingyaoww xingyaoww enabled auto-merge (squash) June 19, 2026 21:27
@xingyaoww xingyaoww merged commit 23ac517 into main Jun 19, 2026
29 checks passed
@xingyaoww xingyaoww deleted the feature/file-browser-include-hidden branch June 19, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants