Skip to content
Open
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
18 changes: 18 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: 2
updates:
- package-ecosystem: 'uv'
# uv workspace root: one uv.lock covers every packages/* member.
directory: '/'
schedule:
interval: 'daily'
cooldown:
default-days: 7
- package-ecosystem: 'github-actions'
# '/' covers .github/workflows only; the composite actions pin their own actions.
directories:
- '/'
- '/.github/actions/*'
schedule:
interval: 'weekly'
cooldown:
default-days: 7
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
push:
branches: [main]
pull_request:
# Weekly, so an advisory published against an unchanged uv.lock still gets reported.
schedule:
- cron: "0 13 * * 1"

permissions:
contents: read
Expand All @@ -23,6 +26,20 @@ jobs:
- run: uv run ruff check .
- run: uv run ruff format --check .

# ─── Dependency audit ───────────────────────────────────────────────────────
audit:
name: Dependency audit
runs-on: ubuntu-latest
# A new advisory must not block unrelated PRs; push and the weekly schedule still fail on it.
continue-on-error: ${{ github.event_name == 'pull_request' }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
python-version: "3.12"
# Audits the committed uv.lock as-is (no `uv lock` first), so a vulnerable pin fails here.
- run: make audit

# ─── Setup ──────────────────────────────────────────────────────────────────
setup:
name: Install & sync
Expand Down
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
.PHONY: start test typecheck lint format format-check lint-fix build clean api-report api-report-check
.PHONY: start test typecheck lint format format-check lint-fix build clean api-report api-report-check audit

# Report known vulnerabilities in the locked dependency set (pip-audit on uv.lock)
# Exports to a file, not a pipe: make's /bin/sh has no pipefail, so a failed export would audit nothing and pass
audit:
reqs=$$(mktemp) && trap 'rm -f "$$reqs"' EXIT && \
uv export --all-packages --no-emit-workspace --frozen -q -o "$$reqs" && \
uvx pip-audit@2.9.0 --disable-pip --no-deps -r "$$reqs"

# Run main.py (uv run python main.py)
start:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,14 @@
#: ``strict_mcp_config``, ``agents``: what the agent is allowed to do and which tools or
#: subagents it gets. The handler wires tools and permissions itself.
#: * ``resume``, ``session_id``, ``fork_session``, ``continue_conversation``, ``session_store``,
#: ``session_store_flush``, ``enable_file_checkpointing``: session state on the host.
#: ``session_store_flush``, ``enable_file_checkpointing``, ``resume_session_at``,
#: ``resume_drops_turn``: session state on the host.
#: * ``stderr``, ``debug_stderr``, ``include_partial_messages``, ``include_hook_events``,
#: ``max_buffer_size``, ``load_timeout_ms``: process I/O and transport plumbing, including the
#: streamed message shape the handler reads.
#: ``forward_subagent_text``, ``max_buffer_size``, ``load_timeout_ms``: process I/O and transport
#: plumbing, including the streamed message shape the handler reads.
#: * ``verbatim_prompts``: whether Claude Code expands ``@path`` mentions and slash commands in
#: the prompt, which decides whether prompt text can make it read host files. The application
#: decides that, not a config.
#: * ``output_format``: structured output, which the handler owns through the config's
#: ``outputFormat`` by adding its schema to the system prompt. Forwarding a second schema here
#: would have the reply follow one schema while the prompt asks for another.
Expand All @@ -145,6 +149,7 @@
"env",
"extra_args",
"fork_session",
"forward_subagent_text",
"include_hook_events",
"include_partial_messages",
"load_timeout_ms",
Expand All @@ -154,6 +159,8 @@
"permission_prompt_tool_name",
"plugins",
"resume",
"resume_drops_turn",
"resume_session_at",
"sandbox",
"session_id",
"session_store",
Expand All @@ -165,6 +172,7 @@
"strict_mcp_config",
"task_budget",
"user",
"verbatim_prompts",
}
)

Expand Down
19 changes: 13 additions & 6 deletions packages/client/tests/test_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,18 +276,25 @@ async def test_is_idempotent(self) -> None:
async def test_a_failed_telemetry_setup_leaves_no_client_behind(
self, restore_otel_globals: Any
) -> None:
# A malformed OTEL_EXPORTER_OTLP_TIMEOUT makes the real _setup_telemetry
# raise. _client used to be assigned first, so the next call returned the
# half-built client as a silent success with no telemetry, and its
# connection was never closed.
# _client used to be assigned before _setup_telemetry ran, so after a
# telemetry failure the next call returned the half-built client as a
# silent success with no telemetry, and its connection was never closed.
# The failure is injected: OpenTelemetry 1.45 logs a malformed
# OTEL_EXPORTER_OTLP_TIMEOUT and uses its default instead of raising.
failed = _make_stub_client()
healthy = _make_stub_client()
mock_ld = MagicMock()
mock_ld.Config = MagicMock(return_value=MagicMock())
mock_ld.LDClient = MagicMock(side_effect=[failed, healthy])
with patch("importlib.import_module", return_value=mock_ld):
bad_env = {"LD_SDK_KEY": "k", "OTEL_EXPORTER_OTLP_TIMEOUT": "soon"}
with patch.dict(os.environ, bad_env):
with (
patch.dict(os.environ, {"LD_SDK_KEY": "k"}),
patch.object(
lifecycle_module,
"_setup_telemetry",
side_effect=ValueError("bad config"),
),
):
with pytest.raises(ValueError):
await init_client()
assert lifecycle_module._client is None
Expand Down
Loading
Loading