Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ jobs:
cache: 'pip'
- name: Install
run: python -m pip install -e ".[dev,tray]"
- name: Install zombie-killer-tray (Python 3.12+ only; it doesn't support 3.11)
if: matrix.python-version != '3.11'
run: python -m pip install "zombie-killer-tray @ git+https://github.com/dev-bricks/zombie-killer-tray.git@039b4f2c7acd69063b39d6168c757b0b2fca2438"
- name: Lint
run: ruff check .
- name: Bytecode compilation check
Expand Down
2 changes: 2 additions & 0 deletions THIRD_PARTY_LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ The core runtime of `safe-start-for-codex` has **zero external runtime dependenc
| **pytest** | `>=9.1.1` | Automated test runner, contract verification suites, mock fixtures | [MIT](https://github.com/pytest-dev/pytest/blob/main/LICENSE) | [pytest-dev/pytest](https://github.com/pytest-dev/pytest) |
| **ruff** | `>=0.5.0` | High-performance Python linter and code formatting enforcement | [MIT / Apache-2.0](https://github.com/astral-sh/ruff/blob/main/LICENSE-MIT) | [astral-sh/ruff](https://github.com/astral-sh/ruff) |
| **PyInstaller** | `>=6.0` | Optional standalone Windows executable builder (`build_exe.bat`) | [GPLv2-or-later with Special Exception](https://github.com/pyinstaller/pyinstaller/blob/develop/COPYING.txt) | [pyinstaller/pyinstaller](https://github.com/pyinstaller/pyinstaller) |
| **zombie-killer-tray** | commit `039b4f2` | Optional, Windows-only companion for orphaned MCP/language-server process cleanup, launched as its own subprocess | [MIT](https://github.com/dev-bricks/zombie-killer-tray/blob/main/LICENSE) | [dev-bricks/zombie-killer-tray](https://github.com/dev-bricks/zombie-killer-tray) |
| **psutil** | `>=7.2,<8` | Watcher PID/create_time verification (Windows + Python>=3.12 only) | [BSD-3-Clause](https://github.com/giampaolo/psutil/blob/master/LICENSE) | [giampaolo/psutil](https://github.com/giampaolo/psutil) |

---

Expand Down
5 changes: 5 additions & 0 deletions THIRD_PARTY_LICENSES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ The base package declares no external runtime dependencies in
| pytest | Development tests | `>=9.1.1` | 9.1.1 | MIT | https://pypi.org/project/pytest/ |
| ruff | Development linter | `>=0.5.0` | 0.6.2 | MIT / Apache-2.0 | https://pypi.org/project/ruff/ |
| PyInstaller | Optional Windows EXE build | `>=6.0` | 6.21.0 | GPLv2-or-later with PyInstaller's special exception | https://pypi.org/project/pyinstaller/ |
| zombie-killer-tray | Optional, Windows-only integration | commit `039b4f2c7acd69063b39d6168c757b0b2fca2438` | 0.1.0 | MIT | https://github.com/dev-bricks/zombie-killer-tray |
| psutil | Watcher PID/create_time verification, Windows/Python>=3.12 only | `>=7.2,<8` | 7.2.0 | BSD-3-Clause | https://pypi.org/project/psutil/ |


## Transitive Build & Test Inventory
Expand All @@ -49,5 +51,8 @@ The base package declares no external runtime dependencies in
- The optional `build` extra uses PyInstaller. PyInstaller metadata states a
GPLv2-or-later license with a special exception that allows building and
distributing non-free applications.
- The optional `zombie-killer` extra installs `zombie-killer-tray`, guarded by
a `sys_platform == 'win32'` environment marker so it is never attempted on
Linux/macOS even if that extra is requested there.
- Binary release preparation should still inspect the actual bundled wheel set
and include the license files from bundled distributions.
22 changes: 22 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,22 @@ dev = [
build = [
"pyinstaller>=6.0"
]
# T-20260926-212716751: optional, Windows-only companion tool (conservative
# cleanup of orphaned MCP/language-server processes). Never installed
# implicitly -- only pulled in if a caller explicitly requests this extra.
# Two environment markers, combined: `sys_platform` protects `pip install
# .[zombie-killer]` on a non-Windows machine from failing on
# zombie-killer-tray's own Win32 deps; `python_version` does the same for
# Python 3.11 (this project's own floor is >=3.11, but zombie-killer-tray
# itself requires >=3.12 -- review finding: without this marker, the CI
# matrix's 3.11 leg would try and fail to install it).
zombie-killer = [
"zombie-killer-tray @ git+https://github.com/dev-bricks/zombie-killer-tray.git@039b4f2c7acd69063b39d6168c757b0b2fca2438 ; sys_platform == 'win32' and python_version >= '3.12'",
# Declared explicitly (not just hoped-for transitively via
# zombie-killer-tray) -- stop_zombie_killer_watch()/launch's double-start
# guard fail-closed without it, so it must not be an implicit assumption.
"psutil>=7.2,<8 ; sys_platform == 'win32' and python_version >= '3.12'",
]

[project.scripts]
safe-start-for-codex = "safe_start_for_codex.cli:main"
Expand All @@ -89,6 +105,12 @@ Notice = "https://github.com/dev-bricks/safe-start-for-codex/blob/main/NOTICE"
"LLM Ready" = "https://github.com/dev-bricks/safe-start-for-codex/blob/main/llms.txt"


[tool.hatch.metadata]
# T-20260926-212716751: the optional `zombie-killer` extra declares a direct
# `git+https://...` reference (zombie-killer-tray), which hatchling refuses
# to build without this explicit opt-in (broke `pip install -e .` in CI).
allow-direct-references = true

[tool.hatch.build.targets.wheel]
packages = ["src/safe_start_for_codex"]

Expand Down
85 changes: 85 additions & 0 deletions src/safe_start_for_codex/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,57 @@ def setup(_icon: pystray.Icon) -> None:
return 0


def command_zombie_killer_report(args: argparse.Namespace) -> int:
from .zombie_killer_integration import build_zombie_killer_status

status = build_zombie_killer_status()
if args.json:
print(json.dumps(status.to_dict(), ensure_ascii=False, indent=2))
else:
print(status.to_text())
return 0


def command_zombie_killer_install(args: argparse.Namespace) -> int:
from .zombie_killer_integration import install_zombie_killer_package

result = install_zombie_killer_package(target=args.target)
if args.json:
print(json.dumps(result.to_dict(), ensure_ascii=False, indent=2))
else:
print(result.to_text())
return 0 if result.status == "ok" else 1


def command_zombie_killer_watch(args: argparse.Namespace) -> int:
from .zombie_killer_integration import (
DEFAULT_MIN_AGE_SECONDS,
DEFAULT_WATCH_INTERVAL_SECONDS,
launch_zombie_killer_watch,
)

result = launch_zombie_killer_watch(
interval_seconds=args.interval or DEFAULT_WATCH_INTERVAL_SECONDS,
min_age_seconds=args.min_age or DEFAULT_MIN_AGE_SECONDS,
)
if args.json:
print(json.dumps(result.to_dict(), ensure_ascii=False, indent=2))
else:
print(result.to_text())
return 0 if result.status == "ok" else 1


def command_zombie_killer_stop(args: argparse.Namespace) -> int:
from .zombie_killer_integration import stop_zombie_killer_watch

result = stop_zombie_killer_watch()
if args.json:
print(json.dumps(result.to_dict(), ensure_ascii=False, indent=2))
else:
print(result.to_text())
return 0 if result.status in ("ok", "not-running") else 1


def command_status(_: argparse.Namespace) -> int:
latest = state_dir() / "latest.json"
if not latest.exists():
Expand Down Expand Up @@ -1923,6 +1974,40 @@ def build_parser() -> argparse.ArgumentParser:

backup = sub.add_parser("backup", help="Create a manual backup of automation TOML files.")
backup.set_defaults(func=command_backup)

zombie_killer_report = sub.add_parser(
"zombie-killer-report",
help="Check zombie-killer-tray status (last cleanup cycle). Optional, Windows-only integration.",
)
zombie_killer_report.add_argument("--json", action="store_true")
zombie_killer_report.set_defaults(func=command_zombie_killer_report)

zombie_killer_install = sub.add_parser(
"zombie-killer-install",
help="Install or upgrade zombie-killer-tray.",
)
zombie_killer_install.add_argument(
"--target", default=None,
help="Optional pip target. Default: local sibling checkout, else the commit-pinned GitHub source.",
)
zombie_killer_install.add_argument("--json", action="store_true")
zombie_killer_install.set_defaults(func=command_zombie_killer_install)

zombie_killer_watch = sub.add_parser(
"zombie-killer-watch",
help="Launch zombie-killer-tray as its own watch subprocess (orphaned MCP/language-server processes). Windows-only.",
)
zombie_killer_watch.add_argument("--interval", type=int, default=None)
zombie_killer_watch.add_argument("--min-age", type=int, default=None)
zombie_killer_watch.add_argument("--json", action="store_true")
zombie_killer_watch.set_defaults(func=command_zombie_killer_watch)

zombie_killer_stop = sub.add_parser(
"zombie-killer-stop",
help="Stop a zombie-killer-tray watch subprocess started via zombie-killer-watch.",
)
zombie_killer_stop.add_argument("--json", action="store_true")
zombie_killer_stop.set_defaults(func=command_zombie_killer_stop)
return parser


Expand Down
Loading
Loading