Skip to content

fix(browser): harden daemon runtime lifecycle#543

Open
xiami762 wants to merge 3 commits into
devfrom
fix/browser-runtime-hardening
Open

fix(browser): harden daemon runtime lifecycle#543
xiami762 wants to merge 3 commits into
devfrom
fix/browser-runtime-hardening

Conversation

@xiami762

@xiami762 xiami762 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Harden the flocks browser daemon runtime so named sessions are isolated, same-session startup is serialized, IPC endpoints can be authenticated and identified, and cleanup never relies on signalling an unverified PID.

The PR also improves CDP discovery and diagnostics, and adds regression coverage for concurrent startup, graceful shutdown, stale artifacts, legacy daemon compatibility, and browser profile probing.

Key Changes

Runtime paths and session isolation

  • Add BrowserRuntimePaths as the single resolver for browser runtime artifacts.
  • Honor the existing FLOCKS_ROOT environment variable; the default remains ~/.flocks/browser/.
  • Keep the default session on bu.* and isolate named BU_NAME sessions on bu-<name>.*.
  • Validate session names before using them in socket, port, PID, log, lock, or screenshot paths.
  • Store screenshots and debug-click screenshots per session.

IPC and daemon identity

  • Add a cross-platform advisory lock that is held for the daemon's full lifetime.
  • Use private Unix sockets on POSIX and authenticated loopback endpoint records on Windows.
  • Write PID and Windows endpoint records atomically with private file permissions.
  • Add a versioned ping response containing the session name, PID, instance ID, protocol version, and browser kind.
  • Strictly validate daemon identity before lifecycle operations.
  • Bound IPC response size and normalize malformed or empty responses into protocol errors.

Lifecycle and concurrent startup

  • Replace PID-file signalling with confirmed graceful shutdown through the daemon IPC protocol.
  • Remove stale endpoint and PID artifacts only while holding the matching session lock.
  • Preserve invalid, mismatched, or locked endpoints instead of deleting or signalling them.
  • Support graceful shutdown of the confirmed legacy default daemon while preserving unsupported legacy named endpoints.
  • Discover and stop default and named sessions independently, collecting per-session failures.
  • Retry daemon startup within the original deadline when a spawned child exits with the lock-busy code and the previous lock holder releases the session without publishing an endpoint.

CDP connection and diagnostics

  • Discover browser profile directories for the active operating system only.
  • Validate DevToolsActivePort candidates through /json/version when available; if Chrome returns an HTTP error, fall back to the WebSocket path from the profile record so the daemon's real CDP handshake can proceed without a 30-second setup stall.
  • Make flocks browser --doctor perform a real CDP Browser.getVersion handshake for explicit BU_CDP_URL and BU_CDP_WS targets.
  • Improve UTF-8 handling for daemon logs, runtime records, process output, and diagnostics.
  • Stop the CDP client during daemon shutdown without hiding the original startup failure.
  • Keep automatically-created bootstrap about:blank targets out of the managed-tab registry, and ensure new_tab() creates a distinct managed tab.

Documentation

  • Document FLOCKS_ROOT, named-session artifact names, persistent lock files, and safe --reload cleanup.
  • Replace guidance that manually deletes socket files with the lifecycle-aware reload flow.

Impact Scope

  • User-visible behavior: Concurrent commands for the same BU_NAME now coordinate through the session lock. --reload, setup, service shutdown, diagnostics, logs, and runtime artifact names use the hardened lifecycle behavior. Named sessions no longer share the default daemon artifacts.
  • Compatibility / migration: No public Python API, CLI flag, or required migration is introduced. The default session keeps the existing bu.* filenames. Legacy Windows integer port files remain readable, and a confirmed legacy default daemon can still be stopped gracefully.
  • Configuration / environment: No new required configuration. Existing BU_NAME, FLOCKS_ROOT, BU_CDP_URL, and BU_CDP_WS settings are honored more consistently.
  • Dependencies: No new third-party dependency is added.
  • Performance / resources: One small lock file is retained per session. Startup and shutdown add bounded identity/lock checks; --doctor performs an explicit CDP handshake when a remote endpoint is configured.
  • Security / permissions: Windows endpoint records carry a random IPC token; POSIX sockets and runtime records use private permissions. Lifecycle code no longer sends signals based on an unverified on-disk PID.

Business Logic to Review

  • flocks/browser/_ipc.py: lock ownership, atomic endpoint publication, Windows token injection, strict ping validation, and the invariant that endpoint cleanup requires the matching acquired lock.
  • flocks/browser/lifecycle.py: graceful shutdown ordering, preservation of invalid or locked endpoints, legacy-default confirmation, and per-session failure collection.
  • flocks/browser/admin.py::ensure_daemon: lock-busy retry behavior. After exit code 75, the caller waits for the session lock, releases its probe lock, and respawns within the original deadline; another contender may win the race, in which case the loop continues waiting for the published endpoint.
  • flocks/browser/daemon.py: the daemon acquires the session lock before publishing PID/endpoint state, holds it through CDP cleanup, and removes only its own PID record.
  • CDP discovery: stale, malformed, open-but-non-CDP, and OS-inappropriate profile candidates must not be selected.
  • Compatibility boundary: only the legacy default endpoint is eligible for confirmed graceful shutdown; named legacy or identity-mismatched endpoints are intentionally preserved.

Why This Approach

An on-disk PID is not sufficient process identity because it may be stale or reused. Combining a lifetime session lock with a protocol-level identity makes startup serialization and cleanup deterministic without terminating an unrelated process.

The design keeps existing environment-based session selection and default runtime filenames, while adding isolation and authentication only where required. Legacy support is deliberately narrow so backward compatibility does not weaken the new cleanup invariants.

Test Plan

  • uv run pytest -q tests/browser (106 passed)
  • uv run pytest -q tests/cli/test_service_manager.py::test_stop_all_uses_supervisor_control_api tests/cli/test_service_manager.py::test_stop_all_reports_when_supervisor_is_down tests/cli/test_service_manager.py::test_stop_all_uses_legacy_runtime_ports_for_orphan_cleanup
  • uv run ruff check flocks/browser tests/browser
  • uv run ruff format --check for the changed Python files, excluding the pre-existing unformatted tests/browser/test_helpers.py
  • git diff --check
  • POSIX Unix-socket integration test rerun outside the restricted sandbox

Regression coverage includes named runtime paths, malformed endpoint records, authenticated Windows requests, private POSIX sockets, same-session lock contention, lock-busy startup takeover, strict identity validation, graceful shutdown refusal, legacy compatibility, stale artifact cleanup, CDP client shutdown, OS-specific profiles, DevToolsActivePort fallback after metadata HTTP 404, and real CDP probing.

Compatibility, Migration & Rollback

  • No migration is required; the default session continues to use the existing runtime filenames.
  • Existing named sessions selected through BU_NAME receive isolated artifacts automatically.
  • No new secrets, services, feature flags, or deployment steps are required.
  • Rolling back the PR restores the previous shared/default runtime behavior; any retained .lock files are harmless advisory-lock artifacts and should not be deleted manually.

Out of Scope

  • Changing how inspect pages are opened, including the Windows system-URI / Windows Store behavior.
  • Adding a CLI --name option; BU_NAME remains the session selector.
  • Changing AX Tree, JavaScript evaluation, keyboard input, managed-tab policy beyond bootstrap-tab handling, or CI configuration.

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.

1 participant