microsoft / microsoft/agent-framework
.NET: run_skill_script generates invalid schema for arguments
@rogerbarreto is already working on this.
Since Sep 7, 2026.
- Dominant language
- Python
- Stars
- 13.6k
- Forks
- 2.3k
- Avg merge
- 2d 45m
- Merged PRs (30d)
- 358
Description
Description
When an agent uses strict structured output, AgentSkillsProvider causes OpenAI/Azure OpenAI to reject the request before model execution.
AgentSkillsProvider always exposes run_skill_script for a file-based skill, even when the skill contains no scripts. In Microsoft.Agents.AI 1.20.0, the generated JSON Schema for its optional arguments parameter is:
{"default":null}
Because the property has no type, OpenAI/Azure OpenAI rejects the complete request when strict schema validation is enabled for the response:
HTTP 400 (invalid_request_error: invalid_function_parameters)
Parameter: tools[...].function.parameters
Invalid schema for function 'run_skill_script':
In context=('properties', 'arguments'), schema must have a 'type' key.
Without strict structured output, the same request is accepted by Azure OpenAI. Enabling strict output with:
chatOptions.AdditionalProperties["strict"] = true;
exposes the invalid tool schema. Although strict mode is configured for the response schema, the service validates every function-tool schema in the same request.
This also affects scriptless file skills because AgentSkillsProviderBuilder.UseFileSkill(s) requires a script runner, and the provider advertises run_skill_script regardless of whether any discovered skill contains scripts.
Minimal reproduction
The following is the only file required. Run it with the .NET 10 SDK:
// Program.cs
#:package Microsoft.Agents.AI@1.20.0
using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var rootDirectory = Path.Combine(Path.GetTempPath(), $"agent-framework-repro-{Guid.NewGuid():N}");
var skillDirectory = Path.Combine(rootDirectory, "hello");
Directory.CreateDirectory(skillDirectory);
File.WriteAllText(
Path.Combine(skillDirectory, "SKILL.md"),
"""
---
name: hello
description: Says hello.
---
Say hello to the user.
""");
try
{
using var skills = new AgentSkillsProviderBuilder()
.UseFileSkills([skillDirectory])
.UseFileScriptRunner((_, _, _, _, _) =>
throw new NotSupportedException("This skill has no scripts."))
.UseOptions(options =>
{
options.DisableLoadSkillApproval = true;
options.DisableReadSkillResourceApproval = true;
})
.Build();
using var client = new CaptureChatClient();
using var outputSchema = JsonDocument.Parse(
"""{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}""");
var chatOptions = new ChatOptions
{
ResponseFormat = ChatResponseFormat.ForJsonSchema(outputSchema.RootElement.Clone()),
AdditionalProperties = new AdditionalPropertiesDictionary
{
["strict"] = true,
},
};
var agent = client.AsAIAgent(
new ChatClientAgentOptions
{
AIContextProviders = [skills],
ChatOptions = chatOptions,
});
await agent.RunAsync([new ChatMessage(ChatRole.User, "Hello")]);
var function = (AIFunction)client.Options!.Tools!
.Single(tool => tool.Name == AgentSkillsProvider.RunSkillScriptToolName);
var argumentsSchema = function.JsonSchema
.GetProperty("properties")
.GetProperty("arguments");
Console.WriteLine(argumentsSchema);
Console.WriteLine($"Has type: {argumentsSchema.TryGetProperty("type", out _)}");
}
finally
{
Directory.Delete(rootDirectory, recursive: true);
}
sealed class CaptureChatClient : IChatClient
{
public ChatOptions? Options { get; private set; }
public Task<ChatResponse> GetResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
Options = options;
return Task.FromResult(
new ChatResponse(new ChatMessage(ChatRole.Assistant, "Hello!")));
}
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
Options = options;
await Task.Yield();
yield return new ChatResponseUpdate(ChatRole.Assistant, "Hello!");
}
public object? GetService(Type serviceType, object? serviceKey = null) =>
serviceType.IsInstanceOfType(this) ? this : null;
public void Dispose()
{
}
}
dotnet run Program.cs
Actual output:
{"default":null}
Has type: False
Expected behavior
Either:
- Do not expose
run_skill_scriptwhen discovered file skills contain no scripts, or provide an option to disable script execution; and/or - Generate a valid schema for
arguments, for example an object schema that OpenAI-compatible providers accept.
Package and runtime versions
Microsoft.Agents.AI: 1.20.0- .NET SDK: 10.0.303
- OS: Windows
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.