Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions docs/workflow-failure-analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# SDK Consistency Review Agent - Workflow Failure Analysis

**Workflow Run:** [#21486968231](https://github.com/github/copilot-sdk/actions/runs/21486968231)
**Date:** January 29, 2026
**Status:** Failed (Race Condition)

## Executive Summary

The SDK Consistency Review Agent workflow failed due to a race condition in the gh-aw infrastructure where the PR branch was deleted (after merge) before the workflow could checkout the code. **However, this failure prevented the workflow from catching a real documentation inconsistency** - the .NET SDK was missing important Azure provider configuration documentation that was added to the other three SDKs.

## What Happened

### Timeline
1. **16:49:13** - PR #260 opened (provider-info branch): "Update docs to reflect you need version for Azure Foundry"
2. **16:50:46** - SDK Consistency Review workflow triggered by PR event
3. **16:50:53** - **PR #260 merged** (branch deleted)
4. **16:51:08** - Workflow step "Checkout PR branch" failed: `fatal: couldn't find remote ref provider-info`

### The Failure
```
fatal: couldn't find remote ref provider-info
##[error]Failed to checkout PR branch: The process '/usr/bin/git' failed with exit code 128
```

The workflow's checkout step tried to `git fetch origin provider-info` but the branch no longer existed because it was deleted as part of the merge process.

## Root Cause Analysis

### Infrastructure Issue
The gh-aw workflow engine's `checkout_pr_branch.cjs` script uses the branch name for checkout instead of the commit SHA. This creates a race condition:

- **Branch names** are ephemeral - they get deleted after PR merge
- **Commit SHAs** persist indefinitely - they remain accessible even after branch deletion

**Solution:** The gh-aw infrastructure should use `github.event.pull_request.head.sha` for checkout, which is always available and never gets deleted. This has been logged as a separate work item for the gh-aw team.

### What the Workflow Missed

PR #260 modified README documentation for three SDKs:
- ✅ **Node.js SDK** - Added Azure provider configuration notes
- ✅ **Python SDK** - Added Azure provider configuration notes
- ✅ **Go SDK** - Added Azure provider configuration notes
- ❌ **.NET SDK** - **Not updated** (inconsistency)

The changes included critical information:
- Azure OpenAI endpoints must use `type: "azure"`, not `type: "openai"`
- BaseURL should be just the host, not include `/openai/v1` path
- Model parameter is required when using custom providers
- Examples for Ollama, custom OpenAI-compatible APIs, and Azure OpenAI

This is **exactly the kind of cross-SDK inconsistency** the workflow is designed to catch.

## Resolution

### Immediate Fix
✅ **Added comprehensive Azure provider documentation to .NET SDK README** (commit: 9778e86)

The .NET SDK now has:
- Complete ProviderConfig field documentation
- Ollama example with local provider usage
- Custom OpenAI-compatible API example
- Azure OpenAI example with important notes
- All the same warnings and best practices as other SDKs

### Future Prevention
📋 **Proposed work item:** Fix gh-aw race condition by using commit SHA instead of branch name for checkout

This will prevent future workflow failures when PRs are merged quickly, ensuring reviews can complete even for fast-moving PRs.

## Key Takeaways

1. **The workflow failure was a symptom, not the disease** - The real issue was the documentation gap across SDKs
2. **Fast merges expose infrastructure limitations** - The gh-aw checkout logic needs to be more resilient
3. **Consistency reviews are valuable** - Even though the workflow failed, manual analysis found the exact kind of issue it was designed to catch

## Recommendations

1. **For gh-aw team:** Implement SHA-based checkout with fallback to branch name
2. **For SDK maintainers:** When updating documentation across SDKs, always check all four implementations (Node.js, Python, Go, .NET)
3. **For this repository:** Consider adding a docs linting check that validates critical sections exist in all SDK READMEs
59 changes: 56 additions & 3 deletions dotnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,20 +430,73 @@ await session.SendAsync(new MessageOptions

### Bring Your Own Key (BYOK)

Use a custom API provider:
The SDK supports custom OpenAI-compatible API providers (BYOK - Bring Your Own Key), including local providers like Ollama. When using a custom provider, you must specify the `Model` explicitly.

**ProviderConfig fields:**

- `Type` (string): Provider type - `"openai"`, `"azure"`, or `"anthropic"` (default: `"openai"`)
- `BaseUrl` (string): API endpoint URL (required)
- `ApiKey` (string): API key (optional for local providers like Ollama)
- `BearerToken` (string): Bearer token for authentication (takes precedence over `ApiKey`)
- `WireApi` (string): API format for OpenAI/Azure - `"completions"` or `"responses"` (default: `"completions"`)
- `Azure` (AzureProviderOptions): Azure-specific options with `ApiVersion` (default: `"2024-10-21"`)

**Example with Ollama:**

```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "deepseek-coder-v2:16b", // Required when using custom provider
Provider = new ProviderConfig
{
Type = "openai",
BaseUrl = "http://localhost:11434/v1", // Ollama endpoint
// ApiKey not required for Ollama
}
});

await session.SendAsync(new MessageOptions { Prompt = "Hello!" });
```

**Example with custom OpenAI-compatible API:**

```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4",
Provider = new ProviderConfig
{
Type = "openai",
BaseUrl = "https://api.openai.com/v1",
ApiKey = "your-api-key"
BaseUrl = "https://my-api.example.com/v1",
ApiKey = Environment.GetEnvironmentVariable("MY_API_KEY")
}
});
```

**Example with Azure OpenAI:**

```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4",
Provider = new ProviderConfig
{
Type = "azure", // Must be "azure" for Azure endpoints, NOT "openai"
BaseUrl = "https://my-resource.openai.azure.com", // Just the host, no path
ApiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"),
Azure = new AzureProviderOptions
{
ApiVersion = "2024-10-21"
}
}
});
```

> **Important notes:**
> - When using a custom provider, the `Model` parameter is **required**. The SDK will throw an error if no model is specified.
> - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `Type = "azure"`, not `Type = "openai"`.
> - The `BaseUrl` should be just the host (e.g., `https://my-resource.openai.azure.com`). Do **not** include `/openai/v1` in the URL - the SDK handles path construction automatically.

## Error Handling

```csharp
Expand Down
Loading