Azure Functions skill for Codex
Triggers, bindings and durable workflows done right. Codex identifies which Azure Functions programming model a project uses, moves it to the current one, and picks a hosting plan from real timeout and networking needs. Bindings connect through managed identity with data-plane roles, Durable Functions orchestrators stay deterministic, and everything runs under Core Tools and Azurite before a confirmed publish.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- azure-functions Installs to ~/.agents/skills/azure-functions/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install azure-functions The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project azure-functions Installs to .agents/skills/azure-functions/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/azure-functions.zipunzip -o azure-functions.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) azure-functions PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $azure-functions, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds and deploys Azure Functions on the Flex Consumption or Premium plans - Node.js v4 and Python v2 programming models, .NET isolated worker, triggers and input/output bindings, host.json and extension bundles, identity-based connections, Durable Functions orchestrations, timeouts and Core Tools local runs. Use when writing or deploying an Azure Function, adding a queue, Service Bus, blob, timer or Cosmos DB trigger, building a Durable Functions workflow, migrating off the .NET in-process model, or debugging functions that do not fire.
Use it when
- Writing, running or publishing an Azure Functions app
- Adding triggers or bindings for queues, blobs, timers, Service Bus or Cosmos DB
- Designing a Durable Functions workflow
- Functions do not show up or do not fire after deploy
Use something else when
- You host a full web app or container API: use azure-app-service
- You are defining the Azure resources themselves as code: use azure-bicep
How to check the result
Core Tools lists every function locally, the published app shows them in az functionapp function list, and Application Insights records successful test invocations.
The mistake it prevents
Calling Date.now() or doing I/O inside a Durable orchestrator, which breaks replay and produces nondeterminism errors.
Done well means: the app uses the current programming model for its language, runs on a plan chosen for its timeout and networking needs, connects to storage and queues with a managed identity instead of connection strings, has idempotent triggers, and runs locally with Core Tools before anything is published.
Inspect first
- Read
AGENTS.md,host.json,package.json/requirements.txt/.csproj, and any Bicep or Terraform for the app. Never commitlocal.settings.json; check.gitignore. - Identify the model from the code:
| You see | Model | Status |
|---|---|---|
app.http(...) from @azure/functions v4 | Node.js v4 | Current |
function.json next to each index.js | Node.js v3 | Migrate to v4 |
app = func.FunctionApp() with decorators | Python v2 | Current |
function.json + __init__.py | Python v1 | Migrate to v2 |
Microsoft.Azure.Functions.Worker package | .NET isolated | Current |
Microsoft.NET.Sdk.Functions, [FunctionName] | .NET in-process | Support ends 10 Nov 2026; migrate |
- Check the live app (needs network and
az login):
az functionapp list -g "$RG" -o table
az functionapp show -g "$RG" -n "$APP" --query "{plan:serverFarmId, kind:kind, state:state}"
az functionapp config appsettings list -g "$RG" -n "$APP" -o table # values are sensitive; do not paste them into chat
Choose the plan
| Need | Plan |
|---|---|
| New serverless app, VNet integration, scale to zero | Flex Consumption |
| Always-warm instances, deployment slots, no cold start | Premium (EP1+) |
| Already paying for an App Service plan with spare capacity | Dedicated |
| Legacy Linux Consumption | Do not start new apps here; it is being retired |
Timeouts: Flex Consumption and Premium default to 30 minutes and can be unbounded; classic Consumption caps at 10. Any HTTP-triggered call is still cut at about 230 seconds by the front-end load balancer, so long HTTP work must become async (queue + status endpoint, or Durable Functions). Flex Consumption does not support deployment slots.
Handlers
Node.js v4 (TypeScript):
import { app, HttpRequest, HttpResponseInit, InvocationContext, output } from "@azure/functions";
const queueOut = output.storageQueue({ queueName: "thumbnails", connection: "Storage" });
app.http("upload", {
methods: ["POST"],
authLevel: "function",
extraOutputs: [queueOut],
handler: async (req: HttpRequest, ctx: InvocationContext): Promise<HttpResponseInit> => {
const body = (await req.json()) as { blobName?: string };
if (!body.blobName) return { status: 400, jsonBody: { error: "blobName required" } };
ctx.extraOutputs.set(queueOut, { blobName: body.blobName });
return { status: 202 };
},
});
app.serviceBusQueue("orders", {
queueName: "orders",
connection: "ServiceBus",
handler: async (message: unknown, ctx: InvocationContext) => {
ctx.log("delivery", ctx.triggerMetadata?.deliveryCount);
await processOrder(message); // throw => message retried, then dead-lettered
},
});
Python v2:
import azure.functions as func
import logging
app = func.FunctionApp()
@app.timer_trigger(schedule="0 */15 * * * *", arg_name="timer", run_on_startup=False)
def sweep(timer: func.TimerRequest) -> None:
if timer.past_due:
logging.warning("timer is running late")
cleanup_expired()
@app.blob_trigger(arg_name="blob", path="uploads/{name}", connection="Storage")
def on_blob(blob: func.InputStream) -> None:
logging.info("processing %s (%d bytes)", blob.name, blob.length)
Timer schedules are six-field NCRONTAB (seconds first). 0 */15 * * * * is every 15
minutes; a five-field expression fails to index.
host.json essentials
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
},
"logging": { "logLevel": { "default": "Information", "Host.Results": "Error" } },
"extensions": {
"serviceBus": { "maxConcurrentCalls": 16 },
"queues": { "batchSize": 16, "maxDequeueCount": 5 }
}
}
.NET isolated apps reference extension NuGet packages instead of the bundle.
Identity-based connections
Replace connection strings with a managed identity. For a binding with
connection: "Storage", set app settings with the double-underscore suffixes:
Storage__accountName = mystorageacct
ServiceBus__fullyQualifiedNamespace = mybus.servicebus.windows.net
AzureWebJobsStorage__accountName = mystorageacct
Then grant the app’s identity data-plane roles: Storage Blob Data Owner,
Storage Queue Data Contributor (host storage needs both), Azure Service Bus Data Receiver.
Control-plane roles such as Contributor do not grant data access.
Durable Functions
Orchestrators must be deterministic: no Date.now(), random numbers, direct I/O or
await fetch inside them. Use the context’s current time, call activities for I/O, and use
durable timers for waits.
import * as df from "durable-functions";
df.app.orchestration("fulfil", function* (ctx) {
const order = ctx.df.getInput<{ id: string }>();
const reserved = yield ctx.df.callActivity("reserveStock", order);
if (!reserved) return "out-of-stock";
const approval = ctx.df.waitForExternalEvent("approved");
const timeout = ctx.df.createTimer(new Date(ctx.df.currentUtcDateTime.getTime() + 24 * 3600e3));
const winner = yield ctx.df.Task.any([approval, timeout]);
return winner === approval ? yield ctx.df.callActivity("ship", order) : "expired";
});
df.app.activity("reserveStock", { handler: async (order) => reserve(order) });
| Pattern | Use |
|---|---|
| Function chaining | Ordered steps with retries per step (callActivityWithRetry) |
| Fan-out/fan-in | Task.all over many activities |
| Human interaction | waitForExternalEvent + timer |
| Long polling / monitor | Loop with createTimer, then continueAsNew to cap history |
Changing an orchestrator’s code while instances are in flight can break replay. Version
orchestrations by name (fulfil_v2) or drain running instances first.
Local run and deploy
func init --worker-runtime node --language typescript # new app only
func new --template "HTTP trigger" --name upload
func start # needs Azurite or a real storage account
az functionapp create -g "$RG" -n "$APP" --storage-account "$SA" \
--flexconsumption-location westeurope --runtime node --runtime-version 22 \
--assign-identity "[system]"
func azure functionapp publish "$APP" # outward-facing: confirm first
Run Azurite (npx azurite --silent) for local storage triggers, with
"AzureWebJobsStorage": "UseDevelopmentStorage=true" in local.settings.json. Package
installs and publishing need network access.
Gotchas
- A function that does not appear after deploy usually failed indexing: check
func startoutput locally and the app’s Log stream for “No job functions found”. - Queue and Service Bus triggers are at-least-once; make handlers idempotent and rely on
poison queues (
<queue>-poison) or dead-letter queues rather than infinite retries. authLevel: "function"keys are shared secrets, not user auth. Put Entra ID auth (Easy Auth or API Management) in front for user-facing APIs.- Changing app settings restarts the app.
- Blob triggers on Flex Consumption use the Event Grid source; polling blob triggers are slow and not supported there.
Verification
func startlists every function with its trigger; a local request or Azurite message produces the expected output.- After publish,
az functionapp function list -g "$RG" -n "$APP" -o tableshows them all. - Application Insights shows successful invocations and no indexing errors.
- A replayed message produces one side effect.
Report template
**App:** orders-func (Flex Consumption, westeurope, Node 22, v4 model)
**Functions:** upload (HTTP POST), orders (Service Bus queue), sweep (timer 15 min)
**Identity:** system-assigned; Storage Blob Data Owner + Queue Data Contributor, Service Bus Data Receiver
**Durable:** fulfil orchestration, approval wait 24 h, activities idempotent on order id
**Verified:** func start local run, publish OK, 3 test invocations in App Insights What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Azure Functions"
short_description: "Triggers, bindings and durable workflows done right"
default_prompt: "$azure-functions Add a Service Bus queue trigger to this Azure Functions app using managed identity" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.