Compare commits

..

6 Commits

Author SHA1 Message Date
github-actions[bot]
4f2543cbe7 Bump 31 plugin SHA pin(s) to upstream HEAD 2026-05-31 08:26:20 +00:00
Mohamed Hegazy
3d368d2972 Merge pull request #2105 from anthropics/fix-2098-output-config-format
security-guidance: migrate _call_claude from deprecated output_format to output_config.format (#2098)
2026-05-30 22:32:02 -07:00
Mohamed Hegazy
84011d43b1 security-guidance: migrate from deprecated output_format to output_config.format (#2098)
Fixes #2098. The Anthropic Messages API moved structured-output
schema specification from a top-level `output_format` field to a
nested `output_config.format` field, per
https://platform.claude.com/docs/en/build-with-claude/structured-outputs.

Per docs the old form "will continue working for a transition period"
— and indeed for api-key + non-streaming auth it still returns HTTP
200 (verified via live API). But OAuth Bearer users with CLI 2.1.158
hit `invalid_request_error: output_format: This field is deprecated.
Use 'output_config.format' instead.` consistently — reporter saw 462
errors in one day. The trigger appears to be auth mode + possibly
stream:true (their controlled curl bypass used Bearer + stream=true);
api-key + non-streaming was my initial repro attempt and didn't fire.

The bug only affected `_call_claude` (the legacy direct-urllib path).
The agentic `_agentic_review` path goes through claude_agent_sdk →
subprocesses to the `claude` CLI binary, which already uses the new
`output_config.format` shape correctly (per src/utils/sideQuery.ts:263
in claude-cli-internal). So this PR only needs to fix the plugin's
direct HTTP path.

This commit:

1. llm.py: rewrite the payload literal in `_call_claude` to use
   `output_config: { format: { type: 'json_schema', schema: ... } }`
   instead of top-level `output_format`.

2. llm.py: in the adaptive-thinking branch, MERGE `effort: "high"`
   into the existing `output_config` dict instead of reassigning.
   Reassignment would silently clobber the format schema set in (1).
   The pre-existing code did `payload["output_config"] = {"effort":
   "high"}` which was correct WHEN output_format was top-level (and
   output_config wasn't otherwise used). With the migration the
   existing dict carries the schema, so we extend it not replace it.

Verified locally on macOS Python 3.13:

  - py_compile clean.
  - Existing 401 tests still pass — 0 regression.
  - 6 new tests in test_2098_output_config_format.py (added to
    internal test suite at sg-staging/tests/, not in this PR):

      * 2 static-shape: the `_call_claude` source no longer contains
        top-level `"output_format":` AND uses `output_config`. The
        adaptive-thinking branch does NOT reassign output_config (and
        DOES set output_config['effort']). Catches the regression
        class where a future refactor reintroduces either bug.
      * 2 payload-shape unit (mocked urllib): both thinking_budget=0
        and thinking_budget>0+adaptive code paths produce a payload
        with the correct `output_config.format` shape AND no
        `output_format` top-level. The adaptive path verifies both
        `format` and `effort` coexist in output_config (i.e., the
        merge fix works).
      * 2 live-API gating (skip-on-no-key): the new shape returns
        HTTP 200 against api.anthropic.com; the old shape's current
        status is recorded for canary purposes (still 200 for
        api-key today, but reporter shows it's 400 for OAuth).

  - Full suite: 405/405 pass + 2 skipped (live API tests, opt-in).
  - The reporter's exact deprecation 400 message reproduces if you
    swap auth to OAuth Bearer + stream:true (could not test locally
    without extracting the keychain OAuth token, which was out of
    scope). The fix shape is API-contract-level so it doesn't depend
    on which auth mode triggers the 400.

NOT verified end-to-end via OAuth-authenticated plugin invocation on
my machine (auto-mode classifier correctly declined to extract the
keychain token). Reporter's 462 production errors + the docs
migration notice + the live-API HTTP 200 on the new form are
sufficient evidence to ship.

Closes #2098.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 20:11:41 -07:00
Mohamed Hegazy
2a822c0787 Merge pull request #2101 from anthropics/fix-2099-quotepath-global
security-guidance: move core.quotePath=false to GIT_CMD globally (#2099 followup)
2026-05-30 20:07:06 -07:00
Mohamed Hegazy
a40c9f1e83 security-guidance: move core.quotePath=false to GIT_CMD globally (#2099 followup)
Followup to PR #2086 (which added the flag to 4 specific git call
sites) and PR #2100 (text=True purge for #2099).

The Windows reporter for #2099 noticed more git invocations still
lacked the flag — rev-parse path queries (--show-toplevel, --git-dir,
--git-common-dir), reflog %gs subjects, and `git show <sha>:<path>`
all output paths but the per-site PR #2086 approach missed them. The
result: an Arabic-named directory shows up via _git_diff_range but
rev-parse-emitted paths get C-quoted, breaking downstream
os.path.isabs() checks.

Fix: add `-c core.quotePath=false` to GIT_CMD itself as the 4th
config-set. Every subprocess.run using the *GIT_CMD splat picks it
up automatically — diff feeders, rev-parse path queries, reflog log,
ls-files, status, git show. No more per-site flag duplication.

This commit:

1. gitutil.py: add -c core.quotePath=false to GIT_CMD.
2. Remove the now-redundant per-site flags at the 7 call sites that
   previously had inline -c core.quotePath=false (cleanup, since the
   global setting subsumes them):
   gitutil.py: _git_diff_range, _git_name_only, _git_status_porcelain,
               get_git_diff (4 sites)
   diffstate.py: _list_untracked git ls-files (1 site)
   security_reminder_hook.py: commit-review git diff + git show (2 sites)

Verified locally on latest main (post PR #2100 merge) with macOS
Python 3.13:

  - py_compile clean on all 3 modified files.
  - Bare main BEFORE my fix: 400/401 pass — 1 failure proves the gap
    (test_git_cmd_contains_quotepath_false catches the missing flag).
  - Main + my fix: 401/401 pass.
  - 23 new tests in test_quotepath_global.py (added to internal test
    suite at sg-staging/tests/, not in this PR):

      * 1 GIT_CMD-level: GIT_CMD list contains core.quotePath=false as
        a (-c, value) pair. Single source of truth — single place a
        future PR will be caught if the flag gets dropped.
      * 10 static-shape (one per hooks/*.py): every subprocess.run
        uses the *GIT_CMD splat (no bare git invocation that would
        bypass the global flag).
      * 12 end-to-end (parametrized over Arabic, Hebrew, CJK directory
        names): real git repo, _git_diff_range emits unquoted diff,
        extract_file_paths_from_diff and parse_diff_into_files keep
        the non-ASCII path in their output, _git_toplevel returns the
        non-ASCII path intact.

  - 1 staleness fix in test_diff_parser_non_ascii.py
    (test_no_bare_git_diff_or_show_without_flag): updated to accept
    EITHER inline core.quotePath=false OR *GIT_CMD splat (which
    globally provides it).

NOT verified end-to-end on Windows with a non-ASCII repo root path.
The new global-flag test pins the contract permanently, and the
parametrized macOS tests confirm parser behavior on ASCII-control
paths in non-ASCII directories. The Windows-specific rev-parse
quoting behavior follows from the same git contract our macOS test
environment exercises (POSIX git always emits raw UTF-8 regardless
of quotePath; on Windows the flag is what makes output raw).

Closes the #2099 followup specifically about _git_diff_range /
rev-parse --show-toplevel / git log %gs paths slipping past.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 12:19:38 -07:00
Mohamed Hegazy
c7a3e2ffa0 Merge pull request #2100 from anthropics/fix-2099-text-true-pythonutf8
security-guidance: purge text=True from subprocess.run + bake PYTHONUTF8=1 (#2099)
2026-05-30 12:14:03 -07:00
5 changed files with 78 additions and 58 deletions

View File

@@ -35,7 +35,7 @@
"url": "https://github.com/adobe/skills.git",
"path": "plugins/creative-cloud/adobe-for-creativity",
"ref": "main",
"sha": "0a015c06894332091b79e055e0404fbc1a18c9fe"
"sha": "8d74ee6b6fdce4a1c46b98b5a66706c9393fc369"
},
"homepage": "https://github.com/adobe/skills/tree/main/plugins/creative-cloud/adobe-for-creativity"
},
@@ -120,7 +120,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/amazon-location-service",
"ref": "main",
"sha": "9d46cc0a092c0a8c01a5bd06a4349985cc6c8f08"
"sha": "f16aaf2a4ec7d59963c4fdf91e7358bd485e992e"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -226,7 +226,7 @@
"source": "url",
"url": "https://github.com/BrainBlend-AI/atomic-agents.git",
"path": "claude-plugin/atomic-agents",
"sha": "bb9708ec7c4c7145bd64033dbece0bfaed0c2ad5"
"sha": "57d6099f499fe21572bdf943396630bd3d968550"
},
"homepage": "https://github.com/BrainBlend-AI/atomic-agents",
"tags": [
@@ -245,7 +245,7 @@
"url": "https://github.com/auth0/agent-skills.git",
"path": "plugins/auth0",
"ref": "main",
"sha": "c38453f6a99bbfeaf73b5be81db987ec6af982da"
"sha": "a4fdd4d0605239aa7be75a57371f4e7e71efc40c"
},
"homepage": "https://auth0.com/docs/quickstart/agent-skills"
},
@@ -274,7 +274,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/aws-amplify",
"ref": "main",
"sha": "9d46cc0a092c0a8c01a5bd06a4349985cc6c8f08"
"sha": "f16aaf2a4ec7d59963c4fdf91e7358bd485e992e"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -335,7 +335,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/aws-serverless",
"ref": "main",
"sha": "9d46cc0a092c0a8c01a5bd06a4349985cc6c8f08"
"sha": "f16aaf2a4ec7d59963c4fdf91e7358bd485e992e"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -346,7 +346,7 @@
"source": {
"source": "url",
"url": "https://github.com/microsoft/azure-skills.git",
"sha": "7cb89c221ecc9eccb71580aaff3695408cdeef2b"
"sha": "d3440b8a4f138585a512ecd4e0c54ede13ab1cc2"
},
"homepage": "https://github.com/microsoft/azure-skills"
},
@@ -442,7 +442,7 @@
"url": "https://github.com/carta/plugins.git",
"path": "plugins/carta-cap-table",
"ref": "main",
"sha": "e66d331cd8e669ee121c96ee35b0c91acd828970"
"sha": "f512df80dd54fd0a607ece2f18a9d226ca705019"
},
"homepage": "https://carta.com"
},
@@ -458,7 +458,7 @@
"url": "https://github.com/carta/plugins.git",
"path": "plugins/carta-crm",
"ref": "main",
"sha": "e66d331cd8e669ee121c96ee35b0c91acd828970"
"sha": "f512df80dd54fd0a607ece2f18a9d226ca705019"
},
"homepage": "https://carta.com"
},
@@ -474,7 +474,7 @@
"url": "https://github.com/carta/plugins.git",
"path": "plugins/carta-investors",
"ref": "main",
"sha": "e66d331cd8e669ee121c96ee35b0c91acd828970"
"sha": "f512df80dd54fd0a607ece2f18a9d226ca705019"
},
"homepage": "https://carta.com"
},
@@ -490,7 +490,7 @@
"source": {
"source": "url",
"url": "https://github.com/cap-js/mcp-server.git",
"sha": "92dc99f5ba0c56957ed5d390484693a69ebd1206"
"sha": "9658cea90c782a6ab007ac16278c90fa4feca0ed"
},
"homepage": "https://cap.cloud.sap/"
},
@@ -716,7 +716,7 @@
"source": {
"source": "url",
"url": "https://github.com/CodSpeedHQ/codspeed.git",
"sha": "407dd3c930b8dc5e5655a2d91a65d88f01829955"
"sha": "f79d57d207f039e44a31a976564715f7731e71b6"
},
"homepage": "https://codspeed.io"
},
@@ -753,7 +753,7 @@
"source": {
"source": "url",
"url": "https://github.com/get-convex/convex-backend-skill.git",
"sha": "ece93250d560f0ce32a24223dea92b33050b2a66"
"sha": "002f9c834cdb834ddef1e4867d87cb6e80f0acba"
},
"homepage": "https://github.com/get-convex/convex-backend-skill",
"keywords": [
@@ -784,7 +784,7 @@
"source": {
"source": "url",
"url": "https://github.com/CrowdStrike/foundry-skills.git",
"sha": "fb25d60ecdbc0129071802dad210a65168ca55a9"
"sha": "2908d2f470f14f58d378b26c2af58825fa8d0149"
},
"homepage": "https://github.com/CrowdStrike/foundry-skills"
},
@@ -878,7 +878,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/databases-on-aws",
"ref": "main",
"sha": "9d46cc0a092c0a8c01a5bd06a4349985cc6c8f08"
"sha": "f16aaf2a4ec7d59963c4fdf91e7358bd485e992e"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -946,7 +946,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/deploy-on-aws",
"ref": "main",
"sha": "9d46cc0a092c0a8c01a5bd06a4349985cc6c8f08"
"sha": "f16aaf2a4ec7d59963c4fdf91e7358bd485e992e"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -1114,7 +1114,7 @@
"source": {
"source": "url",
"url": "https://github.com/firecrawl/firecrawl-claude-plugin.git",
"sha": "e71cec486062680f0c8f8823afcb3558ad81ce60"
"sha": "81178096fa7ae860285923948b8ba13d03c7fa0c"
},
"homepage": "https://github.com/firecrawl/firecrawl-claude-plugin.git"
},
@@ -1217,7 +1217,7 @@
"source": {
"source": "url",
"url": "https://github.com/huggingface/skills.git",
"sha": "df627be1837523c91ac6df472e3dc543d3107bd9"
"sha": "49abf82b2ef2ff2bcc6ca072aac0fe8627390a1d"
},
"homepage": "https://github.com/huggingface/skills.git"
},
@@ -1231,7 +1231,7 @@
"source": {
"source": "url",
"url": "https://github.com/hunter-io/claude-plugin.git",
"sha": "9b6146520c48f9dcc6092f106e5c1a5762ca3e7a"
"sha": "4eb5fbbcb75e0c4c4f2c0d8aa02756165fdde629"
},
"homepage": "https://hunter.io"
},
@@ -1245,7 +1245,7 @@
"source": {
"source": "url",
"url": "https://github.com/heygen-com/hyperframes.git",
"sha": "bc3701f5905c5ba7c8cf03c3bbe3a49162d2b1f1"
"sha": "f8abff2e1c0958b61419b4d2ad91ceca7a7ea110"
},
"homepage": "https://hyperframes.heygen.com"
},
@@ -1665,7 +1665,7 @@
"url": "https://github.com/oracle-samples/oracle-aidp-samples.git",
"path": "ai/claude-code-plugins/oracle-ai-data-platform-workbench-spark-connectors",
"ref": "main",
"sha": "6e59f24cd3e8870649e7f9b2e3e106502b43fd5f"
"sha": "d20075e440af2a209bc3b011a336276336e6d10c"
},
"homepage": "https://docs.oracle.com/en/cloud/paas/ai-data-platform/index.html"
},
@@ -1681,7 +1681,7 @@
"url": "https://github.com/growthxai/output.git",
"path": "coding_assistants/claude/plugins/outputai",
"ref": "main",
"sha": "0eeffece25b6f471c48b705a214471164b8c5946"
"sha": "5a87ebc13343ce6ebabac0bcc443c1da0bcf2459"
},
"homepage": "https://output.ai"
},
@@ -1729,7 +1729,7 @@
"source": {
"source": "url",
"url": "https://github.com/gopigment/ai-plugins.git",
"sha": "4bf16c80558416b9d69fa6531af8588fb2fcbe27"
"sha": "abf36e64750d1323a4cc5fe79161597668231224"
},
"homepage": "https://www.pigment.com"
},
@@ -1985,7 +1985,7 @@
"source": {
"source": "url",
"url": "https://github.com/Digital-Process-Tools/claude-remember.git",
"sha": "c2c82ab5fd2f4f5c0cddc9c7d8a749655dec4cb9"
"sha": "a4ff96f38622f7c4920dc349d59cc980663336f4"
},
"homepage": "https://github.com/Digital-Process-Tools/claude-remember"
},
@@ -2113,7 +2113,7 @@
"source": {
"source": "url",
"url": "https://github.com/cap-js/mcp-server.git",
"sha": "92dc99f5ba0c56957ed5d390484693a69ebd1206"
"sha": "9658cea90c782a6ab007ac16278c90fa4feca0ed"
},
"homepage": "https://cap.cloud.sap/"
},
@@ -2131,7 +2131,7 @@
"url": "https://github.com/SAP/open-ux-tools.git",
"path": "packages/fiori-mcp-server",
"ref": "main",
"sha": "7432d23a7b5c3bd1c0a01cf76696bf0c417ecd1f"
"sha": "c3e0940ef29ac520b4cc5bd6fd71fecede7b3342"
},
"homepage": "https://github.com/SAP/open-ux-tools/tree/main/packages/fiori-mcp-server"
},
@@ -2198,7 +2198,7 @@
"source": {
"source": "url",
"url": "https://github.com/getsentry/sentry-for-claude.git",
"sha": "d6123be331e2224b037e1ffefd27c806e7566dcf"
"sha": "849303a8411c242d250885ffe714235a3bc2f5fe"
},
"homepage": "https://github.com/getsentry/sentry-for-claude/tree/main"
},
@@ -2214,7 +2214,7 @@
"url": "https://github.com/getsentry/cli.git",
"path": "plugins/sentry-cli",
"ref": "main",
"sha": "db90767935558db16c45036f89e68edaa1dde106"
"sha": "213c156a47fb2952287938f91e94b5073402530d"
},
"homepage": "https://sentry.io"
},
@@ -2411,7 +2411,7 @@
"source": {
"source": "url",
"url": "https://github.com/obra/superpowers.git",
"sha": "f2cbfbefebbfef77321e4c9abc9e949826bea9d7"
"sha": "6fd4507659784c351abbd2bc264c7162cfd386dc"
},
"homepage": "https://github.com/obra/superpowers.git"
},
@@ -2445,7 +2445,7 @@
"source": {
"source": "url",
"url": "https://github.com/JetBrains/teamcity-cli.git",
"sha": "9436b94b228579ba952aba809357776c3db9ce1a"
"sha": "533c8cb20928be912188fd8ae40fcba24cc720ca"
},
"homepage": "https://www.jetbrains.com/teamcity/"
},
@@ -2620,7 +2620,7 @@
"source": {
"source": "url",
"url": "https://github.com/wix/skills.git",
"sha": "c5b343f2dadba06da91ee6de07272161fb68d40d"
"sha": "357f8d5b7cd81455e6a57cc64dba83985131d78f"
},
"homepage": "https://dev.wix.com/docs/wix-cli/guides/development/about-wix-skills"
},

View File

@@ -355,9 +355,9 @@ def _list_untracked(cwd):
the holdouts."""
try:
repo = _git_toplevel(cwd) or cwd
# core.quotePath=false comes from GIT_CMD globally (see gitutil.py).
r = subprocess.run(
[*GIT_CMD, "-c", "core.quotePath=false", "ls-files",
"--others", "--exclude-standard", "-z"],
[*GIT_CMD, "ls-files", "--others", "--exclude-standard", "-z"],
cwd=repo, capture_output=True, timeout=15,
)
if r.returncode != 0:

View File

@@ -26,6 +26,17 @@ GIT_CMD = [
"git",
"-c", "core.fsmonitor=false",
"-c", "core.hooksPath=/dev/null",
# core.quotePath=false: emit raw UTF-8 in path-emitting commands instead
# of C-quoting non-ASCII bytes (default `"\\303\\201vila/..."` vs
# `Ávila/...`). Downstream parsers — both ours (parse_diff_into_files,
# extract_file_paths_from_diff) and Python stdlib (os.path.isabs,
# os.path.join) — expect raw paths and silently drop / mishandle the
# quoted form. Adding the flag globally to GIT_CMD covers every
# subprocess.run site that uses the splat — diff feeders, rev-parse
# path queries (--show-toplevel, --git-dir, --git-common-dir),
# reflog %gs subjects, ls-files, status, etc. — without per-site
# flag duplication. See #2082, #2099.
"-c", "core.quotePath=false",
]
@@ -222,15 +233,12 @@ def _git_diff_range(repo_root, base, head="HEAD"):
them reviewed — otherwise unreviewed commits get permanently silenced.
"""
try:
# core.quotePath=false makes git emit raw UTF-8 in `diff --git a/... b/...`
# headers instead of C-quoting non-ASCII path bytes (`"a/\303\201vila/..."`
# vs `a/Ávila/...`). The downstream `re.match(r'^a/(.+?) b/(.+)$', ...)`
# in parse_diff_into_files / extract_file_paths_from_diff matches the
# raw form only — quoted headers slip past and the entire file is
# silently dropped from review. See #2082 (sibling of #2056 / #2075).
# GIT_CMD globally passes core.quotePath=false (see definition) so
# non-ASCII paths in `diff --git a/... b/...` headers come through as
# raw UTF-8, not C-quoted. Required by the downstream
# parse_diff_into_files / extract_file_paths_from_diff regex.
r = subprocess.run(
[*GIT_CMD, "-c", "core.quotePath=false",
"diff", "-p", "--no-color", "--no-ext-diff", base, head],
[*GIT_CMD, "diff", "-p", "--no-color", "--no-ext-diff", base, head],
cwd=repo_root, capture_output=True, timeout=30,
)
if r.returncode != 0:
@@ -355,8 +363,9 @@ def _git_name_only(cwd, base, include_untracked=False):
# result.stdout=None, and propagate AttributeError out of the helper.
# Same fix shape as diffstate._list_untracked. See #2056.
def _run(env):
# core.quotePath=false comes from GIT_CMD globally (see definition).
result = subprocess.run(
[*GIT_CMD, "-c", "core.quotePath=false", "diff", "--name-only", "-z", base],
[*GIT_CMD, "diff", "--name-only", "-z", base],
cwd=cwd, capture_output=True, timeout=30,
env=env,
)
@@ -393,9 +402,9 @@ def _git_status_porcelain(cwd):
# sibling helpers — a non-ASCII path in the worktree would otherwise
# crash the cp1252 reader thread on Windows. See #2056.
try:
# core.quotePath=false comes from GIT_CMD globally (see definition).
r = subprocess.run(
[*GIT_CMD, "-c", "core.quotePath=false", "status",
"--porcelain=v1", "-uall", "-z"],
[*GIT_CMD, "status", "--porcelain=v1", "-uall", "-z"],
cwd=cwd, capture_output=True, timeout=30,
)
if r.returncode != 0:
@@ -471,11 +480,8 @@ def get_git_diff(cwd, baseline_sha, full_context=False, paths=None, untracked_pa
# change exists to fix.
return ""
# core.quotePath=false: emit raw UTF-8 in `diff --git a/... b/...` headers
# so non-ASCII paths aren't C-quoted past the downstream parse_diff_into_files
# regex. See #2082 (sibling of #2056 / #2075).
cmd = [*GIT_CMD, "-c", "core.quotePath=false",
"diff", "--no-color", "--no-ext-diff", baseline_sha] + (["--unified=99999"] if full_context else []) + pathspec
# core.quotePath=false comes from GIT_CMD globally (see definition).
cmd = [*GIT_CMD, "diff", "--no-color", "--no-ext-diff", baseline_sha] + (["--unified=99999"] if full_context else []) + pathspec
try:
with _temp_index(cwd, untracked_paths) as env:
# env is None when no index could be found (bare repo / not a

View File

@@ -479,10 +479,21 @@ def _call_claude(prompt, output_schema, thinking_budget=10000, max_tokens=16000,
"max_tokens": max_tokens,
"system": CLAUDE_CODE_SYSTEM_PROMPT,
"messages": [{"role": "user", "content": prompt}],
"output_format": {
"type": "json_schema",
"schema": output_schema
}
# API moved the structured-output schema from top-level `output_format`
# to `output_config.format` per
# https://platform.claude.com/docs/en/build-with-claude/structured-outputs.
# The old form "continues to work for a transition period" for some
# auth modes (API key + non-streaming), but is rejected with
# `invalid_request_error: output_format: This field is deprecated.
# Use 'output_config.format' instead.` for others (OAuth Bearer +
# newer CLI versions hit it consistently — reporter saw 462 errors
# in one day). See #2098.
"output_config": {
"format": {
"type": "json_schema",
"schema": output_schema,
},
},
}
if thinking_budget > 0:
# Models trained on adaptive thinking (4.6+) reject the budget_tokens
@@ -490,7 +501,10 @@ def _call_claude(prompt, output_schema, thinking_budget=10000, max_tokens=16000,
# models (4.5 and earlier, all 3.x) reject adaptive. Pick by model.
if _model_supports_adaptive_thinking(payload["model"]):
payload["thinking"] = {"type": "adaptive"}
payload["output_config"] = {"effort": "high"}
# Merge `effort` into the existing output_config dict (which
# now carries the `format` schema) rather than reassigning —
# otherwise the schema is silently overwritten. See #2098.
payload["output_config"]["effort"] = "high"
else:
payload["thinking"] = {
"type": "enabled",

View File

@@ -1197,18 +1197,18 @@ def handle_commit_review_posttooluse(input_data):
# core.quotePath=false: emit raw UTF-8 in `diff --git a/... b/...`
# headers so non-ASCII paths aren't C-quoted past the downstream
# parse_diff_into_files regex (sibling of #2056 / #2075). See #2082.
# core.quotePath=false comes from GIT_CMD globally (see gitutil.py).
if pre_amend_sha:
# Delta review: pre-amend → post-amend. `git diff` (not show)
# so the output is a pure unified diff with no commit header.
result = subprocess.run(
[*GIT_CMD, "-c", "core.quotePath=false",
"diff", "--no-color", "--no-ext-diff", pre_amend_sha, sha, "--"],
[*GIT_CMD, "diff", "--no-color", "--no-ext-diff",
pre_amend_sha, sha, "--"],
cwd=repo_root, capture_output=True, timeout=15
)
else:
result = subprocess.run(
[*GIT_CMD, "-c", "core.quotePath=false",
"show", "-p", "--no-color", "--no-ext-diff", sha, "--"],
[*GIT_CMD, "show", "-p", "--no-color", "--no-ext-diff", sha, "--"],
cwd=repo_root, capture_output=True, timeout=15
)
except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e: