Skip to content

Rectify: Interactive Policy Checkpoint Immunity — intent-carrying specs, wired config authority, and an unstubbed corridor startup net - #4688

Merged
Trecek merged 17 commits into
developfrom
impl-rectify-interactive-policy-checkpoint-immunity-20260817-190618
Aug 18, 2026
Merged

Rectify: Interactive Policy Checkpoint Immunity — intent-carrying specs, wired config authority, and an unstubbed corridor startup net#4688
Trecek merged 17 commits into
developfrom
impl-rectify-interactive-policy-checkpoint-immunity-20260817-190618

Conversation

@Trecek

@Trecek Trecek commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Commit 55b697427 (PR #4613) grafted an unconditional Claude agent-teams environment policy onto the interactive pre-spawn checkpoint. The opt-in authority agent_backend.force_claude_agent_teams_inactive was dead config — defined, never read anywhere in src/ — while the checkpoint enforced the policy on every corridor (cook, order, fleet) for every backend, crashing startup on any machine whose gitignored .claude/settings.local.json or shell env legitimately enables CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS.

The architectural weakness is threefold, and each half of it enabled the other:

  1. Intent-blind checkpoints. CmdSpec carried no record of what policy was requested at build time. A post-hoc checker structurally cannot know whether neutralization was asked for — so it degrades to "always enforce." The shipped docstring described behavior that could not exist.
  2. Authority declared but unconsumed, with no contract catching it. The config ledger proves a key parses; nothing proved a key is read. force_claude_agent_teams_inactive passed every contract while being dead from birth — and it was not alone.
  3. No behavioral net over the interactive corridor. 13 call sites across 7 test files stub validate_interactive_invocation to return []; the two real-gate tests run in sterile tmp dirs with no .claude/ settings; smoke lanes are excluded from PR CI. This is the fifth fail-closed-checkpoint corridor breakage in two weeks.

The immunity: (A) policy intent becomes data on the spec, stamped by the builder — checkpoints read intent, never infer it; (B) the config authority is wired end-to-end and a config-consumption contract test makes "declared but unread" a CI failure for every config field forever; (C) an unstubbed corridor startup net — realistic-fixture launches with a fake agent binary, running per-PR — makes any future checkpoint that fires on legitimate state an instant test failure, regardless of its mechanism.

Requirements

Policy intent becomes data on the spec, stamped by the builder, so checkpoints read intent
rather than infer it. The config authority is wired end-to-end and a config-consumption
contract makes "declared but unread" a CI failure for every config field. An unstubbed
corridor startup net running per-PR makes any future checkpoint that fires on legitimate
state an instant test failure, regardless of its mechanism.

Default-config launches must remain byte-for-byte unaffected, and the opt-in must remain
fail-closed when neutralization is genuinely impossible (#4575's original protection).

#4572 (attestation env drift tripping the executable-binding guard) is a separate defect
on the same functions and is explicitly out of scope.

Verification

Measured on a machine whose .claude/settings.local.json legitimately enables agent teams —
the configuration that triggered the defect:

develop (5e3a29a86) this branch
task test-check 81 failed, 39595 passed 16 failed, 39738 passed

The 16 remaining failures fail identically at the base commit and are set-wise a strict
subset of the baseline: zero regressions introduced, 65 baseline failures fixed. The
residue is the #4572 attestation-drift family, explicitly out of scope.

pre-commit run --all-files is clean (ruff, mypy, gitleaks, stub and contract checks).

Two deliberate departures from the plan

The opt-in was unreachable, and the plan preserved the ordering that made it so.
build_interactive_cmd asserted inactivity before stripping settings, so any repository
whose settings enabled teams raised instead of being neutralized — leaving
neutralize_repository_agent_teams_settings dead code for the exact population it exists to
serve. Neutralization now precedes confirmation. A malformed file is still refused rather
than rewritten, so the confirmation continues to fail closed there, preserving #4575's
guarantee where it matters.

The prescribed config-consumption exclusion rule would have flagged a live field.
Excluding _config_dataclasses.py wholesale reports GitHubConfig.allowed_labels as dead —
it is read solely by check_label_allowed(), an accessor colocated with the definition and
called from six production sites. The rule implemented is narrower: skip __post_init__
bodies only. Self-validation is not consumption; an accessor beside the definition is.

Not delivered

The cook black-box launch test. cook hangs before its confirmation prompt in a fully
hermetic environment for reasons unrelated to this change, and a timing-out test is worse
than no test. The cook corridor remains covered by test_force_inactive_config_corridors.py
(config read plus both build branches) and the existing cold-launch mediums; the corridor
realism gap is closed on the order path, which now launches against a teams-enabled
checkout and passes, including the exported-env variant.

Follow-ups filed

Two config fields are forward-declared rather than deleted to make the new contract green:
#4685 (ProviderProfileDef.context_window) and #4686
(RunSkillConfig.natural_exit_grace_seconds). Each carries a 180-day staleness time-bomb.

Closes #4687

Implementation Plan

Plan file: .autoskillit/temp/rectify/rectify_interactive_policy_checkpoint_immunity_2026-08-17_174353.md

🤖 Generated with Claude Code via AutoSkillit

Trecek and others added 17 commits August 17, 2026 19:30
A post-hoc checkpoint cannot know whether neutralization was requested
unless the spec says so, and CmdSpec said nothing. Add
force_inactive_agent_teams so intent is data the checkpoint reads rather
than policy it infers from environment content.

Four sites rebuilt CmdSpec from an explicit keyword allowlist, which
silently zeroes any field the allowlist predates. Convert all four to
dataclasses.replace so field-dropping reconstruction is eliminated as a
class rather than fixed once: LaunchResolver.finalize,
rehydrate_secret_environment, and the two build_cmd wrappers. Each
conversion is byte-identical for the fields it previously listed.

The field also enters the persisted, digest-bearing launch contract, so
LAUNCH_CONTRACT_SCHEMA_VERSION moves to 3; both rehydration consumers
already reject a version mismatch with a typed error rather than
mis-parsing. CANONICAL_LAUNCH_DIGEST_FIELDS needs no entry — it pins
top-level payload keys and "command" already covers the sub-dict.

No behavior change: every builder still defaults the field to False.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pre-spawn checkpoint enforced an agent-teams environment policy on
every corridor and every backend unconditionally, so any machine whose
own .claude/settings.local.json or shell env legitimately enables
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS could not launch. On this host that
was 67 failing tests.

Three changes restore the intended scope:

- assert_interactive_ordering returns to shape-only validation. It is
  backend-agnostic, so the Claude policy it hard-imported was blocking
  Codex launches too. Removing the graft drops the file back under its
  original 220-line budget; both size-budget entries that existed only
  to accommodate the graft are corrected rather than left describing a
  check that no longer exists.
- ClaudeCodeBackend.validate_interactive_invocation now returns early
  unless the spec declares force_inactive_agent_teams, making its
  docstring true and scoping the malformed-settings refusal back to the
  opt-in case it documents.
- build_interactive_cmd confirms rather than refuses when the flag is
  combined with an executable binding, and prepare_interactive_launch
  passes both the flag and project_root on its final build, not just the
  probe. Previously the kwarg was dropped on the build that produces the
  launched spec, and restoring it naively would have hit the refusal —
  the opt-in was never reachable through this flow.

Settings neutralization now runs before the confirmation rather than
after. Asserting first refused every repository whose settings enable
teams, which is exactly the population the opt-in serves, leaving
neutralize_repository_agent_teams_settings unreachable. A malformed file
is still refused rather than rewritten, so the confirmation continues to
fail closed there.

The executable-binding equality check is unchanged and is now the proof
that the binding was resolved from the neutralized env; #4572 remains
untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
force_claude_agent_teams_inactive parsed, validated, and was read by nothing.
It is now sourced at each corridor entry where config is already in scope and
threaded to the sites that actually construct a spec, not to the nearest
parameter that accepts it:

- cook reads it once and passes it to both build branches
- _run_interactive_session and _launch_cook_session gain the parameter, so
  order's three launch sites and all four interactive build sites carry it —
  including the non-probe fork that bypasses prepare_interactive_launch
- both fleet call sites, ad-hoc and campaign; wiring only the first would
  leave campaign launches silently on the default
- the headless skill and food-truck spec closures, which reach command
  construction through SkillSessionConfig and the builder keyword both

_run_headless_attempt's parameter feeds only adapter_digest, never the spec,
so it is fed from the same config read — otherwise two launches differing only
in this flag would hash identically.

Both `cfg` locals along the headless path are RunSkillConfig, not the root
config; the flag is read from ctx.config.agent_backend at each.

tests/arch/test_config_consumption.py makes "declared but never read" a CI
failure for every config field. Three domain differences from the capability
precedent it is modeled on:

- ProviderProfileDef is frozen and synthesized inside a property, so it is
  never a declared field type; the dataclasses are enumerated from the
  definition module directly rather than by walking AutomationConfig
- only __post_init__ bodies are excluded, not the definition file. Excluding
  the file reports GitHubConfig.allowed_labels as dead — it is read solely by
  check_label_allowed(), which six production sites call. A field that merely
  validates itself is dead; an accessor beside the definition is not
- the scan matches bare attribute names with no owner inference, and config
  names collide across classes (timeout x4, command/enabled/step_overrides/
  recipe_overrides x3). The docstring states plainly that this is a floor

That leaves exactly two unconsumed fields, forward-declared against #4685 and
#4686 rather than deleted to make the contract green.

pr_create_guard.py joins FAIL_CLOSED_GUARD_BASENAMES with both doc mirrors
updated: its config-read-error branch falls through to deny, which is a real
fail-closed condition. Allow-tests cover the legitimate states it arms on.

The order black-box launch now runs against a realistic checkout carrying
.claude/settings.json and settings.local.json with agent teams enabled — that
absence was the realism gap that let an ungated settings policy ship green —
plus a variant with the variable exported into the parent process env. Its PTY
driver moved to a shared harness. tests/cli/test_app_startup.py smoke-tests
--help for every command enumerated from the live cyclopts app.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… launch conditions

The black-box net's cook variant was removed from the previous commit because
it appeared to hang before its prompt. It didn't hang. run_cli_launch spawned
the child with start_new_session=True, which makes it a session leader
without a controlling terminal; cook's foreground-process-group management
then hit ENOTTY on tcgetpgrp during teardown. That was a harness defect, not
a cook defect — a real shell always supplies a controlling terminal. Fixed by
acquiring one (setsid + TIOCSCTTY) in preexec_fn.

With that fixed, cook launches in ~2s on a teams-enabled project, reaches its
confirmation prompt, and spawns the fake agent binary cleanly.

tests/cli/test_corridor_startup_blackbox.py replaces the removed cook test
and extends the net to all three interactive corridors: cook, order, and
fleet dispatch. They're covered independently because they don't share a
launch path — cook() runs its own inline build/validate loop rather than
routing through _run_interactive_session, so a passing order test proved
nothing about cook or fleet. Each case drives the real CLI as a subprocess
over a PTY with nothing stubbed and asserts the fake binary's exec marker
exists, not merely that a spec was built. Covers both agent-teams-via-
settings-file and agent-teams-via-exported-env, plus a default-config
settings-preservation check, for all three corridors.

Verified these tests actually catch the regression this branch fixes: with
the graft in _headless_helpers.py temporarily reintroduced, all 6 spawn
cases failed across cook/order/fleet; reverted and confirmed the file is
byte-identical to before.

Full suite: same 16 pre-existing failures as the prior commit (the #4572
attestation-drift family plus unrelated plugin-selector-integration cases),
zero new failures, 39749 passed (+11 from the corridor net and its recorded
proof cases).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…omment

The preceding assignment already makes the type obvious; the trailing
sentence restated it without adding information.
…agent_teams

All other call sites building this kwarg use a same-named local variable
(force_inactive_agent_teams=force_inactive_agent_teams); this was the one
asymmetric case that dropped "agent_" from the local name.
…ive_invocation

CodingAgentBackend.validate_interactive_invocation declares spec: CmdSpec
(non-Optional), and both production callers build spec via
dataclasses.replace(built_spec, ...), which always yields a CmdSpec.
No caller or fixture passes None.
write_fake_agent_binary's shim had no `set -e` and ended with an
unconditional `exit 0`; a failing printf/mv (e.g. missing or unwritable
$AUTOSKILLIT_STATE_DIR) was silently swallowed, leaving only a downstream
marker-assertion failure with no visibility into the real cause.
… with a prefix

_launch_roots mixed bare keys with xdg_-prefixed keys in one flat dict,
forcing its single caller to manually rebuild the nested xdg_roots dict
hermetic_launch_env expects. Return (roots, xdg_roots) instead.
…c names

Both docstrings ("~line 89", "~line 199") already drifted from the actual
call sites in _fleet_session.py (97, 208). Name each branch by its
fleet_mode instead of a positional reference that rots on the next edit.
master_fd is opened unconditionally before the try block and never closed
or reassigned elsewhere, so the `if master_open:` guard was always taken.
Close master_fd unconditionally, matching that it is always open at
cleanup time (contrast slave_open, which is genuinely toggled after a
successful os.close(slave_fd)).
…ss equality check

wrapped.cwd == "/work/repo" is implied by the dataclass equality assertion
on the next line, which compares every field including cwd.
"Five consecutive incidents" was an uncited, uncheckable claim repeated
across new test modules. Keep the invariant the test defends without the
unverifiable narrative.
test_skill_session_config_carries_intent only proved a constructor
round-trip; test_skill_spec_builder_threads_intent_into_the_spec already
proves the field exists on SkillSessionConfig and reaches the backend
builder. Drop the now-unused SkillSessionConfig import.
…test

test_order_reaches_spawn_with_exported_teams_variable duplicated
test_corridor_reaches_spawn_on_teams_enabled_project[exported_env-order]
byte-for-byte (same fixtures, same three assertions in the same order).
Host-side-effect coverage remains in the untouched sibling
test_order_launches_real_cli_without_host_side_effects. _launch_roots was
only used by the removed test, so it goes too.

User-approved DISCUSS finding from PR #4688 review-pr round 0.
…o force_inactive_agent_teams

Same concept had two names across layers: the config field said
"claude_agent_teams" while every downstream consumer (CLI kwargs, backend
builder params, CmdSpec field, launch-contract payload key) already used
the backend-neutral "inactive_agent_teams". Both names were introduced in
the same commit (613b4d7), and AgentBackendConfig's sibling fields
(backend, step_overrides, recipe_overrides) are all backend-neutral,
making the old name an outlier in its own class too.

Renamed the config field to match; registered the old key in
RETIRED_CONFIG_KEYS (src/autoskillit/config/settings.py) so pre-existing
.autoskillit/config.yaml files carrying the old key keep working via
automatic remap, added the matching migrations/0.10.986-to-0.10.987.yaml
note, bumped the version, and updated every call site, doc, and test
fixture. The downstream ~25-site name was left untouched — it includes a
field nested in the persisted, versioned launch-contract digest
(LAUNCH_CONTRACT_SCHEMA_VERSION=3) and Codex's no-op parameters, both a
disproportionately larger blast radius than the config-only rename.

User-approved DISCUSS finding from PR #4688 review-pr round 0 (critical,
cohesion dimension), investigated and independently validated by two
separate subagent passes before implementation.
@Trecek
Trecek enabled auto-merge August 18, 2026 14:40
@Trecek
Trecek added this pull request to the merge queue Aug 18, 2026
Merged via the queue into develop with commit 0b1b412 Aug 18, 2026
4 checks passed
@Trecek
Trecek deleted the impl-rectify-interactive-policy-checkpoint-immunity-20260817-190618 branch August 18, 2026 14:54
Trecek added a commit that referenced this pull request Aug 19, 2026
…cement gap as explicit out-of-scope

Independent audit confirmed the exact trap the plan's own Step 2.4 warned
against: assert_interactive_ordering's inline policy call was removed
without an equivalent call reaching the raw/non-managed
_run_interactive_session branch (the sole launch path for ad-hoc
fleet/campaign interactive sessions), leaving only an explanatory code
comment rather than the plan's specified remediation.

Investigated both remediation options the audit offered:
(a) add a Claude-backend-guarded enforcement call here, or
(b) document the gap explicitly in the plan's Out-of-Scope section.

Chose (b), after confirming (a) is far larger and more redundant than it
first appears: _run_interactive_session has NO force_inactive_agent_teams
parameter at all in this plan's scope — neither branch ever threads it,
so a guarded call alone would still be a no-op; the real fix requires
threading the flag through _run_interactive_session's signature and both
branches, plus _launch_cook_session and its callers. develop's tip
already ships exactly this (0b1b412 / #4688, 'Interactive Policy
Checkpoint Immunity'), independently, via a differently-shaped
architecture (a single inline call inside assert_interactive_ordering,
gated on spec.force_inactive_agent_teams, rather than this plan's Step
2.4 single-enforcement-point consolidation into
backend.validate_interactive_invocation). Hand-duplicating that fix here
would produce two competing, partial implementations of the same
corridor that must be reconciled against develop anyway.

Updated the plan's own §6 Out-of-Scope section (main repo, not this
worktree — plans live outside the implementation worktree) with the
full investigation trail, and tightened the code comment here to
reference it. The Codex-incompatibility rationale for not calling the
generic validate_interactive_invocation Protocol method is unchanged and
still applies regardless of which remediation path closes this gap.

Refs #4684, #4688.
Trecek added a commit that referenced this pull request Aug 19, 2026
…efactors

Post-rebase reconciliation of semantic conflicts that survived the textual
merge (no conflict markers, but wrong against the new integration base):

- _type_launch.py: drop the duplicate "force_inactive_agent_teams" key from
  the launch-record command payload. Both sides added it independently and
  git merged them cleanly into one dict; ruff F601 caught the duplicate.
  develop's placement (after process_idle_timeout_ms) is kept.

- test_subpackage_structure.py: bump the hardcoded _type_constants symbol
  count 168 -> 169. develop added CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT_ENV_VAR
  (+1) while this branch added EXPLORATION_FAILURE_CODES and
  KNOWN_UNAFFECTED_SKILL_IDS (+2); neither side's count absorbed the other's.

- test_cook_settings_local_agent_teams.py / test_cook_real_root_smoke.py:
  rebase the expected outcome onto #4688's remediation strategy. develop
  now neutralizes a conflicting env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS
  settings entry before the inactivity assertion rather than refusing the
  launch, so cook no longer raises for that case. Both tests keep their
  purpose — the real, unstubbed policy running against a real settings
  file — and now assert the file is actually rewritten and the launch env
  is agent-teams-free, plus that the opt-in leaves the repository untouched
  when off.

- tests/contracts/AGENTS.md: follow develop's rename of
  force_claude_agent_teams_inactive -> force_inactive_agent_teams.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Trecek added a commit that referenced this pull request Aug 19, 2026
…tive launch branch

The 24-line comment claimed #4688 (commit 0b1b412) "already shipped on
develop... threading the flag through both branches on its own
single-inline-call architecture." Verified via git show that commit never
touched this file; its actual change removed an inline env-policy call
elsewhere, converging on the single-enforcement-point design this branch
already uses — the opposite of what the comment described. Replaced with a
concise, durably-true summary and dropped the plan-section/date citations.
Trecek added a commit that referenced this pull request Aug 19, 2026
…cement gap as explicit out-of-scope

Independent audit confirmed the exact trap the plan's own Step 2.4 warned
against: assert_interactive_ordering's inline policy call was removed
without an equivalent call reaching the raw/non-managed
_run_interactive_session branch (the sole launch path for ad-hoc
fleet/campaign interactive sessions), leaving only an explanatory code
comment rather than the plan's specified remediation.

Investigated both remediation options the audit offered:
(a) add a Claude-backend-guarded enforcement call here, or
(b) document the gap explicitly in the plan's Out-of-Scope section.

Chose (b), after confirming (a) is far larger and more redundant than it
first appears: _run_interactive_session has NO force_inactive_agent_teams
parameter at all in this plan's scope — neither branch ever threads it,
so a guarded call alone would still be a no-op; the real fix requires
threading the flag through _run_interactive_session's signature and both
branches, plus _launch_cook_session and its callers. develop's tip
already ships exactly this (0b1b412 / #4688, 'Interactive Policy
Checkpoint Immunity'), independently, via a differently-shaped
architecture (a single inline call inside assert_interactive_ordering,
gated on spec.force_inactive_agent_teams, rather than this plan's Step
2.4 single-enforcement-point consolidation into
backend.validate_interactive_invocation). Hand-duplicating that fix here
would produce two competing, partial implementations of the same
corridor that must be reconciled against develop anyway.

Updated the plan's own §6 Out-of-Scope section (main repo, not this
worktree — plans live outside the implementation worktree) with the
full investigation trail, and tightened the code comment here to
reference it. The Codex-incompatibility rationale for not calling the
generic validate_interactive_invocation Protocol method is unchanged and
still applies regardless of which remediation path closes this gap.

Refs #4684, #4688.
Trecek added a commit that referenced this pull request Aug 19, 2026
…efactors

Post-rebase reconciliation of semantic conflicts that survived the textual
merge (no conflict markers, but wrong against the new integration base):

- _type_launch.py: drop the duplicate "force_inactive_agent_teams" key from
  the launch-record command payload. Both sides added it independently and
  git merged them cleanly into one dict; ruff F601 caught the duplicate.
  develop's placement (after process_idle_timeout_ms) is kept.

- test_subpackage_structure.py: bump the hardcoded _type_constants symbol
  count 168 -> 169. develop added CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT_ENV_VAR
  (+1) while this branch added EXPLORATION_FAILURE_CODES and
  KNOWN_UNAFFECTED_SKILL_IDS (+2); neither side's count absorbed the other's.

- test_cook_settings_local_agent_teams.py / test_cook_real_root_smoke.py:
  rebase the expected outcome onto #4688's remediation strategy. develop
  now neutralizes a conflicting env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS
  settings entry before the inactivity assertion rather than refusing the
  launch, so cook no longer raises for that case. Both tests keep their
  purpose — the real, unstubbed policy running against a real settings
  file — and now assert the file is actually rewritten and the launch env
  is agent-teams-free, plus that the opt-in leaves the repository untouched
  when off.

- tests/contracts/AGENTS.md: follow develop's rename of
  force_claude_agent_teams_inactive -> force_inactive_agent_teams.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Trecek added a commit that referenced this pull request Aug 19, 2026
…tive launch branch

The 24-line comment claimed #4688 (commit 0b1b412) "already shipped on
develop... threading the flag through both branches on its own
single-inline-call architecture." Verified via git show that commit never
touched this file; its actual change removed an inline env-policy call
elsewhere, converging on the single-enforcement-point design this branch
already uses — the opposite of what the comment described. Replaced with a
concise, durably-true summary and dropped the plan-section/date citations.
Trecek added a commit that referenced this pull request Aug 20, 2026
…cement gap as explicit out-of-scope

Independent audit confirmed the exact trap the plan's own Step 2.4 warned
against: assert_interactive_ordering's inline policy call was removed
without an equivalent call reaching the raw/non-managed
_run_interactive_session branch (the sole launch path for ad-hoc
fleet/campaign interactive sessions), leaving only an explanatory code
comment rather than the plan's specified remediation.

Investigated both remediation options the audit offered:
(a) add a Claude-backend-guarded enforcement call here, or
(b) document the gap explicitly in the plan's Out-of-Scope section.

Chose (b), after confirming (a) is far larger and more redundant than it
first appears: _run_interactive_session has NO force_inactive_agent_teams
parameter at all in this plan's scope — neither branch ever threads it,
so a guarded call alone would still be a no-op; the real fix requires
threading the flag through _run_interactive_session's signature and both
branches, plus _launch_cook_session and its callers. develop's tip
already ships exactly this (0b1b412 / #4688, 'Interactive Policy
Checkpoint Immunity'), independently, via a differently-shaped
architecture (a single inline call inside assert_interactive_ordering,
gated on spec.force_inactive_agent_teams, rather than this plan's Step
2.4 single-enforcement-point consolidation into
backend.validate_interactive_invocation). Hand-duplicating that fix here
would produce two competing, partial implementations of the same
corridor that must be reconciled against develop anyway.

Updated the plan's own §6 Out-of-Scope section (main repo, not this
worktree — plans live outside the implementation worktree) with the
full investigation trail, and tightened the code comment here to
reference it. The Codex-incompatibility rationale for not calling the
generic validate_interactive_invocation Protocol method is unchanged and
still applies regardless of which remediation path closes this gap.

Refs #4684, #4688.
Trecek added a commit that referenced this pull request Aug 20, 2026
…efactors

Post-rebase reconciliation of semantic conflicts that survived the textual
merge (no conflict markers, but wrong against the new integration base):

- _type_launch.py: drop the duplicate "force_inactive_agent_teams" key from
  the launch-record command payload. Both sides added it independently and
  git merged them cleanly into one dict; ruff F601 caught the duplicate.
  develop's placement (after process_idle_timeout_ms) is kept.

- test_subpackage_structure.py: bump the hardcoded _type_constants symbol
  count 168 -> 169. develop added CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT_ENV_VAR
  (+1) while this branch added EXPLORATION_FAILURE_CODES and
  KNOWN_UNAFFECTED_SKILL_IDS (+2); neither side's count absorbed the other's.

- test_cook_settings_local_agent_teams.py / test_cook_real_root_smoke.py:
  rebase the expected outcome onto #4688's remediation strategy. develop
  now neutralizes a conflicting env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS
  settings entry before the inactivity assertion rather than refusing the
  launch, so cook no longer raises for that case. Both tests keep their
  purpose — the real, unstubbed policy running against a real settings
  file — and now assert the file is actually rewritten and the launch env
  is agent-teams-free, plus that the opt-in leaves the repository untouched
  when off.

- tests/contracts/AGENTS.md: follow develop's rename of
  force_claude_agent_teams_inactive -> force_inactive_agent_teams.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Trecek added a commit that referenced this pull request Aug 20, 2026
…tive launch branch

The 24-line comment claimed #4688 (commit 0b1b412) "already shipped on
develop... threading the flag through both branches on its own
single-inline-call architecture." Verified via git show that commit never
touched this file; its actual change removed an inline env-policy call
elsewhere, converging on the single-enforcement-point design this branch
already uses — the opposite of what the comment described. Replaced with a
concise, durably-true summary and dropped the plan-section/date citations.
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.

1 participant