Skip to content

Apply Claude turn settings without replacing live sessions - #1236

Merged
ymichael merged 5 commits into
get-bb:mainfrom
sholub-dev:per-turn-permission-escalation
Aug 11, 2026
Merged

Apply Claude turn settings without replacing live sessions#1236
ymichael merged 5 commits into
get-bb:mainfrom
sholub-dev:per-turn-permission-escalation

Conversation

@sholub-dev

@sholub-dev sholub-dev commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #1235.

Claude background commands, workflows, and agents all live inside one Agent SDK session. bb previously treated execution-option drift as construction-time configuration, dispatched thread/resume, and replaced that session. Alternating user/system turns therefore interrupted background work, and several other setting changes had the same destructive behavior.

This PR makes reconfiguration provider-specific. Claude settings that can safely change in place now travel with each turn; only actual session-construction changes rebuild the provider session.

What changes

  • Permission escalation is live per input. A steer adopts its new escalation only after the input is accepted, and prompt/subagent/tool tracking preserves the originating turn's policy for background work.
  • Claude background subagents inherit approval provenance from their parent Agent/legacy Task tool chain, including SDK callbacks that omit agent metadata. Permission-request hooks provide an additional tool-level provenance boundary.
  • Approval-policy ownership is explicit on provider adapters. Claude filters allow/ask/deny using the originating work before forwarding an approval, so the runtime no longer reclassifies that request against newer thread settings. Codex, ACP, and Pi retain runtime-owned approval filtering.
  • Model changes use the SDK's live setModel control.
  • Memory, workflow, and reasoning changes use applyFlagSettings, including transitions to and from max reasoning.
  • Claude's native Agent/legacy Task and Workflow toggles are enforced live. They no longer depend on stale construction-time disallowedTools, and disabling them rejects new launches without replacing the session or killing existing work.
  • Unsupported Claude serviceTier: "fast" is normalized to "default" before validation, comparison, persistence, and provider command construction, so it cannot cause a rebuild or reach the Claude bridge.
  • Provider adapters now classify setting changes as unchanged, live, or session-scoped. Codex, ACP, and Pi retain their existing session-rebuild behavior.

The permission path remains conservative: readonly enforcement resolves escalation at tool-call time, unsandboxed retries are automatically denied on escalation-denied turns, and that denial runs before cached session grants so an approval from an earlier user turn cannot leak into later system work.

The following remain session-scoped and still rebuild when changed:

  • Claude Plan mode override
  • Permission mode, workspace scope, or approval reviewer
  • Mock Claude CLI traffic configuration

HOST_DAEMON_PROTOCOL_VERSION is bumped to 99 because the server no longer sends construction-time Claude disallowedTools values for workflow/subagent settings. Version 98 is already used by qualified upstream PR lookup on the updated base.

Validation

  • Standalone QA started a real Claude background process, then changed memory, workflow, and subagent settings while switching Haiku/low to Opus 1M/max. The same bridge PID, Claude CLI PID, provider thread ID, and background process survived; the next turn completed with the new model.
  • A second real Claude run used serviceTier: "fast" on both start and agent-origin steer. It retained one provider thread, preserved the background child's original ask policy after the steer changed the current thread to deny, surfaced the dangerous write as a pending approval, and completed it after allow_once.
  • Affected package typechecks pass for @bb/agent-runtime, @bb/server, @bb/host-daemon, and @bb/host-daemon-contract.
  • Focused execution-option, runtime lifecycle, interactive-request, and Claude bridge coverage: 115 tests passed.
  • Full server suite: 1,395 tests passed.
  • Host-daemon contract suite: 49 tests passed.
  • Full agent-runtime suite: 871/872 passed locally; the only failure is the unchanged timing-sensitive bounds provider stderr while data arrives without a newline lifecycle test, which is outside this diff.

@sholub-dev
sholub-dev force-pushed the per-turn-permission-escalation branch from 4aa085b to 1c28080 Compare August 9, 2026 18:39
@SawyerHood

Copy link
Copy Markdown
Collaborator

🚨 SLOP COP 🚨 · review

I am SlopCop. I am reviewing this pull request for security, code quality, architecture, performance, and end-to-end behavior.

return;
}

threadSession.permissionEscalation = params.permissionEscalation;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🚨 slopcop/review — A shared session value applies the newest turn policy to all background work.

Background tasks outlive their start turn. A later turn overwrites this shared value. An older system task can then read ask from a user turn and enter the approval path. The reverse change can also deny older user work. Please attach the policy to the work that created each tool request. Add overlap tests for both policy directions.

@sholub-dev sholub-dev Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The SDK gives canUseTool no link to the originating turn or task, so per-work policy isn't possible at this layer. The old behavior rebuilt the session on every escalation flip and killed background work — the bug this PR fixes. Both crossovers fail safe: ask only happens while a user turn is active, deny auto-denies.

return;
}

threadSession.permissionEscalation = params.permissionEscalation;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🚨 slopcop/review — A failed steer leaves the new permission policy active.

This assignment occurs before pushInput confirms the steer. The error path returns an error without restoring the old value. A rejected ask steer can therefore loosen the policy for existing deny work. Update the value after acceptance, or restore it in the error path. Add a failed-steer test.

@sholub-dev sholub-dev Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d4bf20e: escalation is set only after pushInput succeeds. Added a failed-steer test.

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🚨 SLOP COP 🚨 · review

ELI5: This change keeps old Claude jobs alive. But it gives all jobs one permission switch. It is like one light switch for every room.

I found two defects. I recommend fixes before merge.

  1. High — Background work crosses the per-turn permission boundary.

    Background tasks outlive their start turn. Each new turn overwrites one session-wide permissionEscalation value. An older system task can later read ask from a user turn and enter the approval path. The reverse change can deny older user work. Attach the policy to the work that created each tool request. Add overlap tests for both policy directions.

  2. Medium — A failed steer keeps its permission change.

    The bridge changes the value before pushInput succeeds. The error path does not restore the old value. A rejected ask steer can loosen existing deny work. Update the value after acceptance, or restore it after failure.

Architecture and performance

The adapter capability is an acceptable abstraction. The two Claude permission checks serve different SDK enforcement points. I found no useful refactor requirement. The change removes session replacement and adds constant-time state access.

Validation

I verified the exact head SHA and reviewed all 14 changed files. Three newer base commits do not touch these files. git diff --check passed. All reported GitHub CI checks passed. The change does not require a host-daemon protocol increase.

The pull request comes from an untrusted fork, and this backend change has no browser route. I did not start a server or execute the pull-request code locally.

This is a comment-only review. I did not approve the pull request or request changes.

@sholub-dev
sholub-dev force-pushed the per-turn-permission-escalation branch from 9269d62 to d4bf20e Compare August 10, 2026 16:18
@ymichael
ymichael force-pushed the per-turn-permission-escalation branch from d4bf20e to 3b81ef3 Compare August 10, 2026 23:48
@ymichael ymichael changed the title Apply permission escalation per turn in the Claude adapter Apply Claude turn settings without replacing live sessions Aug 10, 2026
@ymichael ymichael self-assigned this Aug 10, 2026
@ymichael
ymichael force-pushed the per-turn-permission-escalation branch from 3b81ef3 to f79aeb7 Compare August 11, 2026 02:30
@ymichael ymichael reopened this Aug 11, 2026
sholub-dev and others added 5 commits August 11, 2026 09:49
Escalation follows the turn initiator, but it was pinned into Claude session
construction, so every user/system turn alternation replaced the session and
killed the background tasks inside it. Turn commands now carry escalation, the
bridge adopts it in place, and the runtime records escalation-only changes
without a session-replacing reconfigure.
ymichael added a commit to sholub-dev/bb that referenced this pull request Aug 11, 2026
## Summary

- resolve PRs through the configured upstream remote and branch when a
BB managed local branch has a different name
- preserve distinct no-PR and unavailable GitHub CLI outcomes
- add real-Git regression coverage for fork remotes, renamed branches,
no-PR, authentication, and missing CLI cases
- bump the host-daemon protocol to 97 so enrolled hosts receive the
corrected lookup behavior

## Validation

- pnpm exec turbo run test --filter=@bb/host-workspace --force
- pnpm exec turbo run test --filter=@bb/host-daemon-contract --force
- pnpm exec turbo run typecheck --filter=@bb/host-workspace
- pnpm exec turbo run typecheck --filter=@bb/host-daemon-contract
--filter=@bb/host-daemon
- live read-only lookup against env_yikdjjfryt resolved get-bb#1236
@ymichael
ymichael force-pushed the per-turn-permission-escalation branch from 551feeb to daae198 Compare August 11, 2026 16:51
@ymichael
ymichael merged commit a8f7030 into get-bb:main Aug 11, 2026
9 checks passed
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.

Background tasks are killed when user and system turns alternate

3 participants