Skip to content

feat(cli): add server subcommand for eval-hub-server lifecycle management#149

Merged
gnaulak-redhat merged 5 commits into
eval-hub:mainfrom
gnaulak-redhat:feat-evalhub-server
Jul 1, 2026
Merged

feat(cli): add server subcommand for eval-hub-server lifecycle management#149
gnaulak-redhat merged 5 commits into
eval-hub:mainfrom
gnaulak-redhat:feat-evalhub-server

Conversation

@gnaulak-redhat

@gnaulak-redhat gnaulak-redhat commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

What and why

Add evalhub server command group with run/start/stop/status and config management (set/show/unset), mirroring the mcp subcommand pattern.

Set the server config file

╰─➤  evalhub config set server_config_file myserver-config.yaml
Set 'server_config_file' in profile 'default'

config list does not unfold the yaml

╰─➤  evalhub config list
Profile: default
  base_url: http://localhost:8080
  server_config_file: /Users/gnaulak/.config/evalhub/server/default/config.yaml

  Missing required keys: token, tenant

To check the server yaml use config get with --unfold:

╰─➤  evalhub config get server_config_file --unfold
service:
  port: 8080
  ready_file: "/tmp/repo-ready"

# Database configuration
database:
  driver: sqlite
  url: file::eval_hub:?mode=memory&cache=shared

To start the server in foreground

evalhub server run

To start the server in background:

╰─➤  evalhub server start
Server started (PID 36693).
  URL:  http://localhost:8080
  Logs: /Users/gnaulak/.config/evalhub/server/server.log

╰─➤  evalhub server status
Server is running (PID 36693).
  Health: healthy
  URL:    http://localhost:8080
  Logs:   /Users/gnaulak/.config/evalhub/server/server.log

╰─➤  evalhub server stop
Server stopped.

This support tls:

╰─➤  evalhub config set server_config_file myserver-config-tls.yaml
Set 'server_config_file' in profile 'default'

╰─➤  evalhub server start
Server started (PID 37009).
  URL:  https://localhost:8080
  Logs: /Users/gnaulak/.config/evalhub/server/server.log

╰─➤  curl -k https://localhost:8080/api/v1/health
{"status":"healthy","timestamp":"2026-06-30T17:01:05.99972Z","build":"0.4.3","build_date":"2026-06-22T08:33:42+0000","git_hash":"6110928"}

To unset:

╰─➤  evalhub config unset server_config_file
Unset 'server_config_file' from profile 'default'

Closes # https://redhat.atlassian.net/browse/RHOAIENG-68000

Type

  • feat
  • fix
  • docs
  • refactor / chore
  • test / ci

Testing

  • Tests added or updated
  • Tested manually

Breaking changes

No

Summary by CodeRabbit

Summary

  • New Features

    • Added an evalhub server command group (run, start, stop, status).
    • Introduced file-backed configuration keys, including viewing stored file contents via --unfold.
  • Bug Fixes

    • Improved daemon lifecycle handling for reliable start/stop/status behavior (including graceful shutdown, PID handling, and log/health checks).
    • Refined MLflow tag/metadata conversion for more consistent experiment lookups.
  • Tests

    • Added unit test coverage for the server command lifecycle and config workflows.
    • Updated MCP CLI unit tests to match the new process-management flow.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 04e06277-6972-43a0-8d16-56161f88b53c

📥 Commits

Reviewing files that changed from the base of the PR and between 353ad12 and 772e042.

📒 Files selected for processing (6)
  • src/evalhub/cli/_process.py
  • src/evalhub/cli/config.py
  • src/evalhub/cli/mcp_cmd.py
  • src/evalhub/cli/server_cmd.py
  • tests/unit/test_cli_mcp.py
  • tests/unit/test_cli_server.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/evalhub/cli/server_cmd.py
  • src/evalhub/cli/config.py
  • src/evalhub/cli/mcp_cmd.py
  • tests/unit/test_cli_mcp.py
  • tests/unit/test_cli_server.py

📝 Walkthrough

Walkthrough

Adds a shared _process.py module for daemon lifecycle helpers, a new evalhub server command group, file-backed config key handling for server_config_file, refactors mcp_cmd.py to use shared helpers, and simplifies two adapter methods.

Changes

Shared process lifecycle helpers and MCP refactor

Layer / File(s) Summary
Shared _process.py lifecycle helpers
src/evalhub/cli/_process.py
Introduces signal constants, PID file helpers, binary lookup, graceful stop, background spawning, foreground execution, and daemon guard/stop helpers.
mcp_cmd.py delegation to _process helpers
src/evalhub/cli/mcp_cmd.py, tests/unit/test_cli_mcp.py
Removes local process management and rewires mcp run, start, stop, and status to shared helpers; updates test patch targets.

evalhub server command and config file-key support

Layer / File(s) Summary
FILE_KEYS support in config and main
src/evalhub/cli/config.py, src/evalhub/cli/main.py
Adds file-backed config key support for server_config_file, including validation, copy/remove helpers, --unfold, and CLI registration.
server_cmd.py run/start/stop/status
src/evalhub/cli/server_cmd.py
Adds the server command group with config resolution, health checks, startup polling, and lifecycle command handling.
Server CLI and file-key tests
tests/unit/test_cli_server.py
Adds lifecycle, TLS, config file-key, unfold, cleanup, and roundtrip tests for the new server and file-backed config flows.

Minor adapter cleanups

Layer / File(s) Summary
auth.py and mlflow.py simplifications
src/evalhub/adapter/auth.py, src/evalhub/adapter/mlflow.py
Simplifies model credential construction and uses the shared MLflow tag conversion helper.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: kind/feat

Suggested reviewers: tarilabs, julpayne, nbs-rh, scheruku-rh

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding an eval-hub-server lifecycle subcommand under the CLI.
Description check ✅ Passed The description covers what/why, type, testing, and breaking changes, with only a minor issue in the closing reference format.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

gnaulak-redhat and others added 2 commits June 30, 2026 21:25
…ment

Add `evalhub server run/start/stop/status` commands to manage the local
eval-hub-server binary. Server config is managed through the unified
`config set/get/unset` commands via the `server_config_file` key — the
file is validated and copied into ~/.config/evalhub/server/<profile>/ on
set, deleted on unset. Add `--unfold` flag to `config get` for printing
file-based config contents.

Co-Authored-By: Claude <noreply@anthropic.com>
… module

Deduplicate process management code between server_cmd.py and mcp_cmd.py
by extracting shared helpers (find_binary, live_pid, graceful_stop,
spawn_background) into cli/_process.py. Also combine _read_server_port
and _is_tls_enabled into a single _read_server_config function that
parses the YAML once, and inline the trivial _server_scheme helper.

Co-Authored-By: Claude <noreply@anthropic.com>
@gnaulak-redhat gnaulak-redhat marked this pull request as ready for review June 30, 2026 16:52
…ver/mcp commands

Extract run_foreground, require_not_running, and stop_daemon helpers
into _process.py to eliminate duplicated daemon lifecycle code between
server_cmd and mcp_cmd. Remove --config-dir option from server run/start
in favor of server_config_file as the single config source. Fix minor
simplifications: unnecessary deferred click import, verbose TLS detection,
redundant int() cast on resp.status.

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/evalhub/cli/_process.py`:
- Around line 45-50: The live_pid() helper only validates that a PID is
currently running, which is insufficient because PID reuse can make
require_not_running() and stop_daemon() act on the wrong process. Update
live_pid() in _process.py to verify daemon identity with a stronger check than
PID alone, using a stable identifier stored with the pid file and validated
against the running process before returning it. Make sure the same identity
check is used by require_not_running() and stop_daemon() so they only treat the
original daemon as live.
- Around line 53-59: The env override handling in find_binary should validate
that the path from env_var actually exists before returning it, since currently
it returns the raw string and lets the later subprocess call fail with a
traceback. Update find_binary to check the environment-supplied path with an
existence/file check, and if it is missing fall back to the normal lookup or
raise the same ClickException path used for other missing binaries.
- Around line 35-42: read_pid() currently accepts any integer from the pid file,
which allows 0 or negative values to flow into stop_daemon() and then os.kill().
Update read_pid() in _process.py to validate the parsed PID and return None
unless it is strictly positive, keeping the existing handling for missing files
and parse/read errors. Use the read_pid() helper as the single place to reject
invalid pid values so stop_daemon() only ever receives a safe positive process
ID.

In `@src/evalhub/cli/config.py`:
- Around line 188-189: The stored server config copy in the config save path
currently uses shutil.copy2(), which preserves permissive source permissions.
Update the copy logic in the config-saving flow around save_config() so the
destination under ~/.config/evalhub/server is explicitly chmodded to 0600 after
copying, matching the private-permissions behavior already used elsewhere.
Ensure the fix is applied where the copied file is written so the saved config
is never left world-readable.

In `@src/evalhub/cli/server_cmd.py`:
- Around line 143-156: The startup failure path in server_cmd.py leaves a live
daemon running when _wait_for_healthy() fails but proc.poll() is still None,
because PID_FILE is never written and the process is not cleaned up. Update the
failure handling around spawn_background(), _wait_for_healthy(), and the proc
check to explicitly terminate/kill the spawned server and wait for it before
raising the ClickException, using LOG_FILE for diagnostics if needed, so evalhub
server stop does not depend on a missing PID file.

In `@tests/unit/test_cli_server.py`:
- Around line 10-13: This test module under tests/unit/ is missing the required
unit-test marker, so it won’t be included in marker-based runs. Add a
module-level pytest marker in test_cli_server so all tests in this file are
treated as unit tests, following the repository convention for tests/**/*.py.
Use the existing imports and module scope in test_cli_server to place the marker
alongside the current test setup.
- Around line 29-31: The fixture that restores EVALHUB_TOKEN in
test_cli_server.py leaves the variable set when it was originally absent, which
leaks state into later tests. Update the fixture cleanup logic around the
saved_token handling so that when saved_token is None it removes EVALHUB_TOKEN
from os.environ, and when a value was originally present it restores that value.
Use the existing fixture and saved_token variable to locate the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f029fa0f-60e5-4aea-ba4e-a0148613bf02

📥 Commits

Reviewing files that changed from the base of the PR and between e6bd150 and 353ad12.

📒 Files selected for processing (9)
  • src/evalhub/adapter/auth.py
  • src/evalhub/adapter/mlflow.py
  • src/evalhub/cli/_process.py
  • src/evalhub/cli/config.py
  • src/evalhub/cli/main.py
  • src/evalhub/cli/mcp_cmd.py
  • src/evalhub/cli/server_cmd.py
  • tests/unit/test_cli_mcp.py
  • tests/unit/test_cli_server.py

Comment thread src/evalhub/cli/_process.py Outdated
Comment thread src/evalhub/cli/_process.py
Comment thread src/evalhub/cli/_process.py
Comment thread src/evalhub/cli/config.py
Comment thread src/evalhub/cli/server_cmd.py
Comment thread tests/unit/test_cli_server.py
Comment thread tests/unit/test_cli_server.py
- Reject non-positive PIDs in read_pid to avoid invalid process lookups
- Validate that the binary path from env var actually exists before use
- Restrict permissions on stored config files to owner-only (0600)
- Terminate orphaned server process on health-check timeout instead of
  leaving it running
- Add missing EVALHUB_TOKEN cleanup in test fixture teardown to prevent
  env leakage between tests
- Add pytest unit marker to server CLI tests

Co-Authored-By: Claude <noreply@anthropic.com>
@gnaulak-redhat gnaulak-redhat added the ready-for-review Coderabbit comments have been addressed and the PR is ready to be reviewed label Jun 30, 2026

@ruivieira ruivieira left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gnaulak-redhat LGTM, thanks! Left a couple of suggestions.

Comment thread src/evalhub/adapter/mlflow.py
Comment thread src/evalhub/cli/server_cmd.py Outdated
tls = bool(cert and key)
return port, tls
except (yaml.YAMLError, TypeError, ValueError, AttributeError):
return _DEFAULT_PORT, False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add something like

click.echo(f"Warning: could not parse server config ({exc}), using defaults.", err=True)

to give user feedback when there's an exception parsing YAML?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. I updated to "fail" instead of using defaults. Thanks

…g defaults

When config.yaml exists but contains invalid values (e.g. non-numeric port),
raise a ClickException with the parse error instead of falling back to
default settings, which could cause the server to start with unintended
configuration.

Co-Authored-By: Claude <noreply@anthropic.com>
@ruivieira

Copy link
Copy Markdown
Member

@coderabbitai please open an issue on https://github.com/eval-hub/eval-hub.github.io to document the changes on this PR.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="eval-hub/eval-hub.github.io"

body=$(cat <<'EOF'
## Summary

Document the new `evalhub server` CLI command group and related server configuration management introduced in eval-hub/eval-hub-sdk#149.

## Background

PR eval-hub/eval-hub-sdk#149 adds:

- An `evalhub server` command group for managing the `eval-hub-server` lifecycle:
  - `evalhub server run` — run the server in the foreground.
  - `evalhub server start` — start the server as a background daemon (writes a PID file and log file, waits for the health endpoint before reporting success).
  - `evalhub server stop` — stop the background daemon (graceful shutdown with a timeout, then force-kill).
  - `evalhub server status` — report whether the server is running and healthy, including URL and log path when available.
- Server configuration management through the existing `evalhub config` workflow using a new `server_config_file` key:
  - `evalhub config set server_config_file <path>` — validates the YAML file and stores a profile-scoped copy under `~/.config/evalhub/server/<profile>/config.yaml`.
  - `evalhub config get server_config_file` / `show` — displays the stored value.
  - `evalhub config unset server_config_file` — removes the stored config file and cleans up the profile directory.
- A new `--unfold` flag on `evalhub config get` to print the contents of file-based configuration values (currently only applicable to `server_config_file`).
- Support for TLS-enabled servers (health checks over HTTPS, `https://` URLs reported in `start`/`status` output).

## Requested documentation updates

- [ ] Add a new "Server" section to the CLI reference documenting `evalhub server run|start|stop|status`, including example invocations (foreground vs background) and description of PID/log file locations.
- [ ] Document the `server_config_file` config key: how to set/get/unset it, and the YAML validation requirements (must exist, be valid YAML, and be a mapping).
- [ ] Document the `--unfold` flag on `evalhub config get`, its mutual exclusivity with `--unmask`, and that it only applies to file-based config keys.
- [ ] Include examples covering: starting the server in the foreground, starting/stopping in the background, checking status, handling TLS, and removing the server config setting.

## References

- PR: https://github.com/eval-hub/eval-hub-sdk/pull/149
- Requested by: `@ruivieira`
EOF
)

gh issue create \
  --repo "$repo" \
  --title "Document evalhub server command and server_config_file config key from eval-hub-sdk PR `#149`" \
  --body "$body" \
  --assignee "ruivieira"

Length of output: 2989


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="eval-hub/eval-hub.github.io"

# Check if the issue was already created despite the assignment error
gh issue list --repo "$repo" --search "Document evalhub server command" --json number,title,url,assignees

Length of output: 579


The issue was created successfully, but I couldn't assign it to you due to a permissions limitation with the integration. You'll need to self-assign it manually if desired.

@ruivieira: I've opened an issue on eval-hub/eval-hub.github.io to document the changes from this PR: eval-hub/eval-hub.github.io#81

It covers the new evalhub server command group (run, start, stop, status), the server_config_file config key workflow (set/get/unset), and the --unfold flag on config get, along with a checklist of documentation items to cover (including TLS handling and background/foreground examples). Note that I wasn't able to auto-assign it to you due to a permissions restriction on the integration — feel free to self-assign.

@gnaulak-redhat gnaulak-redhat merged commit 37c65c8 into eval-hub:main Jul 1, 2026
8 checks passed
@gnaulak-redhat gnaulak-redhat deleted the feat-evalhub-server branch July 1, 2026 07:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review Coderabbit comments have been addressed and the PR is ready to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants