Skip to content

IaC: defineRailway is always called with an empty context object #1137

Description

@denistorresan

The context argument documented in the IaC reference is never populated. ctx.environment, ctx.environmentName, ctx.isEnvironment(), ctx.projectId and ctx.shared are all undefined, so a single authoring file cannot render different desired state per environment — which is the documented purpose of the context.

Reproduction

.railway/railway.ts:

import { defineRailway } from "railway/iac";

export default defineRailway((ctx) => {
  throw new Error(`ctx = ${JSON.stringify(ctx)}`);
});
$ railway config plan
Error: ctx = {}

Object.getOwnPropertyNames(ctx) is also empty, so it is not a matter of non-enumerable properties.

Root cause

src/iac/eval.rs, in evaluate_javascript:

const project = typeof exported === "function" ? await exported({}) : exported;

The empty object is a literal inside the wrapper script passed to node --eval. The context is never constructed, and createRailwayContext — which the TypeScript SDK exports for exactly this — is not called anywhere in the CLI.

Because it is a constant in the wrapper, this is not platform- or Node-specific. Reproduced on Windows with Node 23.3.0, and #1117 reports the same missing context on macOS with Node 26.7.0, reaching it through ctx.shared instead.

evaluate_file takes only &Path, so nothing about the project or environment can reach the evaluator today.

The values are already at the call site

engine::run holds everything RailwayContextInput declares, two lines above the call to evaluate_file:

LinkedProject field context field
project projectId
name projectName
environment environmentId
environment_name environmentName

plus the command: &str parameter of run itself.

Suggested fix

Thread a context value through evaluate_file / evaluate_javascript and build it in the wrapper with the SDK's own createRailwayContext, which already normalises environment / environmentName and supplies isEnvironment and randomString.

ctx.shared needs more than parameter threading: the shared variables come from fetch_current_environment, which runs after evaluate_file. That half is #1117 and could be handled separately — the static fields alone are enough to unblock per-environment configuration.

The Python and Go evaluators pass no context either.

Workaround, for anyone who lands here

Until the context is populated, per-environment differences cannot be expressed in one file. Splitting the definition into a shared module plus one thin entry file per environment works, selected with --file:

.railway/stack.ts               the topology, parameterised
.railway/railway.ts             staging
.railway/railway.production.ts  production
railway config plan
railway config plan --file .railway/railway.production.ts

Note that the file does not choose the environment — railway link does — so planning the production file against a staging link is accepted and applies production values to staging. Since the context cannot be read, the only guard available is shelling out to railway status from the authoring file and refusing to build when the linked environment disagrees with the file.

Environment

  • Railway CLI 5.45.0
  • Node 23.3.0
  • Windows 11
  • railway npm package 3.11.0

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