-
[OPIK-6516] [SDK] feat(runner): add `opik connect/endpoint stop` to cleanly terminate local runners (#6730)
发布于
2026-05-18 13:21:41 +00:00 -
[NA] [SDK] refactor: group opik connect/endpoint into cli/local_runner with Rich error UX
-
Move connect.py, endpoint.py, _run.py, pairing.py, error_view.py under
cli/local_runner/ so the pairing flow is one cohesive subpackage. -
Banner now shows Workspace alongside Opik URL and Project, with values
rendered bold so they read before the pairing link. -
Replace the inline error string with a Rich-rendered labelled block
(Reason / Workspace / URL / Config / Fix / Docs / Run) via a new
RichClickError + build_config_error_block factory. Plain text stays on
.message for Sentry/tests; Rich output is used in .show(). -
Detect "no config file at ~/.opik.config" and surface a clear
"runopik configure" call-to-action on every failure path. -
Drop default values on internal kwargs that production callers always
supply (workspace, base_url, config_file_exists, create_if_missing, etc.)
so signatures reflect actual usage; add test helpers to keep call sites
readable.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
-
[NA] [SDK][FE] feat: auto-configure on connect/endpoint + pairing context UX
-
opik connect/endpoint auto-launch
opik configurewhen no config file
exists. Skipped on --non-interactive, --headless, no TTY, or when an
API key is already supplied via --api-key / OPIK_API_KEY. -
Extract preflight helpers (should_create_project, maybe_auto_configure)
into cli/local_runner/preflight.py so tests can target a public surface. -
Pair URL now carries
&url=<ui-url>with the/apisuffix stripped, so
the pairing page can show the user-facing instance address. -
Pairing error screens render a labelled context card under the subtitle:
"Workspace: X / Pairing with Opik at: " — URL is a clickable link
that opens the Opik UI in a new tab. -
scripts/dev-runner.{sh,ps1} export TOGGLE_FORCE_WORKSPACE_VERSION=version_2
by default so a fresh local backend doesn't trip the "Workspace upgrade
required" pairing screen. Override via TOGGLE_FORCE_WORKSPACE_VERSION=disabled.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
-
[NA] [FE] feat(pairing): reword settings card and reorder Opik URL/Workspace
-
Add caption "The CLI tried to pair using these Opik settings:" above
the card so the framing is unambiguous — these are the values the CLI
used, not the user's current session. -
Rename "Pairing with Opik at" → "Opik URL" so the labels match the CLI
banner (Opik URL / Workspace) verbatim. -
Reorder rows: URL first, then Workspace — same order as the banner.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
-
[NA] [SDK][FE] feat(pairing): include project name in pair link and context card
-
CLI build_pairing_link now accepts project_name and appends a URL-encoded
&project=<name>query param. run_pairing forwards it through. -
PairingPage reads
projectfrom the query; PairingStatusScreen renders
a new Project row under Workspace in the error context card. -
Project names with spaces /
/round-trip safely via percent-encoding;
added a regression test covering the encoding shape.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
-
fix lint
-
fix(pairing): scheme allow-list on Opik URL link + URL-encode workspace
Addresses PR #6691 review feedback:
- PairingStatusScreen: validate
expectedBaseUrlwithURL()and only
render it as a clickable<a>when the scheme is http/https; otherwise
render as plain text. Prevents a crafted pair URL like
?url=javascript:alert(1)from producing a clickable script link. - PairingPage: thread expected workspace/project/baseUrl through the
invalid_linkbranch as well, so the CLI context card surfaces even
when the link fragment is malformed. - PairingStatusScreen: loosen
showWorkspaceContextto fire when any of
workspace/project/url is present; gate the Workspace row individually. - pairing.py: URL-encode the
workspacequery param the same way as
urlandproject, so workspace names containing&/=don't split
the query and confuse the FE's URLSearchParams parsing. Added a
regression test covering the encoding.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- fix(tui): use solid bullet for banner mark
The combining-enclosing-circle glyph (⠀⃝) failed to render on
terminals that lack the combining mark, falling back to two red
placeholder boxes before "opik". Switch to U+25CF (●), which renders
consistently as a solid colored dot.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- refactor(preflight): skip redundant project lookup when preflight already 404'd
should_create_project now returns (create_if_missing, known_missing) so
the interactive path — which already observed the 404 before prompting —
can tell resolve_project_id to skip the duplicate lookup and go straight
to _create_project. Headless still leaves known_missing=False so the
resolver can find an already-existing project across re-runs.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
-
[OPIK-6497] [SDK][FE] feat(runner): notify backend on close for instant disconnect
-
Supervisor calls disconnect_runner on shutdown (Ctrl+C, SIGTERM, 410
eviction, clean exit) so the FE flips the runner card off before the
heartbeat TTL expires. Best-effort: server-side cleanup is idempotent
and the heartbeat-TTL reaper picks up anything we miss. -
activate.py restores default signal handlers and raises KeyboardInterrupt
on first signal so a second Ctrl+C force-exits when the child wedges on
its own SIGINT path. Bound supervisor's graceful timeout to 5s. -
Tighten FE poll interval to 1s so the disconnect surfaces in the runner
card immediately. -
Cover supervisor disconnect on shutdown, 410 eviction, ApiError tolerance,
and end-to-end SIGINT/SIGTERM delivery via a subprocess driver.
Implements OPIK-6497.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- revert(activate): drop SIG_DFL + KeyboardInterrupt force-kill path
The two-stage signal handling (raise KeyboardInterrupt on first signal,
restore SIG_DFL so a second signal force-kills) was a safety net for
wedged agents that swallow KeyboardInterrupt. Removing it: the supervisor's
graceful timeout + SIGKILL escalation already covers wedged children, and
keeping the handler purely cooperative avoids changing the SIGINT contract
that frameworks (uvicorn, etc.) may rely on.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- [OPIK-6516] [SDK] feat(runner): add
opik connect/endpoint stopto cleanly terminate local runners
Adds first-class stop subcommands so Ollie (and humans) can tear down a
headless runner without resorting to pkill on the process group, which often
left orphaned children and stale "Endpoint open" state in the Agent Sandbox.opik connect stop --project X | --all | --runner IDand the same for
endpoint. Each Supervisor writes a small JSON lock file under
~/.opik/runners/on startup (pid, runner_id, runner_type, project,
workspace, started_at) and removes it in the shutdown finally; stale entries
are purged opportunistically on the next write.- Stop sends SIGTERM to the supervisor pid, which routes through the
OPIK-6497 handler that callsdisconnect_runnerbefore exit — the FE flips
off on its next poll instead of waiting out the heartbeat TTL. SIGKILL is
reserved for unresponsive supervisors and skips the backend notification
(the reaper picks them up). opik connectandopik endpointbecome Click groups with a hidden_run
subcommand and a sharedRunnerGroup.resolve_commandfallback, so the
legacyopik endpoint --project X -- python script.pyinvocation form
still works alongside the newstopsubcommand.--projectis now
required at the Click layer instead of validated by hand.- Headless endpoint pairing now surfaces a "Paired ✓" panel with a direct
Agent Playground link (connect keeps the generic project URL since Ollie
is the user-facing surface there). Both browser and headless flows share
pairing.post_pairing_urlso the destination logic lives in one place. - Internal helpers (Supervisor, launch_supervisor) drop default values on
parameters that aren't part of the user SDK API — every caller passes
workspace/project_name/runner_type explicitly. - Intra-package imports in
cli/local_runner/switched to module-style
(from . import pairing; pairing.RunnerType) for clearer call-site
attribution and easier patch targets in tests.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- ci(guardrails): disable guardrails-ai integration tests
guardrails-ai is on PyPI quarantine, so the install step in the lib
integration runner fails before pytest even starts. Comment out the
job and its references in workflow_dispatch and notify-slackneeds;
drop the SUITE_RESULTS entry (a YAML#would survive as literal text
into the JSON payload). Re-enable when the package is back on PyPI.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- style(runner): collapse RunnerGroup fallback call onto one line
ruff format wants the super().resolve_command(...) call on a single
line; restore that to unblock CI lint.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- fix(runner): address PR review on opik stop CLI
- stop.py: only drop the pid lock file when the runner actually exited.
On SIGTERM-denied / SIGKILL-failed / still-alive paths we leave the
file in place so a subsequentopik <type> stopcan rediscover the
live supervisor instead of being blind to it. - pid_file.remove(): bump the OSError log line to LOGGER.error — a
failed pid-file unlink is a real signal that the cleanup contract
broke, not a debug-level event. - RunnerGroup: narrow the
UsageError-catch fallback. Connect (no
positional after_run) now surfaces click's native "No such command
'stp'" for subcommand typos instead of routing them into_runand
printing a confusing "Missing --project". Endpoint opts in via
accepts_positional_after_run=Trueso the legacy
opik endpoint -- python script.pyform keeps surfacing the helpful
"Missing --project" error when--projectis omitted. - connect.py / endpoint.py: split
--runnerhelp text across lines and
reword it as a usage hint ("Use when a project has more than one
runner attached to it") instead of the opaque "disambiguates". Brings
source lines back under the 88-char project limit. - test_pid_file.py and test_stop.py: rename every test method to the
python-sdk testing convention
(test_<WHAT>__<CASE>__<EXPECTED_RESULT>/__happyflow).
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
Co-authored-by: Claude Opus 4.7 (1M context) noreply@anthropic.com
下载附件
-