[GH-ISSUE #5724] [Bug] Page reload during inspection receives installation doctor advice instead of navigation recovery #3474

Closed
opened 2026-10-31 00:40:53 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 21, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5724

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Process capture or runtime observation

Observed behavior

When a page reloads during inspect-web-page, REA correctly rejects the changing capture, but its message and remediation direct the caller to rea doctor without explaining the navigation. The returned details.reason is already target_changed. Three 2000 ms captures reproduced this diagnostic on the verified baseline, and three more reproduced it after rebuilding current main. After the fixture stopped reloading, the same target and observation duration succeeded without doctor.

Reproduction

Run from a built source checkout with locked dependencies installed. Set CHROME_BIN to a Chrome executable. This executed Bash reproduction serves only a loopback fixture and launches a separate headless Chrome with a disposable profile; it does not collect script sources.

# Run from the built source checkout; CHROME_BIN selects a Chrome executable.
: "${CHROME_BIN:?Set CHROME_BIN to a Chrome executable}"
work_dir="$(mktemp -d)"
mkdir -p "$work_dir"/{site,home,cfg,data,cache,state,tmp}
run_rea() {
  env -i PATH="$PATH" HOME="$work_dir/home" \
    XDG_CONFIG_HOME="$work_dir/cfg" XDG_DATA_HOME="$work_dir/data" \
    XDG_CACHE_HOME="$work_dir/cache" XDG_STATE_HOME="$work_dir/state" \
    TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent \
    node scripts/rea.mjs "$@" --json
}
cat > "$work_dir/site/app.js" <<'EOF'
window.appLoaded = true;
EOF
cat > "$work_dir/site/index.html" <<'EOF'
<!doctype html><html><head><script src="app.js"></script>
<script>setTimeout(function () { location.reload(); }, 600);</script>
</head><body>reload</body></html>
EOF
# Choose two unused loopback ports.
read web_port cdp_port < <(python3 - <<'PY'
import socket
with socket.socket() as a, socket.socket() as b:
    a.bind(('127.0.0.1', 0)); b.bind(('127.0.0.1', 0))
    print(a.getsockname()[1], b.getsockname()[1])
PY
)
python3 -I -m http.server "$web_port" --bind 127.0.0.1 \
  -d "$work_dir/site" > "$work_dir/server.log" 2>&1 &
server_pid=$!
"$CHROME_BIN" --headless=new --user-data-dir="$work_dir/profile" \
  --remote-debugging-port="$cdp_port" --no-first-run \
  --no-default-browser-check --disable-background-networking \
  "http://127.0.0.1:$web_port/index.html" > "$work_dir/chrome.log" 2>&1 &
chrome_pid=$!
cleanup() {
  kill "$chrome_pid" "$server_pid" 2>/dev/null || true
  wait "$chrome_pid" "$server_pid" 2>/dev/null || true
}
trap cleanup EXIT
endpoint="http://127.0.0.1:$cdp_port"
python3 - "$endpoint" "$web_port" <<'PY'
import json, sys, time, urllib.request
for attempt in range(100):
    try:
        with urllib.request.urlopen(sys.argv[1] + '/json/list', timeout=.5) as r:
            targets = json.load(r)
        if any(t.get('url', '').startswith('http://127.0.0.1:' + sys.argv[2]) for t in targets):
            break
    except Exception:
        pass
    time.sleep(.1)
else:
    raise SystemExit('Fixture target did not become available')
PY
sleep 0.5
run_rea list-browser-targets "$endpoint" > "$work_dir/targets.json"
target_id=$(python3 - "$work_dir/targets.json" "$web_port" <<'PY'
import json, sys
with open(sys.argv[1]) as f:
    targets = json.load(f)['normalized_result']['targets']
print(next(t['target_id'] for t in targets if t['url'].startswith('http://127.0.0.1:' + sys.argv[2])))
PY
)
for attempt in 1 2 3; do
  run_rea inspect-web-page "$endpoint" "$target_id" --observation-ms 2000 \
    > "$work_dir/reload-$attempt.json"
  echo "reload $attempt exit $?"
  cat "$work_dir/reload-$attempt.json"
done
# Stop future fixture reloads without changing the selected browser target.
cat > "$work_dir/site/index.html" <<'EOF'
<!doctype html><html><head><script src="app.js"></script>
</head><body>stable</body></html>
EOF
sleep 1
run_rea inspect-web-page "$endpoint" "$target_id" --observation-ms 2000 \
  > "$work_dir/stable.json"
echo "stable exit $?"
python3 - "$work_dir/stable.json" <<'PY'
import json, sys
with open(sys.argv[1]) as f:
    result = json.load(f)
print({'operation': result.get('operation'), 'has_evidence': 'evidence_id' in result})
PY
# The EXIT trap stops only the two recorded processes; output stays in work_dir.

On rebuilt main, the three reload attempts each exited 1 with the diagnostic below; the stable control exited 0 and returned inspection Evidence. The EXIT trap stopped the recorded fixture-server and browser processes.

Expected behavior

A capture rejected because the main frame navigated should explain that navigation and offer relevant recovery, such as retrying once the page settles. Preserve the rejection of mixed-document capture.

At the verified baseline, browser-observation.md documents discarding a changing capture with target_changed; CdpCaptureEvents.ts retains navigation seen during the observation window through final capture. Installation guidance describes doctor as reading host prerequisites and agent configuration. Those diagnostics do not settle a reloading page.

Impact and scope

P2: bounded misleading recovery advice sends callers to installation diagnostics for a known changing-page condition. All three long captures failed on each tested revision. The same-target, same-duration stable control succeeded on each revision without installation repair. A practical workaround is to interpret details.reason and retry after navigation settles; a perpetually reloading page may remain uncapturable. No data loss or security impact was observed. MCP and other browsers/platforms were not tested.

REA version

rea-agents 6.7.0, verified baseline 9d8e7803326a382cbbb3ffdb900f5e93e5442d68. Rechecked after npm run build:cached on fetched main 9258df02cc414112d4fa91a425f0d599f43e2357 (also 6.7.0): still reproduces. package-lock.json is unchanged between these revisions. These are source-checkout reproductions.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1 arm64; Google Chrome 155.0.8059.85 in headless mode.

Target and artifact context

Self-authored HTTP loopback page with one external script and a 600 ms reload timer. Every REA invocation uses isolated HOME, XDG, and temporary directories. Separate disposable browser profile; no external service or real user profile.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

Each of the three rebuilt-main reload captures returned this complete diagnostic and exited 1:

{"code":"target_unavailable","category":"unavailable","message":"Analysis could not complete. Run `rea doctor`, then try again.","retryable":false,"remediation":{"action":"Analysis could not complete. Run `rea doctor`, then try again."},"details":{"operation":"inspect_web_page","reason":"target_changed"}}

The target was discoverable before inspection. Replacing the fixture with the stable page, then inspecting the same target for 2000 ms, exited 0 and returned operation: inspect_web_page with an evidence_id. The verified baseline produced the same three failures and successful stable control. Evidence provenance: observed public CLI; independently verified at baseline and rerun on current main.

Typed outcome or recovery expected

Retain an explicit changing-target failure, explain that navigation invalidated the capture, and give a navigation-specific next step. The existing generic public code may remain if the reason and recovery are clear. A new error code or particular retryability policy is not required by this report; retryable: false is an observed field, not an independently established contract violation. Preserve genuine missing-target/endpoint diagnostics and successful stable-page capture.

Analysis or suspected cause

Source-derived explanation: browserObservationError.ts:42-58 supplies specific messages for several browser failures but falls through for target_changed. analysisErrorPresentation.ts:307 supplies the generic doctor message, and remediation falls back to that message. analysisErrorProjection.ts:182 maps the reason to target_unavailable while retaining it in details. Message selection, code mapping, and retryability have separate producers. No bundled dependency was identified as the cause.

Additional context

#3941 and PR #4040 concern false screenshot navigation detection for unchanged documents. This report uses genuine reloads and accepts capture rejection; it concerns the recovery explanation. Merged #546 intentionally retains observation-window navigation so mixed-document captures are rejected; that guard should remain. Closed #3126 addressed service-worker policy refusal diagnostics, a different reason and trigger.

The script-source reload case has a separate failure signal (unreadable_output, stale Debugger.getScriptSource ID). Script-source collection is omitted here, so restoring that path can still leave the doctor advice shown above. No fix or successful restoration has been tested. Last known good version: NOT_ENOUGH_INFO; none established. Prepared with AI assistance and independent verification.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5724 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Process capture or runtime observation ### Observed behavior When a page reloads during `inspect-web-page`, REA correctly rejects the changing capture, but its message and remediation direct the caller to `rea doctor` without explaining the navigation. The returned `details.reason` is already `target_changed`. Three 2000 ms captures reproduced this diagnostic on the verified baseline, and three more reproduced it after rebuilding current main. After the fixture stopped reloading, the same target and observation duration succeeded without doctor. ### Reproduction Run from a built source checkout with locked dependencies installed. Set `CHROME_BIN` to a Chrome executable. This executed Bash reproduction serves only a loopback fixture and launches a separate headless Chrome with a disposable profile; it does not collect script sources. ```bash # Run from the built source checkout; CHROME_BIN selects a Chrome executable. : "${CHROME_BIN:?Set CHROME_BIN to a Chrome executable}" work_dir="$(mktemp -d)" mkdir -p "$work_dir"/{site,home,cfg,data,cache,state,tmp} run_rea() { env -i PATH="$PATH" HOME="$work_dir/home" \ XDG_CONFIG_HOME="$work_dir/cfg" XDG_DATA_HOME="$work_dir/data" \ XDG_CACHE_HOME="$work_dir/cache" XDG_STATE_HOME="$work_dir/state" \ TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent \ node scripts/rea.mjs "$@" --json } cat > "$work_dir/site/app.js" <<'EOF' window.appLoaded = true; EOF cat > "$work_dir/site/index.html" <<'EOF' <!doctype html><html><head><script src="app.js"></script> <script>setTimeout(function () { location.reload(); }, 600);</script> </head><body>reload</body></html> EOF # Choose two unused loopback ports. read web_port cdp_port < <(python3 - <<'PY' import socket with socket.socket() as a, socket.socket() as b: a.bind(('127.0.0.1', 0)); b.bind(('127.0.0.1', 0)) print(a.getsockname()[1], b.getsockname()[1]) PY ) python3 -I -m http.server "$web_port" --bind 127.0.0.1 \ -d "$work_dir/site" > "$work_dir/server.log" 2>&1 & server_pid=$! "$CHROME_BIN" --headless=new --user-data-dir="$work_dir/profile" \ --remote-debugging-port="$cdp_port" --no-first-run \ --no-default-browser-check --disable-background-networking \ "http://127.0.0.1:$web_port/index.html" > "$work_dir/chrome.log" 2>&1 & chrome_pid=$! cleanup() { kill "$chrome_pid" "$server_pid" 2>/dev/null || true wait "$chrome_pid" "$server_pid" 2>/dev/null || true } trap cleanup EXIT endpoint="http://127.0.0.1:$cdp_port" python3 - "$endpoint" "$web_port" <<'PY' import json, sys, time, urllib.request for attempt in range(100): try: with urllib.request.urlopen(sys.argv[1] + '/json/list', timeout=.5) as r: targets = json.load(r) if any(t.get('url', '').startswith('http://127.0.0.1:' + sys.argv[2]) for t in targets): break except Exception: pass time.sleep(.1) else: raise SystemExit('Fixture target did not become available') PY sleep 0.5 run_rea list-browser-targets "$endpoint" > "$work_dir/targets.json" target_id=$(python3 - "$work_dir/targets.json" "$web_port" <<'PY' import json, sys with open(sys.argv[1]) as f: targets = json.load(f)['normalized_result']['targets'] print(next(t['target_id'] for t in targets if t['url'].startswith('http://127.0.0.1:' + sys.argv[2]))) PY ) for attempt in 1 2 3; do run_rea inspect-web-page "$endpoint" "$target_id" --observation-ms 2000 \ > "$work_dir/reload-$attempt.json" echo "reload $attempt exit $?" cat "$work_dir/reload-$attempt.json" done # Stop future fixture reloads without changing the selected browser target. cat > "$work_dir/site/index.html" <<'EOF' <!doctype html><html><head><script src="app.js"></script> </head><body>stable</body></html> EOF sleep 1 run_rea inspect-web-page "$endpoint" "$target_id" --observation-ms 2000 \ > "$work_dir/stable.json" echo "stable exit $?" python3 - "$work_dir/stable.json" <<'PY' import json, sys with open(sys.argv[1]) as f: result = json.load(f) print({'operation': result.get('operation'), 'has_evidence': 'evidence_id' in result}) PY # The EXIT trap stops only the two recorded processes; output stays in work_dir. ``` On rebuilt main, the three reload attempts each exited 1 with the diagnostic below; the stable control exited 0 and returned inspection Evidence. The EXIT trap stopped the recorded fixture-server and browser processes. ### Expected behavior A capture rejected because the main frame navigated should explain that navigation and offer relevant recovery, such as retrying once the page settles. Preserve the rejection of mixed-document capture. At the verified baseline, [browser-observation.md](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/docs/browser-observation.md#L289) documents discarding a changing capture with `target_changed`; [CdpCaptureEvents.ts](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/browser/CdpCaptureEvents.ts#L90-L99) retains navigation seen during the observation window through final capture. [Installation guidance](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/docs/installation.md#L315-L316) describes doctor as reading host prerequisites and agent configuration. Those diagnostics do not settle a reloading page. ### Impact and scope P2: bounded misleading recovery advice sends callers to installation diagnostics for a known changing-page condition. All three long captures failed on each tested revision. The same-target, same-duration stable control succeeded on each revision without installation repair. A practical workaround is to interpret `details.reason` and retry after navigation settles; a perpetually reloading page may remain uncapturable. No data loss or security impact was observed. MCP and other browsers/platforms were not tested. ### REA version `rea-agents` 6.7.0, verified baseline `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`. Rechecked after `npm run build:cached` on fetched main `9258df02cc414112d4fa91a425f0d599f43e2357` (also 6.7.0): still reproduces. `package-lock.json` is unchanged between these revisions. These are source-checkout reproductions. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1 arm64; Google Chrome 155.0.8059.85 in headless mode. ### Target and artifact context Self-authored HTTP loopback page with one external script and a 600 ms reload timer. Every REA invocation uses isolated HOME, XDG, and temporary directories. Separate disposable browser profile; no external service or real user profile. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence Each of the three rebuilt-main reload captures returned this complete diagnostic and exited 1: ```json {"code":"target_unavailable","category":"unavailable","message":"Analysis could not complete. Run `rea doctor`, then try again.","retryable":false,"remediation":{"action":"Analysis could not complete. Run `rea doctor`, then try again."},"details":{"operation":"inspect_web_page","reason":"target_changed"}} ``` The target was discoverable before inspection. Replacing the fixture with the stable page, then inspecting the same target for 2000 ms, exited 0 and returned `operation: inspect_web_page` with an `evidence_id`. The verified baseline produced the same three failures and successful stable control. Evidence provenance: observed public CLI; independently verified at baseline and rerun on current main. ### Typed outcome or recovery expected Retain an explicit changing-target failure, explain that navigation invalidated the capture, and give a navigation-specific next step. The existing generic public code may remain if the reason and recovery are clear. A new error code or particular retryability policy is not required by this report; `retryable: false` is an observed field, not an independently established contract violation. Preserve genuine missing-target/endpoint diagnostics and successful stable-page capture. ### Analysis or suspected cause Source-derived explanation: [browserObservationError.ts:42-58](https://github.com/morluto/rea/blob/9258df02cc414112d4fa91a425f0d599f43e2357/src/domain/browserObservationError.ts#L42-L58) supplies specific messages for several browser failures but falls through for `target_changed`. [analysisErrorPresentation.ts:307](https://github.com/morluto/rea/blob/9258df02cc414112d4fa91a425f0d599f43e2357/src/domain/analysisErrorPresentation.ts#L307) supplies the generic doctor message, and remediation falls back to that message. [analysisErrorProjection.ts:182](https://github.com/morluto/rea/blob/9258df02cc414112d4fa91a425f0d599f43e2357/src/domain/analysisErrorProjection.ts#L182) maps the reason to `target_unavailable` while retaining it in details. Message selection, code mapping, and retryability have separate producers. No bundled dependency was identified as the cause. ### Additional context #3941 and PR #4040 concern false screenshot navigation detection for unchanged documents. This report uses genuine reloads and accepts capture rejection; it concerns the recovery explanation. Merged #546 intentionally retains observation-window navigation so mixed-document captures are rejected; that guard should remain. Closed #3126 addressed service-worker policy refusal diagnostics, a different reason and trigger. The script-source reload case has a separate failure signal (`unreadable_output`, stale `Debugger.getScriptSource` ID). Script-source collection is omitted here, so restoring that path can still leave the doctor advice shown above. No fix or successful restoration has been tested. Last known good version: NOT_ENOUGH_INFO; none established. Prepared with AI assistance and independent verification.
Author
Owner

@morluto commented on GitHub (Oct 23, 2026):

Fixed in 590bab9ad. Observed navigation retains target_changed and projects navigation recovery advice rather than installation doctor advice; the primary stale source-read cause remains attached. Verified by tests/boundary/browser/cdpCaptureInvalidation.test.ts. Verification used loopback fake CDP and production provider/error projection; no real-browser run is claimed.

<!-- gh-comment-id:6376083051 --> @morluto commented on GitHub (Oct 23, 2026): Fixed in [590bab9ad](https://github.com/morluto/rea/commit/590bab9adac0bca82be0e4c39d7270575fa61a73). Observed navigation retains target_changed and projects navigation recovery advice rather than installation doctor advice; the primary stale source-read cause remains attached. Verified by `tests/boundary/browser/cdpCaptureInvalidation.test.ts`. Verification used loopback fake CDP and production provider/error projection; no real-browser run is claimed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3474
No description provided.