Read more of this story at Slashdot.
When The Mandalorian first debuted on Disney Plus, it was a refreshing reminder of how fascinating Star Wars stories can be when they aren't focused on the same handful of well-established characters. Especially in its first season, the series felt like a sign that Disney was shifting gears after disappointing fans with its last trilogy of big budget features. But as The Mandalorian went on, it became overstuffed with supporting characters and haphazardly introduced lore that did little to make the show feel like must-see TV.
The relative weakness of The Mandalorian's most recent season is part of what made it so surprising when Lucasfilm a …
You shipped a Python app to App Service. It worked in the demo. It works locally. In production, /chat is returning 502s — but /health is green, the deployment succeeded, the logs are quiet, and your laptop can't reproduce it.
What you actually need is a shell on the running container so you can poke at DNS, env vars, installed packages, the listening port, and the AI endpoint your app is calling. The platform has had SSH for a while, but the playbook of "open SSH, then remember which 14 commands to run" was tribal knowledge.
We just shipped a set of SSH helper aliases that turn that tribal knowledge into one-word commands. apphelp shows you everything; appconfig, showpkgs, and appcurl cover the app side; ai-test, ai-diagnose, ai-curl, ai-latency, ai-dns, and ai-access-check cover the Azure AI Foundry side.
This post is a hands-on tour. We built a deliberately fragile FastAPI sample with six different fault modes, deployed it, broke it, and SSH'd in to watch the aliases drive each one to root cause. Every transcript below is real output from the deployed sample.
📦 Sample repo: seligj95/app-service-ssh-diagnostics-python —
azd upand you have a fault-injectable Python + Foundry app live in your subscription in about 4 minutes.
FastAPI app, Python 3.14, App Service Linux on P0v3 — uses the new Oryx FastAPI auto-detection so no custom startup command is needed
POST /admin/fault toggles one of seven modes: off, bad-creds, wrong-endpoint, dns-fail, port-mismatch, dep-import-error, latency-spikeGET / is a landing page with a built-in cheat sheet of the SSH aliasesThe endpoints are intentionally boring. The point is to give the aliases something realistic to chew on.
A quick note on Azure OpenAI vs. AI Foundry. This sample provisions an Azure OpenAI account (
kind: OpenAI). The newai-*aliases speak the OpenAI chat-completions API (/openai/deployments/<model>/chat/completions), which is identical on Azure OpenAI and on Azure AI Foundry projects — both expose*.openai.azure.comendpoints, both accept managed-identity bearer tokens, both speak the same schema. The aliases work against either; the env-var nameAZURE_AI_FOUNDRY_ENDPOINTis just the alias contract. Drop a Foundry endpoint into it and the same walkthrough applies.
Shout-out to the new FastAPI auto-detect on Python 3.14. This sample also benefits from another recent App Service change: on Python 3.14+, App Service automatically detects FastAPI apps and starts them with gunicorn -k uvicorn_worker.UvicornWorker — no custom startup command needed. Our Bicep ships an empty
appCommandLineand lets Oryx do the right thing. The whole sample is a nice tour of recent App Service Python improvements landing together.
apphelpAfter azd up finishes, the first thing to do over SSH is:
az webapp ssh -g rg-ssh-diag-demo -n app-web-<token>
Then inside the container:
$ apphelp
apphelp prints every alias the image ships with, grouped by category. You don't need to memorize anything — when you forget what checkport does, you run apphelp and it's right there. We'll lean on most of these:
showpkgs, appconfig, appenvapplogs, deploylogs, logfilesappcurl, checkport, gohome, gosrcai-test, ai-dns, ai-access-check, ai-curl, ai-latency, ai-diagnoseinstall-nettoolsBefore breaking anything, run ai-diagnose. This is the one-shot "is my AI path healthy?" check, and it's the alias we reach for most:
$ ai-diagnose
────────────────────────────────────────────────────────────────
AI Foundry Diagnostics
────────────────────────────────────────────────────────────────
[✓] Managed identity token
[✓] DNS resolution (d8f9grasb7ewc7h8.ai-gateway.eastus2-01.azure-api.net. - public)
[✓] Foundry connectivity (761ms)
────────────────────────────────────────────────────────────────
Three green checks tell you three different things: the managed identity is issuing tokens, the Foundry hostname resolves, and the endpoint responded in a reasonable time. If any of these are red, you already know which layer the fault is in.
For more detail, the individual aliases are worth knowing:
$ ai-test
✓ Connected | 1009ms | Model: gpt-4o-mini | Auth: Managed Identity
$ ai-access-check
✓ Foundry endpoint: https://cog-ftirxupt2yjoe.openai.azure.com/
✓ Model: gpt-4o-mini
✓ Using auth mode: Managed Identity
✓ Access check passed: authorized to call Foundry
$ ai-latency
Running 5 requests to gpt-4o-mini...
Request 1: 679ms ✓
Request 2: 826ms ✓
Request 3: 758ms ✓
Request 4: 641ms ✓
Request 5: 664ms ✓
Results (5/5 successful):
Avg: 713ms | Min: 641ms | Max: 826ms
And the app side:
$ checkport
✓ App is listening on port 8000
$ appcurl /health
HTTP Status: 200
Time: 0.002417s
Size: 5423 bytes
That's our "everything is fine" reference. Now let's break things.
A subtle thing trips people up the first time. POST /admin/fault mutates the app process's environment — but your SSH shell is a separate process. It inherited the container's env when you opened the session, so ai-test will still see the healthy values.
The sample handles this by also writing a small file to the persistent share:
# app/faults.py
def _write_env_file() -> None:
"""Write fault env to /home/site/diagnostics/fault.env so SSH can `source` it."""
diag = Path("/home/site/diagnostics")
diag.mkdir(parents=True, exist_ok=True)
snap = _snapshot_unlocked()
lines = [f"# Active fault: {snap['mode']}", ""]
for k, v in snap["env"].items():
lines.append(f"export {k}={shlex.quote(v) if v else "''"}")
(diag / "fault.env").write_text("\n".join(lines) + "\n")
After toggling a fault, run this once in your SSH session:
source /home/site/diagnostics/fault.env
Now the aliases see the same env the broken app sees. This pattern — flip a flag from outside, source the change inside — is worth stealing for your own debugging workflows.
Some faults are in the path between App Service and Foundry — wrong endpoint, broken DNS, network. The ai-* aliases reproduce the failure end-to-end, and they tell you exactly which layer.
wrong-endpoint — a typo in the AOAI endpointThe most common AI-side incident: someone fat-fingers an app setting. The endpoint resolves to something (it's still *.openai.azure.com) but it's not your resource.
curl -X POST $URL/admin/fault -H 'content-type: application/json' \
-d '{"mode":"wrong-endpoint"}'
curl $URL/chat -H 'content-type: application/json' \
-d '{"prompt":"hi"}'
# HTTP 502
# {"detail":"APIConnectionError: Connection error."}
SSH in, source the fault env, run the AI aliases:
$ source /home/site/diagnostics/fault.env
$ ai-dns
Resolving: this-resource-does-not-exist.openai.azure.com
✗ DNS resolution failed for this-resource-does-not-exist.openai.azure.com
$ ai-curl
Request:
POST https://this-resource-does-not-exist.openai.azure.com//openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-02-01
Authorization: Bearer [hidden]
Content-Type: application/json
curl: (6) Could not resolve host: this-resource-does-not-exist.openai.azure.com
$ ai-diagnose
[✓] Managed identity token
[✗] DNS resolution failed for this-resource-does-not-exist.openai.azure.com
[✗] Foundry connectivity (HTTP 000)
ai-diagnose collapses the whole story into three lines: token works, DNS fails, connectivity fails. The fault is unambiguously a bad endpoint — check appconfig and your Bicep parameters.
dns-fail — NXDOMAINA subtler variant of the same failure mode is when the endpoint is structurally wrong (private endpoint misconfigured, hosts file mishap, custom domain expired). ai-dns calls it out the same way:
$ ai-dns
Resolving: no-such-host.invalid.example
✗ DNS resolution failed for no-such-host.invalid.example
If you need deeper diagnostics — say, you suspect a flaky resolver rather than the hostname itself — install-nettools gives you dig, nslookup, and friends without rebuilding the container.
$ install-nettools
$ dig openai.azure.com
$ nslookup cog-ftirxupt2yjoe.openai.azure.com
ai-test but break your appHere's the most useful thing we learned building this sample: ai-test can be green while your app is on fire, and that's a signal, not a bug.
The ai-* aliases call Foundry directly. If they're green and your app is red, the platform-to-Foundry path is fine — the divergence is in your app. Time to pivot to appenv, applogs, showpkgs.
bad-creds — wrong AZURE_CLIENT_IDThis one is the classic user-assigned managed identity mishap: you scoped your code to a user-assigned managed identity, but the GUID in AZURE_CLIENT_ID doesn't actually exist (or wasn't granted RBAC).
curl -X POST $URL/admin/fault -d '{"mode":"bad-creds"}'
curl $URL/chat -d '{"prompt":"hi"}'
# HTTP 502
# {"detail":"ClientAuthenticationError: DefaultAzureCredential failed to retrieve a token..."}
Now SSH in and try the AI aliases:
$ source /home/site/diagnostics/fault.env
$ ai-test
✓ Connected | 734ms | Model: gpt-4o-mini | Auth: Managed Identity
$ ai-access-check
✓ Foundry endpoint: https://cog-ftirxupt2yjoe.openai.azure.com/
✓ Using auth mode: Managed Identity
✓ Access check passed: authorized to call Foundry
Both green. That looks like a contradiction, but it's not. The aliases authenticate using the system-assigned managed identity directly (via IMDS), and they pass. Your Python app uses DefaultAzureCredential, which honors AZURE_CLIENT_ID to pick a user-assigned identity — and that one is broken.
The takeaway: when ai-test is green but /chat is red, the platform's identity is fine. Pivot to appenv to see exactly what env your app process sees, and check AZURE_CLIENT_ID:
$ appenv | grep AZURE_CLIENT_ID
AZURE_CLIENT_ID=00000000-0000-0000-0000-000000000000
There's the bug. The aliases didn't fail — they told you the fault isn't in the platform. That's diagnosis by elimination, and it's faster than guessing.
dep-import-error — your code throwsSame pattern. The app raises an ImportError on /chat, the AI aliases are green:
curl -X POST $URL/admin/fault -d '{"mode":"dep-import-error"}'
curl $URL/chat -d '{"prompt":"hi"}'
# HTTP 500
# {"detail":"ImportError: No module named 'tiktoken'..."}
This is where the app-side aliases earn their keep:
$ showpkgs | head -20
──────────────────────────────────────────────────────
Virtual environment packages (antenv)
──────────────────────────────────────────────────────
Package Version
-------------------------------------- ---------
annotated-types 0.7.0
anyio 4.13.0
azure-core 1.41.0
azure-identity 1.19.0
azure-monitor-opentelemetry 1.8.8
...
No tiktoken in that list. Confirmation in one command — no need to remember pip list or where the virtualenv lives.
deploylogs then tells you what the last deployment actually built:
$ deploylogs 10
Latest deployment: b8a64ed4-b6b7-4419-91eb-6d8e4e7ef323
Log file: /home/site/deployments/b8a64ed4-b6b7-4419-91eb-6d8e4e7ef323/log.log
2026-05-18T19:10:52.3844297Z,Parsing the build logs,abc3cf97-...
2026-05-18T19:10:52.5414396Z,Found 0 issue(s),7d11d013-...
2026-05-18T19:10:52.7913394Z,Build Summary :,...
2026-05-18T19:10:53.5643089Z,Deployment successful. deployer = Push-Deployer ...
Build was clean. The package just isn't in requirements.txt. Two aliases, one minute, root cause.
port-mismatch — uvicorn binds the wrong portA real-world bug: someone sets WEBSITES_PORT=9999 in app settings to expose a different port, but the app still binds to 8000.
curl -X POST $URL/admin/fault -d '{"mode":"port-mismatch"}'
The aliases tell you exactly which port everything sees:
$ checkport
Checking if app is listening on port 8000...
✓ App is listening on port 8000
$ appcurl /health
Testing app at localhost:8000 ...
HTTP Status: 200
Time: 0.002417s
$ appconfig
PORT
Value: 8000
Note: The port your Python app should listen on. Default is 8000.
The app is healthy from inside the container. The mismatch is between what the platform tries to forward to and what uvicorn is bound to. This is the kind of fault where curling the public URL fails but appcurl /health succeeds — and the contrast is itself the diagnosis.
latency-spike — the alias bench is fast, your app is slowThe app injects 4 seconds of asyncio.sleep before each Foundry call. /chat is now ~4.5 seconds. ai-latency:
$ ai-latency
Running 5 requests to gpt-4o-mini...
Request 1: 715ms ✓
Request 2: 588ms ✓
Request 3: 578ms ✓
Request 4: 669ms ✓
Request 5: 643ms ✓
Results (5/5 successful):
Avg: 638ms | Min: 578ms | Max: 715ms
Foundry, from this instance, averages 638ms. If your app is taking 5 seconds end-to-end and ai-latency says the model is sub-second, the slowness is in your code — not in Foundry, not in the network. Time to look at App Insights end-to-end transactions, or at any pre-call work (retrieval, vector lookup, your own sleep).
Before these aliases, the SSH playbook for a Python AI app went something like: open SSH, dig around /home/site/wwwroot/antenv, grep applicationHost.config for ports, write a curl by hand against the AOAI endpoint with a manually-fetched managed identity token, hope you got the API version right.
Now it's ai-diagnose. If that's red, you know exactly which layer. If it's green, you know the fault is in your code or your settings, and appenv, appconfig, showpkgs, applogs walk you the rest of the way.
Three patterns we'd lean on going forward:
apphelp and ai-diagnose every time. Don't try to remember the right command — let the aliases tell you.ai-test being green as a signal, not a finish line. If /chat is red and ai-test is green, the platform path is fine; pivot to app-side aliases.source /home/site/diagnostics/fault.env as a pattern. Any time you want your SSH shell to see what the app process sees, write env to a file and source it. It's a small thing that removes a huge class of "but it worked when I tested it" confusions.The aliases are GA today on Python images and we have ideas for where they go next — Node, .NET, more ai-* checks (Foundry agents, vector indexes), tighter integration with azd diagnose. If you have a Python app on App Service and you want a specific alias added, tell us by dropping a comment on this post.
git clone https://github.com/seligj95/app-service-ssh-diagnostics-python
cd app-service-ssh-diagnostics-python
azd auth login
azd up
Four minutes later you'll have the whole thing live. Then curl -X POST $URL/admin/fault -d '{"mode":"<pick one>"}', SSH in, and walk through any of the six faults above. The README has the full alias-to-fault map.
Welcome back to GitHub Checkout! In this episode, we sit down with Nhu to explore the new auto mode for GitHub Copilot. Instead of manually testing different models, auto mode intelligently routes your prompt to the best available model based on reasoning needs and real-time capacity. We walk through a demo in the CLI and VS Code to show how it saves time and optimizes performance. Let us know what you think of this feature in the comments!
#GitHubCopilot #GitHub #AI
— CHAPTERS —
00:00 - Welcome to GitHub Checkout
00:23 - What is auto mode?
01:21 - Demo: how auto selects models
04:23 - How we built intelligent routing
07:17 - Model evaluations and what is next
Stay up-to-date on all things GitHub by connecting with us:
YouTube: https://gh.io/subgithub
Blog: https://github.blog
X: https://twitter.com/github
LinkedIn: https://linkedin.com/company/github
Insider newsletter: https://resources.github.com/newsletter/
Instagram: https://www.instagram.com/github
TikTok: https://www.tiktok.com/@github
About GitHub
It’s where over 180 million developers create, share, and ship the best code possible. It’s a place for anyone, from anywhere, to build anything—it’s where the world builds software. https://github.com
What actually happens after you drop a letter in a blue collection box or click “ship” on a package? And why does tracking sometimes look confusing even when everything is moving as it should? In this episode of Mailin’ It!, hosts Karla Kirby and Jeff Marino dive into the questions Postal Service customers ask most often and break down what is happening behind the scenes of the nation’s mail network. From package tracking and shipping costs to mail forwarding, delivery safety, scam prevention, and the rollout of new USPS delivery vehicles, we explore how the Postal Service handles the everyday situations customers encounter most and also learn about the online tools and customer support resources available to help make mailing and shipping easier.
Hosted by Simplecast, an AdsWizz company. See pcm.adswizz.com for information about our collection and use of personal data for advertising.