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
40 changes: 34 additions & 6 deletions .claude/session-start-global-deny.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
#!/bin/bash
# Session start hook: ensure global Claude Code deny rules and git proxy config
# CANONICAL SOURCE — managed by runcycles/.github/shared-config/
# Do not edit this file in individual repos. Changes should be made here
# and synced to all repos via scripts/sync-claude-config.sh.
#
# 1. Writes MCP deny rules to ~/.claude/settings.json so mcp__github__
# file-mutation tools are blocked globally (even in cross-repo sessions).
# 2. Fixes git remote URLs to use the local git proxy when available,
# so native git push works instead of falling back to MCP tools.
# Session start hook: ensure global Claude Code deny rules and git proxy config.
#
# This script runs at session start and does TWO things:
#
# 1. (Per-user, idempotent) Writes MCP deny rules to ~/.claude/settings.json
# so mcp__github__ file-mutation tools are blocked globally — even in
# cross-repo sessions where deny rules in a single repo wouldn't apply.
#
# 2. (MULTI-REPO MUTATION) If a local git proxy is detected in any sibling
# repo under /home/user/*, rewrites the `origin` remote URL of EVERY
# sibling github.com repo under /home/user/ to route through the proxy.
# This is intentional for Claude Code remote-environment workflows where
# multiple Cycles repos are cloned side-by-side and all need the same
# proxy. It is surprising if you only know about the per-repo .claude/
# hook, so it is called out explicitly here.
#
# OPT-OUT: set CYCLES_CLAUDE_SKIP_REMOTE_REWRITE=1 to disable Part 2 entirely
# (useful for local Claude Code runs where you do not want sibling repos
# touched). Part 1 always runs.
#
# Issue: runcycles/.github#63

set -e

Expand Down Expand Up @@ -56,9 +75,18 @@ EOF
fi
fi

# --- Part 2: Fix git remote URLs to use local proxy ---
# --- Part 2: Fix git remote URLs to use local proxy (MULTI-REPO) ---
# Some sessions clone repos via github.com directly, which lacks push credentials.
# If the local git proxy is running, rewrite remote URLs to use it.
#
# WARNING: this part iterates every directory under /home/user/ and mutates the
# `origin` remote of any github.com repo it finds, not just the current
# checkout. See the file-level header for the rationale and opt-out.

if [ "${CYCLES_CLAUDE_SKIP_REMOTE_REWRITE:-}" = "1" ]; then
# Opt-out path for local Claude Code runs that should not touch sibling repos.
exit 0
fi

# Detect local git proxy: look for the proxy in any sibling repo's remote URL
PROXY_BASE=""
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ jobs:
with:
python-version: "3.12"

# Fail-fast before build/publish if (a) pyproject.toml declares a pre-release
# version (e.g. 1.0.0.dev0, 1.0.0a1) when a final-version tag (vX.Y.Z) was
# pushed, or (b) the tag version doesn't match pyproject.toml. PyPI rejects
# duplicate versions server-side, but a local guard surfaces the operator
# error before the upload phase. See runcycles/.github#61.
- name: Verify pyproject version matches tag
if: startsWith(github.ref, 'refs/tags/v')
run: |
python -c "import tomllib,sys; v=tomllib.load(open('pyproject.toml','rb'))['project']['version']; print(v)" > /tmp/pyproject_version
PYPROJECT_VERSION=$(cat /tmp/pyproject_version)
TAG_VERSION="${GITHUB_REF_NAME#v}"
echo "pyproject.toml version: ${PYPROJECT_VERSION}"
echo "Git tag version: ${TAG_VERSION}"
if [ "${PYPROJECT_VERSION}" != "${TAG_VERSION}" ]; then
echo "::error::Version mismatch: pyproject.toml has '${PYPROJECT_VERSION}' but tag is 'v${TAG_VERSION}'. Bump pyproject.toml or retag."
exit 1
fi

- name: Install build tool
run: python -m pip install --upgrade pip build

Expand Down
15 changes: 15 additions & 0 deletions AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,18 @@ Documentation-only update pointing users at the new sibling package [`langchain-
Background: LangChain 1.x introduced an `AgentMiddleware` API with `wrap_tool_call`, `before_model`, and `wrap_model_call` hooks. The new package wraps that API on top of this SDK's existing `decide` / `create_reservation` / `commit_reservation` / `release_reservation` surface — no new SDK methods needed. Splitting into a sibling repo follows LangChain's [publishing guidance](https://docs.langchain.com/oss/python/contributing/publish-langchain) ("New integrations should be published as standalone PyPI packages") and the `langchain-<service>` naming convention used by `langchain-anthropic`, `langchain-openai`, etc.

Protocol conformance: No protocol or wire-format changes. The new sibling package consumes this SDK as a normal dependency.

## Infrastructure Hardening (added 2026-05-12)

**Files:** `.claude/session-start-global-deny.sh`, `.github/workflows/python-publish.yml`
**Version:** unreleased (CI/Claude-config only — no package version change)

Cross-cutting hardening landed in response to org-wide tracking issues filed in `runcycles/.github`. Two distinct changes; both are infra-only.

- **`.claude/session-start-global-deny.sh`** synced from the new canonical at `runcycles/.github/shared-config/`. The script now (a) carries a top-of-file callout explaining that Part 2 mutates the `origin` remote of every sibling repo under `/home/user/*`, not just the current checkout, and (b) honors a `CYCLES_CLAUDE_SKIP_REMOTE_REWRITE=1` opt-out env var. Part 1 (MCP deny rules) is unchanged. Tracks `runcycles/.github#63`.

- **`.github/workflows/python-publish.yml`** gained a `Verify pyproject version matches tag` step that runs on tag-triggered builds (`refs/tags/v*`). The step parses `pyproject.toml` via `tomllib` and fails the workflow before the build phase if the declared version doesn't match the tag (e.g., tag `v0.5.0` against `pyproject.toml` still on `0.4.1` or a `dev0` pre-release). PyPI already rejects duplicate versions server-side, but this surfaces operator error earlier in the pipeline. Python analog of the Java SNAPSHOT-guard tracked in `runcycles/.github#61`.

Not included in this change: bumping the reusable-workflow ref `runcycles/.github/.github/workflows/ci-python.yml@main` to `@v1` (`runcycles/.github#60`). That bump is intentionally split into a separate follow-up PR — it depends on the `v1` tag existing in `runcycles/.github`, which is being cut after the canonical-script PR (`runcycles/.github#64`) merges.

Protocol conformance: No protocol or wire-format changes. No SDK source touched. Test suite unaffected.