Summary
plugins/railway/skills/use-railway/scripts/railway-api.sh currently reads Railway auth only from ~/.railway/config.json:user.token.
Railway CLI 4.x can store the authenticated session under user.accessToken, with user.refreshToken and user.tokenExpiresAt, instead of user.token. In that case railway whoami --json works, but the helper exits with No Railway token found.
Affected paths
plugins/railway/skills/use-railway/scripts/railway-api.sh
plugins/railway/skills/use-railway/references/request.md
AGENTS.md
CLAUDE.md
plugins/railway/.claude-plugin/plugin.json if the fix is shipped as a skill/plugin content update
Current behavior
The helper has this auth lookup:
TOKEN=$(jq -r '.user.token' "$CONFIG_FILE")
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
echo '{"error": "No Railway token found. Run: railway login"}'
exit 1
fi
On a machine authenticated with Railway CLI 4.42.1, the config shape was:
user.accessToken
user.refreshToken
user.tokenExpiresAt
There was no user.token.
As a result, this failed:
plugins/railway/skills/use-railway/scripts/railway-api.sh \
'query { me { name email } }'
With:
{"error": "No Railway token found. Run: railway login"}
But this succeeded:
Manually using ~/.railway/config.json:user.accessToken as the bearer token against https://backboard.railway.com/graphql/v2 also succeeded.
Expected behavior
railway-api.sh should work when the Railway CLI is authenticated, including with Railway CLI 4.x config.
It should resolve auth in this order:
RAILWAY_API_TOKEN, useful for automation.
~/.railway/config.json:user.accessToken, current Railway CLI 4.x session token shape.
~/.railway/config.json:user.token, legacy CLI config fallback.
If user.accessToken is present and user.tokenExpiresAt is expired, the helper could run railway whoami --json once to let the CLI refresh the session, then re-read the config.
Suggested fix
Centralize compatibility in railway-api.sh so all downstream skill scripts inherit the behavior.
Suggested token lookup shape:
CONFIG_FILE="$HOME/.railway/config.json"
if [[ -z "${RAILWAY_API_TOKEN:-}" && ! -f "$CONFIG_FILE" ]]; then
echo '{"error": "Railway config not found and RAILWAY_API_TOKEN is not set. Run: railway login"}'
exit 1
fi
read_token() {
if [[ -n "${RAILWAY_API_TOKEN:-}" ]]; then
TOKEN="$RAILWAY_API_TOKEN"
TOKEN_SOURCE="RAILWAY_API_TOKEN"
TOKEN_EXPIRES_AT=""
return
fi
TOKEN=$(jq -r '.user.accessToken // .user.token // empty' "$CONFIG_FILE")
TOKEN_SOURCE=$(jq -r '
if .user.accessToken then "user.accessToken"
elif .user.token then "user.token"
else "none"
end
' "$CONFIG_FILE")
TOKEN_EXPIRES_AT=$(jq -r '.user.tokenExpiresAt // empty' "$CONFIG_FILE")
}
read_token
if [[ -z "$TOKEN" ]]; then
echo '{"error": "No Railway token found. Expected RAILWAY_API_TOKEN, ~/.railway/config.json user.accessToken, or legacy user.token. Run: railway login"}'
exit 1
fi
if [[ "$TOKEN_SOURCE" == "user.accessToken" && "$TOKEN_EXPIRES_AT" =~ ^[0-9]+$ ]]; then
NOW=$(date +%s)
if [[ "$TOKEN_EXPIRES_AT" -le "$NOW" ]]; then
if ! command -v railway >/dev/null 2>&1; then
echo '{"error": "Railway access token is expired and the railway CLI is not installed, so it cannot be refreshed. Run: railway login"}'
exit 1
fi
if ! railway whoami --json >/dev/null 2>&1; then
echo '{"error": "Railway access token is expired and refresh failed. Run: railway login"}'
exit 1
fi
read_token
if [[ -z "$TOKEN" ]]; then
echo '{"error": "Railway access token missing after refresh. Run: railway login"}'
exit 1
fi
fi
fi
Documentation updates
Update the GraphQL helper docs to say the script authenticates in this order:
RAILWAY_API_TOKEN
~/.railway/config.json:user.accessToken
~/.railway/config.json:user.token
Also update AGENTS.md / CLAUDE.md, which currently say the token location is only user.token.
It may also be worth documenting that railway whoami can work while older helper scripts fail if they only check the legacy user.token key.
Reproduction
Using Railway CLI 4.42.1:
Succeeds.
Check config shape without printing secrets:
jq -r 'paths(scalars) | join(".")' ~/.railway/config.json | sort
Observed relevant keys:
user.accessToken
user.refreshToken
user.tokenExpiresAt
Run helper:
plugins/railway/skills/use-railway/scripts/railway-api.sh \
'query { me { name email } }'
Observed:
{"error": "No Railway token found. Run: railway login"}
Impact
This breaks all skill workflows that fall back to the GraphQL helper, including metrics queries and other operations not exposed by the CLI, even though the Railway CLI itself is authenticated.
Note
This is not a request to expose or print tokens. The helper should continue to avoid logging secrets.
Summary
plugins/railway/skills/use-railway/scripts/railway-api.shcurrently reads Railway auth only from~/.railway/config.json:user.token.Railway CLI 4.x can store the authenticated session under
user.accessToken, withuser.refreshTokenanduser.tokenExpiresAt, instead ofuser.token. In that caserailway whoami --jsonworks, but the helper exits withNo Railway token found.Affected paths
plugins/railway/skills/use-railway/scripts/railway-api.shplugins/railway/skills/use-railway/references/request.mdAGENTS.mdCLAUDE.mdplugins/railway/.claude-plugin/plugin.jsonif the fix is shipped as a skill/plugin content updateCurrent behavior
The helper has this auth lookup:
On a machine authenticated with Railway CLI
4.42.1, the config shape was:There was no
user.token.As a result, this failed:
plugins/railway/skills/use-railway/scripts/railway-api.sh \ 'query { me { name email } }'With:
{"error": "No Railway token found. Run: railway login"}But this succeeded:
Manually using
~/.railway/config.json:user.accessTokenas the bearer token againsthttps://backboard.railway.com/graphql/v2also succeeded.Expected behavior
railway-api.shshould work when the Railway CLI is authenticated, including with Railway CLI 4.x config.It should resolve auth in this order:
RAILWAY_API_TOKEN, useful for automation.~/.railway/config.json:user.accessToken, current Railway CLI 4.x session token shape.~/.railway/config.json:user.token, legacy CLI config fallback.If
user.accessTokenis present anduser.tokenExpiresAtis expired, the helper could runrailway whoami --jsononce to let the CLI refresh the session, then re-read the config.Suggested fix
Centralize compatibility in
railway-api.shso all downstream skill scripts inherit the behavior.Suggested token lookup shape:
Documentation updates
Update the GraphQL helper docs to say the script authenticates in this order:
RAILWAY_API_TOKEN~/.railway/config.json:user.accessToken~/.railway/config.json:user.tokenAlso update
AGENTS.md/CLAUDE.md, which currently say the token location is onlyuser.token.It may also be worth documenting that
railway whoamican work while older helper scripts fail if they only check the legacyuser.tokenkey.Reproduction
Using Railway CLI
4.42.1:Succeeds.
Check config shape without printing secrets:
Observed relevant keys:
Run helper:
plugins/railway/skills/use-railway/scripts/railway-api.sh \ 'query { me { name email } }'Observed:
{"error": "No Railway token found. Run: railway login"}Impact
This breaks all skill workflows that fall back to the GraphQL helper, including metrics queries and other operations not exposed by the CLI, even though the Railway CLI itself is authenticated.
Note
This is not a request to expose or print tokens. The helper should continue to avoid logging secrets.