[Bug]: desktop takeover always fails on a default-bind install: tracked-runtime-inspection reads an absent record hostname as "" while resolve reports 127.0.0.1

Chiusa
#8,181 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
25/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Ferma
Stack tecnologico
typescript
Ambito
backend, cli

Direzione di ricerca

Read src/config/tracked-runtime-inspection.ts, src/server/proxy-liveness.ts, and src/cli/stop-approval.ts to trace how an absent hostname is read and compared; inspect the regression tests in linked PR #8182. Reproduce with a default-bind runtime record that omits hostname. Done means takeover approval accepts the valid record and safely stops the runtime, with tests covering the absent-hostname case.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

bug platform service
Client or integration

Codex App

Area

Service lifecycle

Summary

On a default-bind install (config.hostname unset), the desktop's approved takeover never succeeds. Approving the "A runtime is already listening. Take it over?" prompt ends in:

state: taking-over
reason: the guarded stop could not confirm the approved runtime:
        The approved runtime or managing CLI changed: the tracked runtime record changed; no stop was attempted.
runtime process: still running or never started

The old runtime is never signalled, so the desktop waits ~165 s and the port stays occupied. Retrying cannot help: nothing rewrites the record while the old runtime keeps serving.

The cause is two readers of the same runtime-port.json disagreeing about an absent optional field. hostname is optional in the record schema, and a default bind has no configured value to persist, so the writer drops the key. Liveness discovery then defaults it to loopback for its verdict, while the guarded stop defaults it to "" for comparison — so the guard compares "" against "127.0.0.1" and always reports drift.

Reproduction

Environment: Desktop 2.97.0 (notarized), CLI on PATH 2.97.0, the runtime it wanted to stop was 2.96.0 started by the launchd service com.opencodex.proxy, config home ~/.opencodex, port 10100 (default), config.hostname unset.

  1. Install the CLI and start the proxy through the service on a default bind:
    ocx service install && ocx service start
    
  2. Confirm the record has no hostname key:
    cat ~/.opencodex/runtime-port.json
    # {"pid":32333,"port":10100,"attestationSecret":"..."}   <- no hostname
    
  3. Launch the desktop app while that runtime is still running, and approve the takeover prompt.

Expected: the approved runtime is stopped and the desktop starts its own.
Actual: approval-changed / the tracked runtime record changed, runtime still running, and the record's mtime never changes.

Root cause, with hostname optional in the record (src/config/process-state.ts:37, and isValidRuntimePortState accepts undefined):

  • The writer drops it when nothing is configured: src/cli/index.ts:609 writes hostname: bound.config.hostname, which is undefined for a default bind, so JSON.stringify omits the key.
  • src/server/proxy-liveness.ts:525 reads absence as loopback (opts.hostname?.trim() || "127.0.0.1"), so ocx resolve --json reports liveness.hostname: "127.0.0.1" for that record.
  • src/config/tracked-runtime-inspection.ts:150 reads absence as "" (hostname: parsed.hostname ?? "").
  • The desktop forwards the discovered value as the approved expectation (desktop/src-tauri/src/runtime_stop.rs:254, --expect-hostname), and src/cli/stop-approval.ts:147-149 compares the two directly: pid and port match, "" !== "127.0.0.1", so it returns approvalChanged("the tracked runtime record changed").

Observed ocx resolve --json in the failing state (abridged):

{"liveness":{"status":"live","pid":32333,"port":10100,"source":"runtime","hostname":"127.0.0.1","version":"2.96.0"},
 "takeover":{"kind":"supported","protocolVersion":1,"minimumCliVersion":"2.61.0","token":"..."}}

Proposed fix: treat an absent record hostname as unknown rather than an empty host, compare bind scope through probeHostname only when the record carries one, and persist the scope the listener actually bound. Patch with regression tests: #8182.

Version

Desktop 2.97.0; the running proxy it refused to stop was 2.96.0.

Operating system

macOS 27.2 (26B123), arm64 (Apple M4 Max)

Provider and model

No response

Logs or error output
$ cat ~/.opencodex/runtime-port.json
{"pid":32333,"port":10100,"attestationSecret":"..."}

$ ocx resolve --json | jq '.liveness'
{"status":"live","pid":32333,"port":10100,"source":"runtime","hostname":"127.0.0.1","version":"2.96.0"}

# desktop, after approving the takeover
state: taking-over
reason: the guarded stop could not confirm the approved runtime: The approved runtime or managing CLI changed: the tracked runtime record changed; no stop was attempted.
runtime process: still running or never started

Workaround that avoids the takeover entirely: quit the desktop, ocx service stop, ocx service uninstall, reopen the app so it supervises its own runtime, then ocx restore back to re-point Codex at that proxy.

Not a duplicate of #7771: there the Windows Task Scheduler registration survives and the port is never freed. Here no manager stop is ever attempted — the guard refuses first, on a valid record that simply omits an optional field.

Screenshots and supporting files

No response

Redacted configuration

No response

Checks
  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.
Lingua principale
TypeScript
Stelle
16.9k
Fork
1.3k
Merge medio
23h
PR unite (30g)
687

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di lidge-jun/opencodex

Tutte le issue di lidge-jun/opencodex

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.