From 0c9d8174401ee1247c203e0c4b0667de9c62e296 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Fri, 10 Apr 2026 22:23:20 +0100 Subject: [PATCH 1/2] feat: add job.workflow_* context properties Add workflow_ref, workflow_sha, workflow_repository, and workflow_file_path to the job context for reusable workflow jobs. These fields provide direct access to the workflow file information without needing to parse github.workflow_ref. - Add 4 new fields to getJobContext() in job.ts - Add descriptions in descriptions.json - Update autocomplete test expectations - Add validation and unit tests --- .../src/complete.expressions.test.ts | 11 +++++++++- .../src/context-providers/descriptions.json | 12 +++++++++++ .../src/context-providers/job.test.ts | 21 +++++++++++++++++++ languageservice/src/context-providers/job.ts | 6 ++++++ .../src/validate.expressions.test.ts | 18 ++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/languageservice/src/complete.expressions.test.ts b/languageservice/src/complete.expressions.test.ts index 0a2f2761..09767c8c 100644 --- a/languageservice/src/complete.expressions.test.ts +++ b/languageservice/src/complete.expressions.test.ts @@ -1164,7 +1164,16 @@ jobs: `; const result = await complete(...getPositionFromCursor(input), {contextProviderConfig}); - expect(result.map(x => x.label)).toEqual(["check_run_id", "container", "services", "status"]); + expect(result.map(x => x.label)).toEqual([ + "check_run_id", + "container", + "services", + "status", + "workflow_file_path", + "workflow_ref", + "workflow_repository", + "workflow_sha" + ]); }); it("job context is suggested within a job output", async () => { diff --git a/languageservice/src/context-providers/descriptions.json b/languageservice/src/context-providers/descriptions.json index b43af228..80364ab1 100644 --- a/languageservice/src/context-providers/descriptions.json +++ b/languageservice/src/context-providers/descriptions.json @@ -218,6 +218,18 @@ }, "check_run_id": { "description": "The unique identifier of the check run for this job." + }, + "workflow_file_path": { + "description": "The path of the workflow file that contains the job. For example, `.github/workflows/my-workflow.yml`." + }, + "workflow_ref": { + "description": "The ref of the workflow file that contains the job. For example, `octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch`." + }, + "workflow_repository": { + "description": "The owner and repository name of the workflow file that contains the job. For example, `octocat/Hello-World`." + }, + "workflow_sha": { + "description": "The commit SHA of the workflow file that contains the job." } }, "secrets": { diff --git a/languageservice/src/context-providers/job.test.ts b/languageservice/src/context-providers/job.test.ts index 4f16596f..4a768f57 100644 --- a/languageservice/src/context-providers/job.test.ts +++ b/languageservice/src/context-providers/job.test.ts @@ -24,6 +24,10 @@ describe("job context", () => { expect(context.get("status")).toBeDefined(); expect(context.get("check_run_id")).toBeDefined(); + expect(context.get("workflow_ref")).toBeDefined(); + expect(context.get("workflow_sha")).toBeDefined(); + expect(context.get("workflow_repository")).toBeDefined(); + expect(context.get("workflow_file_path")).toBeDefined(); expect(context.get("container")).toBeUndefined(); expect(context.get("services")).toBeUndefined(); }); @@ -173,4 +177,21 @@ describe("job context", () => { expect(redis.getDescription("ports")).toBeDefined(); }); }); + + describe("workflow context fields", () => { + it("includes workflow context fields with descriptions", () => { + const workflowContext = {job: {}} as WorkflowContext; + const context = getJobContext(workflowContext); + + expect(context.get("workflow_ref")).toBeDefined(); + expect(context.get("workflow_sha")).toBeDefined(); + expect(context.get("workflow_repository")).toBeDefined(); + expect(context.get("workflow_file_path")).toBeDefined(); + + expect(context.getDescription("workflow_ref")).toBeDefined(); + expect(context.getDescription("workflow_sha")).toBeDefined(); + expect(context.getDescription("workflow_repository")).toBeDefined(); + expect(context.getDescription("workflow_file_path")).toBeDefined(); + }); + }); }); diff --git a/languageservice/src/context-providers/job.ts b/languageservice/src/context-providers/job.ts index d0846671..0a7bbc27 100644 --- a/languageservice/src/context-providers/job.ts +++ b/languageservice/src/context-providers/job.ts @@ -42,6 +42,12 @@ export function getJobContext(workflowContext: WorkflowContext): DescriptionDict // Check run ID jobContext.add("check_run_id", new data.StringData(""), getDescription("job", "check_run_id")); + // Workflow context fields (populated at runtime for reusable workflow jobs) + jobContext.add("workflow_file_path", new data.StringData(""), getDescription("job", "workflow_file_path")); + jobContext.add("workflow_ref", new data.StringData(""), getDescription("job", "workflow_ref")); + jobContext.add("workflow_repository", new data.StringData(""), getDescription("job", "workflow_repository")); + jobContext.add("workflow_sha", new data.StringData(""), getDescription("job", "workflow_sha")); + return jobContext; } diff --git a/languageservice/src/validate.expressions.test.ts b/languageservice/src/validate.expressions.test.ts index 35302903..903d65c6 100644 --- a/languageservice/src/validate.expressions.test.ts +++ b/languageservice/src/validate.expressions.test.ts @@ -432,6 +432,24 @@ jobs: expect(result).toEqual([]); }); + it("job.workflow_ref", async () => { + const input = ` +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo \${{ job.workflow_ref }} + - run: echo \${{ job.workflow_sha }} + - run: echo \${{ job.workflow_repository }} + - run: echo \${{ job.workflow_file_path }} +`; + const result = await validate(createDocument("wf.yaml", input)); + + expect(result).toEqual([]); + }); + it("job.services.", async () => { const input = ` on: push From 763dff2018de396e4e1ee7f7018d497575998cdf Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Tue, 14 Apr 2026 10:55:58 +0000 Subject: [PATCH 2/2] fix: address review nits - update doc comments, test names, and description wording - Update getJobContext doc comment to include workflow identity fields - Rename test to reflect all returned fields, not just status/check_run_id - Rename validate test to 'job.workflow_* fields' covering all 4 properties - Clarify workflow_ref description: 'ref path to' instead of 'ref of' --- languageservice/src/context-providers/descriptions.json | 2 +- languageservice/src/context-providers/job.test.ts | 2 +- languageservice/src/context-providers/job.ts | 2 +- languageservice/src/validate.expressions.test.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/languageservice/src/context-providers/descriptions.json b/languageservice/src/context-providers/descriptions.json index 80364ab1..c8326f64 100644 --- a/languageservice/src/context-providers/descriptions.json +++ b/languageservice/src/context-providers/descriptions.json @@ -223,7 +223,7 @@ "description": "The path of the workflow file that contains the job. For example, `.github/workflows/my-workflow.yml`." }, "workflow_ref": { - "description": "The ref of the workflow file that contains the job. For example, `octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch`." + "description": "The ref path to the workflow file that contains the job. For example, `octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch`." }, "workflow_repository": { "description": "The owner and repository name of the workflow file that contains the job. For example, `octocat/Hello-World`." diff --git a/languageservice/src/context-providers/job.test.ts b/languageservice/src/context-providers/job.test.ts index 4a768f57..0e558443 100644 --- a/languageservice/src/context-providers/job.test.ts +++ b/languageservice/src/context-providers/job.test.ts @@ -18,7 +18,7 @@ describe("job context", () => { expect(context.pairs().length).toBe(0); }); - it("returns status and check_run_id when job has no container or services", () => { + it("returns status, check_run_id, and workflow fields when job has no container or services", () => { const workflowContext = {job: {}} as WorkflowContext; const context = getJobContext(workflowContext); diff --git a/languageservice/src/context-providers/job.ts b/languageservice/src/context-providers/job.ts index 0a7bbc27..2e066e0c 100644 --- a/languageservice/src/context-providers/job.ts +++ b/languageservice/src/context-providers/job.ts @@ -5,7 +5,7 @@ import {WorkflowContext} from "../context/workflow-context.js"; import {getDescription} from "./descriptions.js"; /** - * Returns the job context with container, services, status, and check_run_id. + * Returns the job context with container, services, status, check_run_id, and workflow identity fields. */ export function getJobContext(workflowContext: WorkflowContext): DescriptionDictionary { // https://docs.github.com/en/actions/learn-github-actions/contexts#job-context diff --git a/languageservice/src/validate.expressions.test.ts b/languageservice/src/validate.expressions.test.ts index 903d65c6..850251fc 100644 --- a/languageservice/src/validate.expressions.test.ts +++ b/languageservice/src/validate.expressions.test.ts @@ -432,7 +432,7 @@ jobs: expect(result).toEqual([]); }); - it("job.workflow_ref", async () => { + it("job.workflow_* fields", async () => { const input = ` on: push