Avoid reinjecting permissions after command approvals (#36800)

## What changed

- Track approved command prefixes separately from the stable permissions
  instructions in world-state snapshots.
- Emit only newly approved prefixes after an exec-policy amendment instead of
  appending the full permissions block again.
- Preserve prefix updates when full permissions instructions are disabled and
  remain compatible with legacy world-state snapshots.

## Testing

- Cover incremental prefix additions, removals, legacy snapshots, history
  rollback, and approval flows with permissions instructions enabled or disabled.

GitOrigin-RevId: e6f68c6a91be82750e70e28456f2b7c58607fbd8
This commit is contained in:
felixxia-oai
2026-08-03 21:37:17 +00:00
committed by copyberry
parent 51d4aa946c
commit 1bbfb5cfad
15 changed files with 539 additions and 59 deletions
+10 -21
View File
@@ -381,29 +381,18 @@ pub async fn exec_approval(
if let ReviewDecision::ApprovedExecpolicyAmendment {
proposed_execpolicy_amendment,
} = &decision
{
match sess
&& let Err(err) = sess
.persist_execpolicy_amendment(proposed_execpolicy_amendment)
.await
{
Ok(()) => {
sess.record_execpolicy_amendment_message(
&event_turn_id,
proposed_execpolicy_amendment,
)
.await;
}
Err(err) => {
let message = format!("Failed to apply execpolicy amendment: {err}");
tracing::warn!("{message}");
let warning = EventMsg::Warning(WarningEvent { message });
sess.send_event_raw(Event {
id: event_turn_id.clone(),
msg: warning,
})
.await;
}
}
{
let message = format!("Failed to apply execpolicy amendment: {err}");
tracing::warn!("{message}");
let warning = EventMsg::Warning(WarningEvent { message });
sess.send_event_raw(Event {
id: event_turn_id.clone(),
msg: warning,
})
.await;
}
match decision {
ReviewDecision::Abort => {
-18
View File
@@ -22,7 +22,6 @@ use crate::compact;
use crate::compact::CompactedHistoryMetadata;
use crate::config::ManagedFeatures;
use crate::config::resolve_tool_suggest_config_from_layer_stack;
use crate::context::ApprovedCommandPrefixSaved;
use crate::context::ContextualUserFragment;
use crate::context::ModelSwitchInstructions;
use crate::context::NetworkRuleSaved;
@@ -104,7 +103,6 @@ use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::models::BaseInstructions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::SandboxEnforcement;
use codex_protocol::models::format_allow_prefixes;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::permissions::FileSystemSandboxPolicy;
@@ -2164,22 +2162,6 @@ impl Session {
))
}
pub(crate) async fn record_execpolicy_amendment_message(
&self,
sub_id: &str,
amendment: &ExecPolicyAmendment,
) {
let Some(prefixes) = format_allow_prefixes(vec![amendment.command.clone()]) else {
warn!("execpolicy amendment for {sub_id} had no command prefix");
return;
};
let message: ResponseItem =
ContextualUserFragment::into(ApprovedCommandPrefixSaved::new(prefixes));
let turn_context = self.turn_context_for_sub_id(sub_id).await;
self.inject_no_new_turn(vec![message], turn_context.as_deref())
.await;
}
pub(crate) async fn persist_network_policy_amendment(
&self,
amendment: &NetworkPolicyAmendment,
+4
View File
@@ -7,6 +7,7 @@ use crate::context::ApprovalPromptContext;
use crate::context::world_state::AgentsMdState;
use crate::context::world_state::AppsInstructionsState;
use crate::context::world_state::CollaborationModeState;
use crate::context::world_state::CompactPermissionsState;
use crate::context::world_state::ContextWindowGuidanceState;
use crate::context::world_state::EnvironmentsInstructionsState;
use crate::context::world_state::EnvironmentsState;
@@ -135,6 +136,9 @@ impl Session {
.features
.enabled(Feature::RequestPermissionsTool),
));
} else {
let exec_policy = self.services.exec_policy.current();
world_state.add_section(CompactPermissionsState::new(exec_policy.as_ref()));
}
if turn_context.config.include_collaboration_mode_instructions {
world_state.add_section(CollaborationModeState::from_collaboration_mode(