Skip to content

use-railway: railway-api.sh fails with Railway CLI 4.x auth config #33

Description

@dringrayson

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:

railway whoami --json

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:

  1. RAILWAY_API_TOKEN, useful for automation.
  2. ~/.railway/config.json:user.accessToken, current Railway CLI 4.x session token shape.
  3. ~/.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:

  1. RAILWAY_API_TOKEN
  2. ~/.railway/config.json:user.accessToken
  3. ~/.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:

railway whoami --json

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions