Skip to content

fix(#2294): make EnsureProvider idempotent via delete-and-recreate - #2296

Closed
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/2294-idempotent-ensure-provider
Closed

fix(#2294): make EnsureProvider idempotent via delete-and-recreate#2296
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/2294-idempotent-ensure-provider

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

EnsureProvider called openshell provider create and treated any error as a hard failure. When a provider already existed from a prior run, the AlreadyExists error blocked subsequent runs, requiring manual cleanup between iterations.

Now when the create command fails with AlreadyExists, the function deletes the existing provider and recreates it with current credentials. This makes the function truly idempotent (matching the "Ensure" naming convention used by EnsureGateway and the Provider.Provision() interface contract) while also ensuring credentials are never stale across runs.

Also extracted a redactSecrets helper to reduce duplication in error formatting paths.


Closes #2294

Post-script verification

  • Branch is not main/master (agent/2294-idempotent-ensure-provider)
  • Secret scan passed (gitleaks — 9ca6edc3770e16b0f37a6e79046cfba7d01bffc9..HEAD)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

EnsureProvider called `openshell provider create` and treated any
error as a hard failure. When a provider already existed from a
prior run, the AlreadyExists error blocked subsequent runs,
requiring manual cleanup between iterations.

Now when the create command fails with AlreadyExists, the function
deletes the existing provider and recreates it with current
credentials. This makes the function truly idempotent (matching
the "Ensure" naming convention used by EnsureGateway and the
Provider.Provision() interface contract) while also ensuring
credentials are never stale across runs.

Also extracted a redactSecrets helper to reduce duplication in
error formatting paths.

Closes #2294
@github-actions

Copy link
Copy Markdown

E2E tests did not run

E2E tests run automatically for org/repo members and collaborators on pull requests.

For other contributors, a maintainer must add the ok-to-test label after the latest push.

See E2E testing guide for details.

@github-actions

Copy link
Copy Markdown

Site preview

Preview: https://af470785-site.fullsend-ai.workers.dev

Commit: fac85e457735eec3a3fe4b23edc38b1e6a2639e1

@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/sandbox/sandbox.go 85.71% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:43 AM UTC · Completed 10:55 AM UTC
Commit: fac85e4 · View workflow run →

@fullsend-ai-review

Copy link
Copy Markdown

Review

Findings

Medium

  • [architectural-coherence] internal/sandbox/sandbox.go:103 — The function docstring says "creates or updates a provider" but the implementation is delete-and-recreate, which is neither create-if-absent nor update-in-place. The godoc should be updated to accurately reflect the actual semantics (e.g., "creates a provider, deleting and recreating it if it already exists to ensure credentials are current").

Low

  • [missing-test] internal/sandbox/sandbox_test.go — No test covers the path where delete succeeds but the retry create fails. This is the most dangerous error path (provider deleted but not recreated), and a test verifying the error message contains "failed after delete" would be valuable.

  • [edge-case] internal/sandbox/sandbox.go:121 — The AlreadyExists detection uses strings.Contains against combined stdout+stderr. If openshell changes its error format, the detection silently breaks and falls through to the generic error path. The fallback is safe (returns an error), but loses idempotency. Consider whether openshell provides a structured exit code that could be used instead.

  • [intent-scope-alignment] internal/sandbox/sandbox.go:117 — Issue EnsureProvider should be idempotent — fails with AlreadyExists on repeated runs #2294 proposed three approaches. Option 2 (delete-and-recreate, chosen here) also refreshes credentials on every conflict, which is a useful property but goes slightly beyond pure idempotency. This is a reasonable design choice given the issue's analysis of credential staleness.

  • [architectural-coherence] internal/sandbox/sandbox.go:117 — Delete-and-recreate introduces a timing window where provider deletion could affect concurrent sandbox operations if the gateway is shared. In practice this is unlikely given the per-run lifecycle, but worth documenting if concurrent usage becomes a concern.

Info

  • [race-condition] internal/sandbox/sandbox.go:123 — Theoretical TOCTOU window between delete and retry create. Non-actionable given single-process usage; same pattern exists in CreateWithRetry.

  • [architectural-trajectory] internal/sandbox/sandbox.go:117 — Delete-and-recreate is a different reconciliation model from WIF provider create-or-update elsewhere. Not necessarily wrong, but creates divergent patterns worth being aware of.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

// If the provider already exists, delete it and recreate with
// current credentials. This keeps EnsureProvider idempotent and
// ensures credentials are never stale across runs.
if strings.Contains(string(out), "AlreadyExists") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

AlreadyExists detection uses strings.Contains against combined stdout+stderr. If openshell changes its error format, the detection silently breaks and falls through to the generic error path — safe fallback, but loses idempotency.

@@ -115,16 +115,34 @@ func EnsureProvider(name, providerType string, credentials, config map[string]st
cmd.Env = append(os.Environ(), extraEnv...)
out, err := cmd.CombinedOutput()
if err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] intent-scope-alignment

Issue #2294 proposed three approaches. Option 2 (delete-and-recreate) also refreshes credentials on every conflict, which goes slightly beyond pure idempotency but is a reasonable design choice.

@@ -115,16 +115,34 @@ func EnsureProvider(name, providerType string, credentials, config map[string]st
cmd.Env = append(os.Environ(), extraEnv...)
out, err := cmd.CombinedOutput()
if err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] architectural-coherence

Delete-and-recreate introduces a timing window where provider deletion could affect concurrent sandbox operations if the gateway is shared.

// ensures credentials are never stale across runs.
if strings.Contains(string(out), "AlreadyExists") {
delCmd := exec.Command("openshell", "provider", "delete", name)
if delOut, delErr := delCmd.CombinedOutput(); delErr != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[info] race-condition

Theoretical TOCTOU window between delete and retry create. Non-actionable given single-process usage; same pattern exists in CreateWithRetry.

@@ -115,16 +115,34 @@ func EnsureProvider(name, providerType string, credentials, config map[string]st
cmd.Env = append(os.Environ(), extraEnv...)
out, err := cmd.CombinedOutput()
if err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[info] architectural-trajectory

Delete-and-recreate is a different reconciliation model from WIF provider create-or-update elsewhere. Creates divergent patterns worth noting.

@rh-hemartin

Copy link
Copy Markdown
Member

Closed in favor of #2323

@rh-hemartin
rh-hemartin deleted the agent/2294-idempotent-ensure-provider branch June 16, 2026 11:42
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jun 16, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 11:46 AM UTC · Completed 11:58 AM UTC
Commit: fac85e4 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #2296 — Agent chose reactive fix, human found preventive design

Outcome: PR #2296 (agent-authored) was closed without merging, superseded by human-authored PR #2323.

Timeline:

  1. Issue #2294 filed describing EnsureProvider failing with AlreadyExists on repeated runs. Listed three reactive approaches (succeed silently, delete-and-recreate, hybrid).
  2. Triage agent recommended catching AlreadyExists or delete-and-recreate — aligned with issue's options.
  3. Code agent chose delete-and-recreate, produced PR #2296 in ~10 minutes.
  4. Review agent flagged concurrency/TOCTOU risks as low/info severity, gave COMMENTED verdict (no approval), PR got requires-manual-review label.
  5. Human (rh-hemartin) investigated deeper, realized the problem could be prevented by design using sandbox-scoped unique provider names — no collision possible, plus proper cleanup on teardown.
  6. PR fix(#2294): make EnsureProvider idempotent via delete-and-recreate #2296 closed in favor of PR #2323.

What worked well:

  • The pipeline correctly flagged the PR for manual review (COMMENTED, not APPROVED)
  • Review agent identified the real architectural concerns (concurrency, TOCTOU, divergent pattern) — just rated them too low
  • End-to-end latency from issue to PR was fast (~20 minutes)

Filtered proposals:

  • Review agent strategic fitness dimension: Already covered by open issue #849. The review agent's low/info ratings on the concurrency findings would likely be escalated with a strategic fitness check.
  • Code agent approach selection: Partially covered by #2185 and #1891, though from different angles.

Proposals filed

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

Labels

requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EnsureProvider should be idempotent — fails with AlreadyExists on repeated runs

1 participant