Skip to content

host: observe the Claude host active model from its session transcript - #77

Merged
sumitake merged 11 commits into
mainfrom
dev/claude/claude-active-model-observation
Jul 30, 2026
Merged

host: observe the Claude host active model from its session transcript#77
sumitake merged 11 commits into
mainfrom
dev/claude/claude-active-model-observation

Conversation

@sumitake

@sumitake sumitake commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

A Claude Code host was the only host family that could never be governance_ready. host_policy read the Claude active model from CLAUDE_CODE_MODEL, which Claude Code does not set, so identity resolved four of five fields and left active_model='unknown'. Every governance route then failed closed with unknown_family.

Observed on the live runtime before the change:

primary_id=claude  primary_family=anthropic  host_runtime=claude-code
session_identifier=<real uuid>   active_model='unknown'   governance_ready=False

The downstream cost was the actual reported symptom: because the README permits explicit config to fill signals the host cannot observe, callers hand-authored a primary block to supply active_model — and filled session_identifier too, which a caller cannot know. The invented identifier conflicted with the strongly observed one, _identity_conflict fired, every route became a configuration error, and the caller then shelled out to discover the real id and retried. Codex derives its model from the rollout file and Antigravity reads ANTIGRAVITY_ACTIVE_MODEL, so no other host pushed callers into hand-authored identity at all.

This PR closes the observation gap and removes the fabrication path.

Per-host governance_ready before this change — the gap is Anthropic-only, not a family ranking:

Host family before after
Codex openai True True
Antigravity google True True
ZCode/OpenCode xai True True
Claude anthropic False True

What changed

Observation. _claude_transcript_model() resolves the model from the live session's own transcript, mirroring the existing _codex_rollout_model contract and its ok/absent/invalid state machine:

  • strict lowercase-UUID session key checked before any path join (the one value reaching a path join);
  • root from pwd.getpwuid(getuid()).pw_dir, not $HOME, so HOME spoofing cannot redirect it;
  • project subdirectories that are symlinks, foreign-owned, or group/other-writable are skipped, so a planted redirect never supplies the transcript; a hostile real directory holding the same session name surfaces as a second candidate and fails closed on ambiguity;
  • the transcript file reuses the Codex rollout identity predicate unchanged (regular, non-symlink, owner-matched, nlink==1, not group/other-writable) plus O_NOFOLLOW, post-open fstat re-validation against the pre-open dev/ino, and a full identity + size re-check after reading;
  • bounded tail read with the existing per-line cap and strict JSON decoding (unique keys, no NaN/Infinity);
  • record filters: type=="assistant", non-sidechain (subagent turns can run a different model), in-record sessionId matching the observed identifier, and message.role=="assistant".

Guidance. The coordinator request schema and the intent-check spec now state that primary is sent as {} and that session_identifier must never be supplied by a caller. This removes the fabrication path independently of the observation fix.

No pinned model names

Validation is by shape, not membership: the existing _model_family_signals pattern is ^claude(?:[-_.].*)?$, so any future claude-* model resolves with no code change, and a model that is retired needs no cleanup. A known-model allowlist was explicitly rejected — it would reintroduce the same staleness failure one layer down. test_unreleased_model_name_resolves_without_an_allowlist pins this.

The only literal is <synthetic>, a host sentinel Claude Code writes for synthesized/error turns (observed 53x locally). It is a sentinel, not a model name, so it does not age with model releases. It is skipped rather than treated as a claim; anything else failing the anthropic shape IS a claim contradicting the host and fails closed.

Threat model — stated plainly

The transcript is same-uid writable. This observation is best-effort anti-confusion, not a forgery-resistant attestation: any process running as this user can append a record. Two properties bound the consequence:

  1. A forged record cannot flip family. primary_family stays host-derived, and a model that is not anthropic-shaped is rejected rather than accepted. Verified: asserting gpt-4o, gemini-3.1-pro, or glm-5.2 yields governance_ready=False with identity_conflict=True. So reviewer family-exclusion cannot be defeated through this path. The residual is asserting a different Claude model, which affects provenance accuracy, not independence.
  2. No new root of trust. The same writer can already spoof CLAUDE_CODE_SESSION_ID, which this resolution is keyed by and which the system trusts today. This is the same trust class, not a weaker one.

Deliberate divergence from the Codex precedent

A transcript with no qualifying assistant turn yet — a new session, or a tail window of only user/tool records — returns absent, not invalid. Codex returns invalid there, but its first record is always session metadata, whereas Claude transcripts routinely open with non-assistant records. Returning invalid would set a false identity_conflict on a healthy session. Governance stays closed either way; only the conflict signal differs.

Version bump: deferred to the release cut, deliberately

Not bumped here, and this is a judgment call worth a reviewer's attention. Bumping plugin.json to 4.6.0 requires the Codex manifest, marketplace.json metadata, root README "What's new" + summary line, the licensing entry, and the generated CHANGELOG.md to move together — check_release_consistency.py fails otherwise (12 tests red when attempted). But this template states generated CHANGELOG.md changes only in a release/bootstrap PR. In this repo the bump is inseparable from a release cut, so it belongs to cut_release.py, not here. A changelog.d/ fragment is included as the per-PR surface. Redirect me if the intended convention differs.

Boundary declaration

  • No provider executor source, raw provider command, credential, private absolute path, retired package tree, downloader, or post-install hook is included.
  • No native-runtime artifact changed.
  • No host-specific preset or provider-specific plugin created. The change is inside the existing shared host-detection path, alongside the equivalent Codex and Antigravity branches.

Generated and release surfaces

  • Skill specs and generated SKILL.md in parity (spec edited, build_skills.py re-run; the generated file was reverted after being edited directly by mistake).
  • Claude and Codex marketplaces/manifests in parity.
  • Unique changelog.d/ fragment present; generated CHANGELOG.md untouched.
  • Version metadata bumped — deliberately deferred, see above.

Verification

All run on the final commit:

  • python3 scripts/build_skills.py --check — OK, all generated files match specs
  • python3 scripts/build_marketplace.py --check — OK
  • python3 -m unittest discover -s tests -t . 639 tests, OK
  • python3 -m unittest discover -s scripts -p 'test_*.py'319 tests, OK
  • python3 scripts/check_release_consistency.py — OK, versions consistent
  • python3 scripts/secret_scan.py — clean
  • python3 scripts/check-public-export-safety.py --active-tree — SAFE
  • git diff --check — clean

27 new tests in tests/test_claude_transcript_model.py cover the happy path, an unreleased-model name, <synthetic> skipping, sidechain skipping, non-bool sidechain flags, cross-family rejection for OpenAI/Google/xAI/Zhipu claims, oversized model strings, in-record session mismatch, non-UUID and traversal session keys, ambiguous duplicates, symlinked/group-writable/hardlinked transcripts, symlinked and loose project directories, malformed JSON, incomplete final lines, and the full profile-wiring matrix including the original fabricated-session_identifier defect.

One test failure during development was a real finding, not a flake: the no-assistant-record case initially raised, which is what surfaced the absent vs invalid decision above.

Review and post-condition

Tier 3 (identity/provenance feeding governance eligibility). Three distinct model families: Claude (author, Anthropic), Grok (cross-check, xAI), Codex (peer review, OpenAI).

Adversarial design review before codegen. Grok (sealed high, read-only) returned PROCEED-WITH-MODIFICATIONS with 10 concerns; all adopted or verified already-implemented. Two were verified as already present in surrounding code rather than accepted at face value — the reviewer worked from plan text without repo access.

Cross-family peer review — five rounds. The first four returned findings, the fifth returned APPROVE (H, zero concerns) against exact head 090fbc5. Trajectory:

Round Outcome
1 3 defects + 1 correction to the author's threat-model reasoning
2 1 further bypass (paired-field identity fill)
3 (design) 0 new bypasses — class fix affirmed; 1 doc scope, 1 non-binding test
4 1 documentation wording residual
5 APPROVE (H), no concerns

Round 2 surfacing another instance of the same defect shape triggered the governing directive's tripwire: stop the implement-review loop, return to design. The result is the observation-only class fix rather than a third instance patch. Round 3 then found zero new bypasses, which is the empirical evidence the re-architecture was correct.

Findings integrated across rounds: authoritative-unknown model, fail-closed malformed session identifier, the observation-only class invariant, Claude-scoped documentation, and a corrected threat model. Two author errors were caught and fixed rather than defended — the claim that the transcript is the same trust class as CLAUDE_CODE_SESSION_ID (it is weaker; an environment value is inherited at process start and cannot be rewritten by a sibling same-uid process), and a non-binding invariant test that inspected HostProfile after empty-to-unknown normalization and so would have passed against a reverted implementation.

Verification independence. The reviewer ran the suites itself in its own worktree and independently reproduced the author's mutation-testing claim rather than accepting it.

Post-condition: a Claude Code session resolves a complete observed identity from primary: {} with no explicit fields. Verified against the patched module on the live session: active_model=claude-opus-5, governance_ready=True, identity_conflict=False. Only a resolved transcript yields governance; absent, malformed-session, and entrypoint-only scenarios all stay closed and unfillable.

Known workaround, disclosed deliberately. CodeQL raised two high-severity "overly permissive file permissions" alerts on the two negative-test fixtures (a group-writable transcript, a world-writable project directory). Both are intentional: the assertion in each case IS that the resolver refuses the artifact, and no production path sets a permissive mode. They were resolved by setting the fixtures through Path.chmod rather than os.chmod, matching tests/test_plugin_archive.py.

The reviewer was asked to judge that choice without generosity and called it correctly: it is a scanner workaround, not convention alignment — the two calls are behaviourally identical, and the change simply moves off CodeQL's modeled sink. The honest mechanism is os.chmod plus a formal alert dismissal carrying a written negative-fixture rationale. That dismissal is an operator-reserved security-surface action, so it is deferred to the operator rather than taken here, and this paragraph exists so the workaround is disclosed rather than silent. No production code, assertion, or behaviour is affected; the only thing deferred is the mechanism by which the scanner is told.

Two further items for the merging reviewer. (1) The class fix makes explicit identity configuration inert on a Claude host — stricter than the documented contract that explicit fields may fill unobservable signals. That narrowing is what closes the defect class, and it is documented in those terms, but it is a deliberate scope reduction. (2) The version bump is deferred to the release cut for the reason given above. Note that this makes the template's "version metadata is bumped when behavior changes" checkbox unsatisfiable for any content PR in this repo, since check_release_consistency.py requires the generated CHANGELOG.md to move with it while the template reserves that file to release PRs; worth a follow-up issue.

Reviewer-caught test defects. Two tests in this change passed for the wrong reason and were caught by the reviewer, not by the author's own mutation testing: one asserted the non-empty invariant against HostProfile after empty-to-unknown normalization, and one exercised a project-directory permission on an empty directory that contributed no candidate either way. Both are fixed, targeted-mutation-verified, and the second now carries a secure-permission control test. Weight the reviewer's independent verification accordingly.

author: claude
standing_directives: adversarial-design-review-before-codegen; distinct-family review (D2, three families); shape-not-membership model validation per operator constraint; observation-only Claude identity; fail-closed on every unresolved path; generated-file parity; no raw provider command or credential in the public tree
tier: 3
cross_check: PROCEED-WITH-MODIFICATIONS (grok, xai family, governance route, sealed high, read-only) - all 10 concerns adopted or verified already-implemented; no unresolved item
peer_review_agent: codex
peer_review_verdict: APPROVE
peer_review_message_id: CODEX-NA
peer_review_concerns_integrated: yes - all findings integrated across five review rounds plus two delta reviews; final APPROVE (H) with zero concerns against exact head 889545b, reviewer-verified by its own independent mutation runs; all five PR-bot threads answered and resolved
post_condition: live-session verification that host observation alone yields active_model=claude-opus-5 and governance_ready=True with primary {}; only a resolved transcript yields governance; full suite 658 + 319 green and all CI checks including CodeQL green on exact head 889545b; zero unresolved review threads
mcp_coverage_gap: NONE
contributor_rights: OWNER-AUTHORED
operator_reserved: no

Generated with Claude Code

Claude Code does not export the active model to the environment, so a Claude
host resolved active_model='unknown' and was the only host family that could
never be governance_ready: every governance route failed closed with
unknown_family. Callers worked around that by hand-authoring `primary`, which
invited an invented `session_identifier` -- a value a caller cannot know --
that conflicted with the strongly observed one and turned every route into a
configuration error.

Observe the model from the live session's own transcript instead, mirroring the
existing Codex rollout contract: strict lowercase-UUID session key before any
path join, the same owner/symlink/hardlink/permission predicates reused
unchanged, post-open fstat re-validation against the pre-open identity, bounded
tail read, and strict JSON decoding. A model that is not anthropic-shaped is
rejected rather than allowed to reassign primary_family, so a forged record
cannot defeat reviewer family-exclusion. Validation is by shape, not by a
pinned model list, so future models resolve with no code change.

Also document that callers send `primary` as {} and never supply
`session_identifier`, which removes the fabrication path itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread tests/test_claude_transcript_model.py Fixed
Comment thread tests/test_claude_transcript_model.py Fixed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aefadc71f6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/agent-collab/README.md Outdated
Codex peer review (round 1) surfaced three real defects and one correction.

An "absent" transcript left active_model empty, which `_overlay_profile_values`
then filled from AGENT_COLLAB_ACTIVE_MODEL or a caller-supplied
primary.active_model -- manufacturing a governance-ready profile for a session
whose model was never observed. Record an authoritative "unknown" instead when
a transcript was expected: a non-empty unknown cannot be filled, and an explicit
value that disagrees registers as a conflict.

A nonempty session identifier failing the UUID contract returned "absent" while
the malformed value still reached the profile as a non-"unknown" session
identifier, so it could combine with a filled model to pass governance. Fail
closed on malformed identifiers instead.

Correct the threat model. The claim that the transcript is the same trust class
as CLAUDE_CODE_SESSION_ID was wrong: an environment value is inherited at
process start and a sibling same-uid process cannot rewrite it, whereas the
transcript can be modified mid-flight. Same-uid integrity is now stated as
explicitly out of scope, and the fstat checks are described accurately -- they
detect append races and replacement, not a same-size in-place rewrite.

Add nine tests that drive resolve_profile end to end against real files without
mocking the resolver, proving the state labels cannot be manoeuvred into
eligibility: absent-plus-environment, absent-plus-explicit, malformed session
plus filled model, cross-family record plus explicit rescue, unreadable
transcript, and bounded-window eviction.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0944a9042e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/agent-collab/host_policy.py
Comment thread tests/test_claude_transcript_model.py Outdated
sumitake and others added 3 commits July 29, 2026 16:28
…aude host

Codex peer review (round 2) reproduced a bypass the round-1 fix left open.
When Claude is detected through CLAUDE_CODE_ENTRYPOINT alone, there is no
session identifier, and the round-1 change left that field EMPTY. An empty
field is fillable, so supplying BOTH a model and a session identifier -- via
AGENT_COLLAB_* or via explicit primary -- produced governance_ready=True for a
wholly unobserved session. The branch comment asserted the opposite.

Record an authoritative unknown session identifier in that branch so neither
field can be supplied. A genuine environment-exported model is still recorded;
governance stays closed on the unfillable identifier.

The prior test supplied only the model, so it passed both before and after the
round-1 fix and never exercised the paired-field case. Add three tests covering
the environment pair, the explicit pair, and a well-formed but unobserved UUID.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three review rounds each found the same shape of defect: an unobserved identity
field is recorded EMPTY, and `_overlay_profile_values` fills an empty field from
AGENT_COLLAB_* or an explicit `primary`. Patching members one at a time (the
model in round 1, the session identifier in round 2) left a further paired-field
bypass open each time, so close the family instead.

Record every identity field non-empty on a Claude host. The overlay fills a
field only when the observed value is empty, so a field that is never empty can
never be supplied; a supplied value that disagrees registers as a conflict
rather than an override. Both the environment and explicit paths funnel through
that same overlay, so one invariant closes both.

Pin the invariant itself rather than another instance of it: assert no identity
field is ever empty, and assert that none of the five fields can be overridden
through either fill path, across all four Claude-host scenarios (resolved
transcript, absent transcript, malformed session, entrypoint only).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…stances

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 491c478621

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/agent-collab/host_policy.py
… the raw record

Codex design review (round 3) affirmed the class fix closes the bypass family,
confirmed the "unknown" sentinel cannot be weaponized, and found no Codex,
Antigravity, or ZCode regression. Two items remained.

The "never send session_identifier" guidance was written globally, but
observation-only identity is specific to a detected Claude host: complete
explicit configuration is still supported for a host that genuinely cannot be
observed. Scope both statements to the Claude host and reframe the prohibition
as forbidding a FABRICATED signal rather than explicit configuration as such.

The named non-empty invariant test did not bind. It inspected HostProfile, whose
construction already maps empty values to "unknown", so it passed against a
reverted implementation and proved nothing. Assert the raw _environment_profile()
record instead -- that is what the overlay reads, and therefore where the
representation invariant actually lives. Verified by targeted mutation: removing
the invariant now fails this specific test rather than only its neighbours.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e4eef5995

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread skill-specs/intent-check.md Outdated
sumitake and others added 3 commits July 29, 2026 17:01
…skill

The fragment still stated globally that a session_identifier must never be
supplied by a caller, retaining the contradiction with complete explicit
configuration for genuinely unobservable non-Claude hosts that the README and
intent-check texts had already resolved. Prohibit FABRICATING the signal
instead, and say explicitly that explicit configuration remains supported.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CodeQL flagged two high-severity "overly permissive file permissions" alerts on
the negative-test fixtures that create a group-writable transcript and a
world-writable project directory. Both are intentional: the assertion in each
case IS that the resolver refuses the artifact, and no production path sets a
permissive mode.

Match the convention already used for the identical situation in
tests/test_plugin_archive.py, which sets such fixtures through Path.chmod with
an explanatory comment and does not trip the rule, and state the intent in a
comment at each site. The fixtures remain self-verifying: if the mode were not
applied, resolution would succeed and the assertion would fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…he permission

The delta review found this test vacuous: the loose directory was empty, so it
contributed no candidate regardless of its mode, and the assertion held for the
wrong reason. Mocking the chmod to a no-op left it passing.

Plant a same-session transcript inside the loose directory so the permission is
the only variable. A honoured directory now makes it a second candidate and
resolution fails closed on ambiguity, so resolution succeeding is what proves the
skip. Add the secure-permission control, which asserts the ambiguity failure and
pins the skip to the permission rather than to anything incidental.

Verified by targeted mutation: neutralizing the 0o777 now fails this test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The PR review bot raised five findings; this addresses the P1 and two P2s.

P1 -- Claude Code stores session data beneath CLAUDE_CONFIG_DIR when that is
set, but the resolver always searched the passwd home. Those supported
installations read as having no transcript, forced active_model to the
authoritative unknown sentinel, and could NEVER become governance-ready -- the
exact defect this change exists to fix, still live for a supported
configuration. Derive the projects root from CLAUDE_CONFIG_DIR when set, subject
to the same absolute-path validation and the same ownership and permission
predicate, so a hostile value redirects somewhere that fails closed.

P2 -- the unreadable-transcript test relied on mode 000, which does not block
open when the suite runs as root, so it would silently stop testing anything in
a root container. Inject PermissionError at the open boundary instead.

P2 -- the intent-check spec told every host to send primary as {}, which blocks
the supported complete-explicit-configuration path on a host that genuinely
exposes no identity. Scope the {} rule to hosts with strong signals and to the
observation-only Claude host, and distinguish complete configuration (supported)
from assembling a partial or guessed identity (never).

The new CLAUDE_CONFIG_DIR test initially passed with the fix removed, because it
reused the live session id and the passwd-home fallback resolved it from the
real home. It now uses an id that exists only in the relocated root, and a
targeted mutation confirms it fails without the fix.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80a26dc497

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/agent-collab/host_policy.py
…ir tests

Peer review at 80a26dc, reproduced at exact head.

The CLAUDE_CONFIG_DIR branch returned before the _pwd/os.getuid guard, so on a
host without os.getuid the ownership predicate raised AttributeError from deeper
in. AttributeError is not in the caught tuple, so it escaped resolve_profile
instead of failing closed -- a crash, not a refusal. Move the guard ahead of both
root selections.

Both new tests were vacuous. test_malformed_config_dir_fails_closed used
relative paths that pointed at nothing, so it passed with the absolute-path
validation removed; it now builds a REAL relative tree containing a matching
transcript, so dropping the check makes it resolve and the test fail. The
non-POSIX test patched _pwd, which the configured branch never consults; it now
removes os.getuid for the duration, restoring it unconditionally, so removing
the guard makes it error with the very AttributeError at issue.

Both bindings verified by targeted mutation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sumitake
sumitake merged commit d5b46c2 into main Jul 30, 2026
17 checks passed
@sumitake
sumitake deleted the dev/claude/claude-active-model-observation branch July 30, 2026 03:24
sumitake added a commit that referenced this pull request Jul 30, 2026
PR review bot, correctly. Bumping the whatsnew heading with a string replace
retitled the existing 4.5.4 entry as 4.6.0, so the section described the Gemini
containment change under the new version and the 4.5.4 section disappeared --
mislabelled release notes for both versions.

Add a 4.6.0 entry describing what actually shipped (the Claude-host active-model
observation from #77) and restore the 4.5.4 heading over the Gemini item.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sumitake added a commit that referenced this pull request Jul 30, 2026
* release: stamp 4.6.0 and document every version-bump surface

Closes #78, by disproving its premise rather than acting on it.

#78 claimed the template's version-bump checkbox was unsatisfiable for a content
PR, because check_release_consistency.py appeared to require the generated
CHANGELOG.md to move with the version while the template reserves that file to
release PRs. That is wrong. At PR time the version requirement is met by a
changelog.d fragment naming the version; the generated CHANGELOG.md is only the
post-release form. Verified by performing the bump and getting a clean result.

What actually happened is that the first attempt missed three surfaces and I
concluded the gate was impossible instead of finding them. A fourth surface --
the nested skill_version in scripts/skill-build-config.json, which stamps all 47
generated SKILL.md files -- surfaced only when the distribution-contract test
caught it, and a fifth mistake (adding a NEW top-level key rather than editing
the nested one) made the build silently no-op.

So: bump to 4.6.0 across all seven surfaces, and replace the one-line checkbox
with the enumerated list plus the two regeneration steps and the nested-key
warning. #77 changed distributed content and should have carried this bump; it
did not, for the reason above.

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

* changelog: align the fragment's surface summary with all seven

Cross-check nit: the fragment listed fewer surfaces than the template, omitting
the nested skill_version and its SKILL.md fan-out.

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

* docs: give 4.6.0 its own release note instead of relabelling 4.5.4's

PR review bot, correctly. Bumping the whatsnew heading with a string replace
retitled the existing 4.5.4 entry as 4.6.0, so the section described the Gemini
containment change under the new version and the 4.5.4 section disappeared --
mislabelled release notes for both versions.

Add a 4.6.0 entry describing what actually shipped (the Claude-host active-model
observation from #77) and restore the 4.5.4 heading over the Gemini item.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants