localhost isn't one address
Dev container OAuth that times out, and the IPv6 loopback behind it.
The short version of a problem I tell at length in One org, one container. If you got here from the error message, this is the part you need.
sf org login web inside a dev container. The browser opened on the host. I logged in. Login succeeded.
Then I waited. And a few minutes later the CLI died with AuthTimeoutError.
Port 1717 was forwarded. Everything looked right. This is my least favorite kind of problem: every individual piece works.
What was actually happening
The CLI's callback server binds to the name localhost. Node resolves that name to ::1 before 127.0.0.1, so the server was listening on IPv6 loopback only. Meanwhile, the editor's port forward delivers the callback over IPv4 127.0.0.1.
Connection refused. The CLI never sees the redirect, and it just sits there patiently waiting for a callback that's knocking on the wrong door.
The fix
Make it container-wide, so the IDE's own "Authorize an Org" command picks it up too:
// .devcontainer/devcontainer.json
"containerEnv": {
"NODE_OPTIONS": "--dns-result-order=ipv4first",
"SF_WEB_OAUTH_SERVER_TIMEOUT": "300000",
"DEV_ORG_ALIAS": "My Sandbox"
},
"forwardPorts": [1717],
"portsAttributes": { "1717": { "label": "Salesforce OAuth callback", "onAutoForward": "silent" } }
While I was in there: a small script runs on folder open, checks sf org list --skip-connection-status for the alias, and only kicks off the login when nothing's authorized. The auth lives in a named volume keyed on the workspace folder name. So when I clone a second copy for a second client org, it gets its own auth, and the two never share a token.*
*When you work across multiple clients, "never share a token" isn't a nice-to-have.