Skip to content

Fix data race on shared *Agent.historyProvider by not mutating the interface field mid-run - #722

Open
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanaveFork:fix-history-provider-data-race
Open

Fix data race on shared *Agent.historyProvider by not mutating the interface field mid-run#722
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanaveFork:fix-history-provider-data-race

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

handleHistoryProviderConflict cleared the configured history provider by writing a.historyProvider = nil in the middle of a run, while historyProviderForSession reads that same interface field unsynchronized on every run. The Agent struct has no mutex and historyProvider is a plain interface value, so a shared *Agent run concurrently (each call with its own service-managed session) races the write against the reads — a two-word interface value written and read without synchronization, which go test -race flags and which can produce a torn read.

A shared *Agent is the intended concurrency unit here (sessions are per-conversation and the repo ships a concurrent agent-workflow example), so this is a real data race.

Fix

  • Keep historyProvider immutable after construction.
  • Add an historyCleared atomic.Bool to Agent.
  • handleHistoryProviderConflict now calls a.historyCleared.Store(true) instead of niling the field.
  • historyProviderForSession returns nil early when a.historyCleared.Load() is set.

This preserves the existing .NET/Python clear-on-conflict semantics: once a provider promotes a session to service-managed and the conflict policy allows clearing (throw disabled, keep disabled), the configured history provider is cleared globally for subsequent runs. The behavior is identical; only the mechanism changes from mutating a shared interface field to setting an atomic flag, which removes the race.

Testing

  • go build ./..., go vet ./agent/..., go test -race ./agent/... all pass.
  • Added TestAgent_Run_HistoryProvider_ConcurrentConflictClearIsRaceFree in agent_test.go: it configures an agent with an explicit HistoryProvider, AllowHistoryProviderConflict=true, KeepHistoryProviderOnConflict=false, and a provider whose Run promotes the session to service-managed mid-run, then launches many goroutines calling Run concurrently and drains each stream. Under -race it reports a data race against the pre-fix code and passes after the fix. The existing clear-on-conflict test remains green.

Copilot AI review requested due to automatic review settings July 24, 2026 03:19
@PratikDhanave
PratikDhanave requested a review from a team as a code owner July 24, 2026 03:19

Copilot AI 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.

Pull request overview

This PR removes a data race in Agent when a shared *Agent is run concurrently by preventing mid-run mutation of the historyProvider interface field. Instead, it preserves the existing “clear-on-conflict” behavior by recording the global clear using an atomic.Bool flag that is safe to read/write concurrently.

Changes:

  • Added historyCleared atomic.Bool to Agent and stopped mutating Agent.historyProvider during runs.
  • Updated historyProviderForSession to disable history provider usage once historyCleared is set.
  • Added a concurrent -race regression test covering the clear-on-conflict path.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
agent/agent.go Replaces racy historyProvider field mutation with an atomic “cleared” flag and gates provider usage on that flag.
agent/agent_test.go Adds a concurrency test intended to exercise the previous race pattern under go test -race.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 24, 2026
@github-actions

This comment has been minimized.

handleHistoryProviderConflict cleared the configured provider by writing
a.historyProvider = nil during a run, while historyProviderForSession read
the same interface field unsynchronized on every run. A shared *Agent run
concurrently (each with its own service-managed session) therefore raced on
the two-word interface value, which go test -race flags with a possible torn
read.

Keep historyProvider immutable and track the global clear with an
atomic.Bool (historyCleared). handleHistoryProviderConflict now stores true
instead of niling the field, and historyProviderForSession returns nil early
when the flag is set. This preserves the .NET clear-on-conflict semantics
(the provider is cleared globally after a conflict) while removing the race.
@PratikDhanave
PratikDhanave force-pushed the fix-history-provider-data-race branch from 56c6490 to 2cb8f9e Compare July 24, 2026 09:31
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review: ✅ No cross-repo consistency issues

This PR fixes a data race in the Go *Agent by replacing a mid-run mutation of the historyProvider interface field with an atomic.Bool (historyCleared) flag.

Scope assessment: Internal-only change.

  • No exported functions, types, methods, fields, or options were added, removed, or modified.
  • The observable behavior — clearing the configured history provider once a session is promoted to service-managed, with AllowHistoryProviderConflict=true and KeepHistoryProviderOnConflict=false — is preserved identically.
  • The PR description confirms alignment with .NET/Python clear-on-conflict semantics.

Labels: public-api-change is not warranted (no exported API surface changed). parity-approved is already present and correct — this fix keeps Go semantically aligned with upstream implementations.

No parity issues found.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 18.7 AIC · ⌖ 5.55 AIC · ⊞ 5.9K ·

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

Labels

parity-approved Go API consistency review found no parity issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants