Repository navigation
Add local background service helpers (opencode.service, from_service) - #10
Merged
Merged
Conversation
shrijayan
force-pushed
the
claude/service-helpers
branch
2 times, most recently
from
October 22, 2026 10:28
addab3e to
b17fce7
Compare
opencode.service gets discover, ensure, stop and headers, with async twins, matching packages/client/src/promise/service.ts at OpenCode v2.1.2: the registration-file contract, health classification, version replacement, contender race handling and timing, and persistent terminal handoff. Opencode.from_service and AsyncOpencode.from_service connect to it. Includes unit tests with a fake service process, a live test in temporary XDG dirs, a README section and docs pages.
BasicAuth and the registration record hide their password from repr, so endpoints can be logged safely. The service command is resolved from PATH entries only: shutil.which on Windows also tries the current directory, which Node's spawn does not.
Falling back to the bare name let Windows CreateProcess search the current directory. Look the command up on the child's PATH, as Node's spawn does, and raise (surfaced as "Failed to start server") when it is missing.
shrijayan
force-pushed
the
claude/service-helpers
branch
from
October 22, 2026 11:07
c2073b5 to
c0f7fc5
Compare
shrijayan
enabled auto-merge (rebase)
October 22, 2026 11:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ports the OpenCode TS client's local-service helpers (
packages/client/src/{service,service-probe,service-contender,service-timing,service-version,promise/service,pty-handoff}.tsatv2.1.2) tosrc/opencode/_service.py, exposed as theopencode.servicemodule.Not for self-merge: merges to
mainrelease to PyPI, so the owner merges.API
service.discover(file=None, version=None) -> Endpoint | None,service.ensure(file, version, *, command, env, on_start) -> Endpoint,service.stop(file, *, pty="clear"|"handoff"),service.headers(endpoint),service.default_file();async_discover/async_ensure/async_stop.Endpoint(url, auth: BasicAuth | None)frozen dataclass,ServiceError(OpencodeError);EndpointandServiceErrorare also top-level exports.Opencode.from_service(...)andawait AsyncOpencode.from_service(...)(start=Falseuses discover and raisesServiceErrorif nothing matches).Shape: module functions, not a
Serviceclass — the operations share no state, so a class would only be a namespace;from opencode import service; service.ensure()reads the same as TSService.ensure(). Async twins useasync_prefixes and a real async implementation (httpx.AsyncClient, anyio sleeps) sharing all decision logic with the sync one.Matched TS behavior
$XDG_STATE_HOME/opencode/service.json(default~/.local/state), probeGET /api/infowith basic authopencode:<password>; 404 → incompatible, pid/version mismatch → not this service, 2xx ready / 500 failed / other waiting; timeouts tracked separately.ensureloop: reuse / wait / replace on version mismatch (handoff if ready, else clear) / fail;on_startcalled once withmissingorversion-mismatch+ previous version; three consecutive timeouts from the same registration → clear PTY sidecar, terminate, evict, recruit immediately.service.json.pty-handoff): prepare/publish (0600, atomic rename, 30 s TTL for null handoff), shutdown on failed handoff,OPENCODE_PTY_HANDOFFpassed to new contenders when fresh and matching, cleared when a different owner is reused.start_new_session/ new process group on Windows), stdin/stdout ignored.Deliberate differences
urlor integerpidis treated as absent (TS casts unvalidated JSON and then fails later on it).ServiceError("Failed to start server")immediately instead of on the next poll.console.warn→logging.getLogger("opencode").warning._service._TIMING(TS uses a WeakMap) — not part of the public signature in either.docs/getting-started/service.mdguide anddocs/reference/service.mdreference (rebased onto the MkDocs site;mkdocs build --strictand the docs-coverage test pass).Tests
tests/test_service.py: temp registration files, respx-mocked probes, andtests/fake_service.py(a real child process that registers, serves/api/infoand PTY endpoints, or fails / loses the race).tests/test_service_live.py(live): realopencode serve --servicewith temporary XDG dirs and a free managed port written toXDG_CONFIG_HOME/opencode/service.json, so a developer's real service is never touched.Security note
Like the TS client, the probe sends the registered password to the registered URL and trusts the self-reported
pidas identity. A process that can write$XDG_STATE_HOME/opencode/service.jsoncan therefore receive the password. Kept identical to TS here; possible hardening (check the file is owned by the user and not group/world-writable) is a follow-up best done upstream too.