Skip to content

[codex-analytics] emit terminal review events#18748

Merged
rhan-oai merged 1 commit into
mainfrom
pr18748
May 11, 2026
Merged

[codex-analytics] emit terminal review events#18748
rhan-oai merged 1 commit into
mainfrom
pr18748

Conversation

@rhan-oai

@rhan-oai rhan-oai commented Apr 20, 2026

Copy link
Copy Markdown
Collaborator

Why

Review telemetry should describe reviews as first-class events, not only as counters denormalized onto terminal tool-item events. That lets us analyze guardian and user reviews consistently across command execution, file changes, permissions, and network access, while still preserving the terminal item summaries that existing tool analytics need.

To make those review events accurate, analytics also needs the observed completion time for each review and enough command metadata to distinguish shell from unified_exec reviews.

What changed

  • emit generic codex_review_event rows for completed user and guardian reviews, with review subjects, reviewer, trigger, terminal status, resolution, and observed duration
  • reduce approval request / response / abort facts into review events for command execution, file change, and permissions flows
  • keep denormalized review counts, final approval outcome, and permission-request flags on terminal tool-item events for item-associated reviews
  • plumb review completion timing so user-review responses and aborts use app-server-observed completion times, while guardian analytics reuse the same terminal timestamps emitted on guardian assessment events
  • carry command approval source through the protocol and app-server layers so review analytics can distinguish shell from unified_exec
  • add analytics coverage for user-review emission, guardian-review emission, permission reviews that should not denormalize onto tool items, item-summary isolation across threads, and the serialized review-event shape

Verification

  • cargo test -p codex-analytics

Stack created with Sapling. Best reviewed with ReviewStack.

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

Copy link
Copy Markdown
Contributor

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: 0c8f41eab9

ℹ️ 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 codex-rs/analytics/src/reducer.rs Outdated
Comment on lines +801 to +804
let status = match response.scope {
PermissionGrantScope::Turn => ToolReviewStatus::Approved,
PermissionGrantScope::Session => ToolReviewStatus::ApprovedForSession,
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Classify empty permission grants as denied reviews

PermissionsRequestApproval maps status from scope only, so Turn is always emitted as approved. But UI denial paths return an empty permissions profile with scope=Turn (see approval_overlay::handle_permissions_decision). That causes denied/timed-out permission reviews to be reported as approvals and denormalized into incorrect final_approval_outcome values on tool item events.

Useful? React with 👍 / 👎.

Comment thread codex-rs/analytics/src/reducer.rs Outdated
Comment on lines +1052 to +1057
let started = self.guardian_review_starts.remove(&notification.review_id);
let created_at = started
.as_ref()
.map(|start| start.created_at)
.unwrap_or(completed_at);
let Some(status) = guardian_review_status(notification.review.status) else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Deduplicate guardian review completion notifications

This handler emits a review even when guardian_review_starts.remove(review_id) returns None. Because notifications are tracked per connection, multi-client threads ingest the same completion once per connection, creating duplicate codex_tool_call_review_event records and inflating per-item review counters.

Useful? React with 👍 / 👎.

@rhan-oai

rhan-oai commented May 8, 2026

Copy link
Copy Markdown
Collaborator Author

@codex

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

Copy link
Copy Markdown
Contributor

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: e4cc9b8873

ℹ️ 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 codex-rs/analytics/src/reducer.rs Outdated
turn_id: params.turn_id,
item_id: Some(params.item_id),
review_id: user_review_id(&request_id),
subject_kind: ReviewSubjectKind::CommandExecution,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Classify network approval reviews as network access

When params.network_approval_context is present, this request is the app-server's Network approval presentation, not a command execution review. Recording it unconditionally as CommandExecution with subject_name of shell/unified_exec makes user network-access review telemetry inconsistent with guardian network reviews and the commit's stated first-class network access subjects.

Useful? React with 👍 / 👎.

..
} => {}
completed_at_ms,
response,

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.

This path consumes ServerResponse facts that app-server records from the raw JSON-RPC result before the approval-specific handlers apply their effective fallback/validation. If the client returns a malformed command/file approval response, the handler later falls back to Decline, but analytics emits no review event because response_from_result never produced a typed response. For permissions, analytics can mark a raw non-empty grant as approved before the handler intersects/validates it and potentially grants nothing. Consider tracking the effective approval decision after the approval handler computes it, so analytics matches the behavior actually applied to the turn.

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.

^ codex-generated - if it complicates the implementation too much I actually don't think it's worth doing, fwiw

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.

feels like we should be tracking malformed client responses separately anyways

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

fixed up permissions issue, agree on pushing off malformed response tracking

///
/// Uses `#[serde(default)]` for backwards compatibility with older senders.
#[serde(default = "default_guardian_command_source")]
pub source: GuardianCommandSource,

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.

let's remove this, I don't want to add this implementation detail to the public app-server API.

can we populate codex_review_event.subject_name as just command_execution instead?

@rhan-oai

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector

Copy link
Copy Markdown
Contributor

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ 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 codex-rs/protocol/src/approvals.rs Outdated
///
/// Uses `#[serde(default)]` for backwards compatibility with older senders.
#[serde(default = "default_guardian_command_source")]
pub source: GuardianCommandSource,

@owenlin0 owenlin0 May 11, 2026

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.

Can we remove this too? We also polluted the protocol a bit

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

whoops yes my bad

Comment thread codex-rs/core/src/guardian/review.rs Outdated
let (assessment, count_denial_for_circuit_breaker, completed_at_ms) = match outcome {
GuardianReviewOutcome::Completed(assessment) => {
let approved = matches!(assessment.outcome, GuardianAssessmentOutcome::Allow);
let completed_at_ms = now_unix_timestamp_ms();

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.

nit: we do this in each of the match outcome branch arms below. can we define it once right above the match outcome?

@owenlin0 owenlin0 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.

just some small comments at this point, but preapproving

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants