Skip to content

Fix context is not defined crash in safeoutputs MCP server on create_pull_request#18939

Merged
pelikhan merged 4 commits intomainfrom
copilot/fix-safeoutputs-crash
Mar 1, 2026
Merged

Fix context is not defined crash in safeoutputs MCP server on create_pull_request#18939
pelikhan merged 4 commits intomainfrom
copilot/fix-safeoutputs-crash

Conversation

Copy link
Contributor

Copilot AI commented Feb 28, 2026

  • Fix context is not defined crash in getDefaultTargetRepo (GITHUB_REPOSITORY fallback + typeof guard)
  • Fix resolveAndValidateRepo regression: only error when both defaultTargetRepo and item.repo are empty
  • Fix test env isolation: swap fallback order (context before GITHUB_REPOSITORY) to prevent CI env leaking into handler tests
  • unassign_from_user.test.cjs: clear env vars in beforeEach (belt-and-suspenders)
  • repo_helpers.test.cjs: add test for item.repo with empty defaultTargetRepo
Original prompt

This section details on the original issue you should resolve

<issue_title>Bug Report: safeoutputs MCP server crashes with context is not defined on create_pull_request</issue_title>
<issue_description># Bug Report: safeoutputs MCP server crashes with context is not defined on create_pull_request

Repository: https://github.com/github/gh-aw
Version: v0.50.6 (awf_version: v0.23.0, awmg_version: v0.1.5)
Severity: High — PR creation silently fails; committed changes are lost when the container exits


Summary

When the agent calls safeoutputs-create_pull_request, the safeoutputs MCP server crashes with a JavaScript ReferenceError:

MCP server 'safeoutputs': McpError: MCP error 0: backend error: code=-32603, message=context is not defined

Because the MCP call never succeeds, nothing is written to the JSONL queue, the safe_outputs job has nothing to process, and the branch is never pushed to GitHub. Any commits made inside the AWF Docker container are permanently lost when the container exits.


Reproduction

  1. Trigger a workflow via workflow_dispatch that uses safe-outputs: create-pull-request
  2. Have the agent make file edits, commit them to a branch, then call safeoutputs-create_pull_request
  3. Observe 4 consecutive failures, all with the same error:
✗ safeoutputs-create_pull_request
  MCP server 'safeoutputs': McpError: MCP error 0: backend error: code=-32603,
  message=context is not defined

Root Cause Analysis

The safeoutputs MCP server is started as a standalone Node.js daemon by start_safe_outputs_server.sh inside the AWF Docker container. The create_pull_request handler references a JavaScript variable named context — which is only injected into the scope of actions/github-script workflow steps by the Actions runner, and is not available in a standalone daemon process.

From the compiled lock file, the agent job environment confirms the dispatch context:

GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}

For workflow_dispatch events, github.event.pull_request is null, so this renders as an empty string. However, the empty PR number is a symptom — the actual crash is the context variable reference inside the daemon.

The safe-output-items.jsonl artifact is 0 bytes, confirming no items were ever enqueued. The safe_outputs job ran successfully but processed nothing, so no branch push occurred.


Impact

  • The agent successfully completes all code changes (file edits, lint, commit)
  • The branch exists only inside the ephemeral AWF Docker container
  • The container exits, the branch is permanently lost
  • The safe_outputs job reports success (it processed 0 items with 0 errors), so the workflow conclusion is success — making the failure invisible without auditing

Affected scenario: Any workflow_dispatch-triggered workflow that uses safe-outputs: create-pull-request. Likely also affects any non-PR event trigger (scheduled runs, issue comments, etc.).


Audit Evidence

agent-stdio.log (run 22501315269):

✗ safeoutputs-create_pull_request
  MCP server 'safeoutputs': McpError: MCP error 0: backend error: code=-32603,
  message=context is not defined

✗ safeoutputs-create_pull_request
  MCP server 'safeoutputs': McpError: MCP error 0: backend error: code=-32603,
  message=context is not defined

✗ safeoutputs-create_pull_request
  MCP server 'safeoutputs': McpError: MCP error 0: backend error: code=-32603,
  message=context is not defined

✗ safeoutputs-create_pull_request
  MCP server 'safeoutputs': McpError: MCP error 0: backend error: code=-32603,
  message=context is not defined

● safeoutputs-missing_tool
  └ {"result":"success"}

safe-output-items.jsonl: 0 bytes (nothing enqueued)

aw_info.json:

{
  "cli_version": "v0.50.6",
  "awf_version": "v0.23.0",
  "engine_id": "copilot",
  "event_name": "workflow_dispatch"
}

Expected Behavior

safeoutputs-create_pull_request should successfully enqueue a PR creation request in the JSONL queue, which the safe_outputs job then processes to push the branch and create the PR on GitHub.


Actual Behavior

The safeoutputs MCP daemon crashes with ReferenceError: context is not defined on every create_pull_request call when triggered from a workflow_dispatch event, silently discarding all agent work.


Workaround

Add a bash fallback to the workflow prompt that pushes the branch and creates the PR via gh pr create with a PAT when safeoutputs-create_pull_request fails:

git remote set-url origin "https://x-access-token:${CROSS_REPO_PAT}@github.com/ORG/REPO.git"
git push -u origin "BRANCH_NAME"
export GH_TOKEN="${CROSS_REPO_PAT}"
gh pr create --repo ORG/REPO --title "TITLE" --body "BODY" --base main --label my-label

This bypasses the safeoutputs MCP entirely and works because api.github.com is in the...


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…eate_pull_request

The getDefaultTargetRepo function in repo_helpers.cjs used the bare `context`
global variable without any guard. When the safeoutputs MCP server runs as a
standalone Node.js daemon, `context` may not be defined, causing:
  ReferenceError: context is not defined

Fix:
- Add process.env.GITHUB_REPOSITORY as a direct env var fallback before context
- Guard the context access with typeof check (matching get_base_branch.cjs pattern)
- Return empty string when all fallbacks fail (instead of crashing)
- Add early descriptive error in resolveAndValidateRepo when default repo is empty

Tests:
- Add delete process.env.GITHUB_REPOSITORY to beforeEach for proper isolation
- Add test: GITHUB_REPOSITORY env var used when context is not defined
- Add test: GH_AW_TARGET_REPO_SLUG preferred over GITHUB_REPOSITORY
- Add test: empty string returned when no config, env vars, or context
- Add test: descriptive error when defaultTargetRepo is empty

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix MCP server crash in safeoutputs during PR creation Fix context is not defined crash in safeoutputs MCP server on create_pull_request Feb 28, 2026
@pelikhan
Copy link
Contributor

pelikhan commented Mar 1, 2026

@copilot review of this fix is still available if shim.cjs is already loaded

Copy link
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot review of this fix is still available if shim.cjs is already loaded

You're right that safe-outputs-mcp-server.cjs (the HTTP entry point) loads shim.cjs first, which sets global.context from GITHUB_REPOSITORY. So in the HTTP server path, context is always defined by the time repo_helpers.cjs is used.

The fix remains valuable for two reasons:

  1. The stdio server (safe_outputs_mcp_server.cjs) doesn't load shim.cjs — it requires modules directly without the shim preamble, so context is genuinely undefined there. The typeof context !== "undefined" guard protects that path.

  2. Checking process.env.GITHUB_REPOSITORY directly is more explicit and reliable than depending on the shim's context re-population. It avoids a double-indirection: the shim reads the same env var to populate context.repo, so going direct is cleaner and also works when the shim is present.

No code changes needed.

@pelikhan pelikhan marked this pull request as ready for review March 1, 2026 00:31
Copilot AI review requested due to automatic review settings March 1, 2026 00:31
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a ReferenceError: context is not defined crash in the safe-outputs MCP server by making default target-repo resolution work in standalone daemon mode (non-actions/github-script contexts).

Changes:

  • Add process.env.GITHUB_REPOSITORY fallback and guard context access in getDefaultTargetRepo.
  • Add an early, more actionable error in resolveAndValidateRepo when no default target repo can be determined.
  • Extend Vitest coverage for the new fallback/guard behavior and the new error message.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
actions/setup/js/repo_helpers.cjs Make default repo resolution resilient when context is unavailable; improve error surfaced when repo can’t be resolved.
actions/setup/js/repo_helpers.test.cjs Add tests for GITHUB_REPOSITORY fallback, context-undefined path, empty default, and new descriptive error.
Comments suppressed due to low confidence (1)

actions/setup/js/repo_helpers.cjs:121

  • getDefaultTargetRepo can now return an empty string when no config/env/context sources are available, but the JSDoc still states it returns a repository slug in owner/repo format. Please update the doc comment (or change the API to return null/throw) so callers and type-checking expectations match the new behavior.
  // Fall back to GITHUB_REPOSITORY env var (available in standalone daemon mode)
  const githubRepo = process.env.GITHUB_REPOSITORY;
  if (githubRepo) {
    return githubRepo;
  }
  // Fall back to context repo (only available in github-script or shim-provided context)
  if (typeof context !== "undefined" && context.repo?.owner && context.repo?.repo) {
    return `${context.repo.owner}/${context.repo.repo}`;
  }
  return "";

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 224 to 231
function resolveAndValidateRepo(item, defaultTargetRepo, allowedRepos, operationType) {
// Check for empty defaultTargetRepo, which indicates a configuration or environment issue
if (!defaultTargetRepo) {
return {
success: false,
error: `Unable to determine target repository for ${operationType}. Set GH_AW_TARGET_REPO_SLUG, ensure GITHUB_REPOSITORY is available, or configure target-repo in safe-outputs settings.`,
};
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new !defaultTargetRepo guard returns an error even when item.repo is explicitly provided. This is a behavior regression for tools/messages that support a repo override (e.g., unassign_from_user in safe_outputs_tools.json), where operations should still be able to proceed (and be validated against allowedRepos) even if the default repo can’t be inferred. Consider only erroring when both the resolved repo (after trimming) is empty and there’s no item.repo, or moving repo selection (itemRepo) before the guard and checking that instead.

This issue also appears on line 112 of the same file.

See below for a potential fix:

  // Normalize and trim the default target repo (may be empty if not configured)
  const trimmedDefaultTargetRepo = defaultTargetRepo ? String(defaultTargetRepo).trim() : "";

  // Determine target repository for this operation, allowing item.repo to override
  const rawItemRepo = item && item.repo != null ? String(item.repo).trim() : "";
  const itemRepo = rawItemRepo || trimmedDefaultTargetRepo;

  // If we still don't have a repo after considering overrides, treat as configuration/environment issue
  if (!itemRepo) {
    return {
      success: false,
      error: `Unable to determine target repository for ${operationType}. Set GH_AW_TARGET_REPO_SLUG, ensure GITHUB_REPOSITORY is available, or configure target-repo in safe-outputs settings.`,
    };
  }

  // Validate the repository is allowed
  const repoValidation = validateRepo(itemRepo, trimmedDefaultTargetRepo, allowedRepos);

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 952f6cb. resolveAndValidateRepo now extracts item.repo first and only errors when both item.repo and defaultTargetRepo are empty, following the suggested pattern. I also updated validateRepo to receive trimmedDefaultTargetRepo and added a test for the item.repo-with-empty-default case.

@pelikhan
Copy link
Contributor

pelikhan commented Mar 1, 2026

- resolveAndValidateRepo: check item.repo before failing on empty
  defaultTargetRepo so cross-repo operations still work when the
  default repo cannot be inferred (addresses code review comment)
- unassign_from_user.test.cjs: clear GITHUB_REPOSITORY and
  GH_AW_TARGET_REPO_SLUG in beforeEach to prevent CI env leakage
- repo_helpers.test.cjs: add test for item.repo with empty default

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot fix tests https://github.com/github/gh-aw/actions/runs/22531616517/job/65273607784

Fixed in 952f6cb. Added delete process.env.GITHUB_REPOSITORY and delete process.env.GH_AW_TARGET_REPO_SLUG to beforeEach in unassign_from_user.test.cjs to prevent the CI GITHUB_REPOSITORY=github/gh-aw from overriding the global.context mock. All 63 tests pass locally.

@pelikhan
Copy link
Contributor

pelikhan commented Mar 1, 2026

Swap fallback order in getDefaultTargetRepo so context is checked
first, GITHUB_REPOSITORY second. This preserves behaviour for all
existing tests that set global.context (daemon tests delete context
explicitly so GITHUB_REPOSITORY still serves as their fallback),
and avoids CI's GITHUB_REPOSITORY=github/gh-aw overriding the
global.context mock in every handler test file.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot fix tests https://github.com/github/gh-aw/actions/runs/22532568261/job/65274568993

Fixed in b4f2f38. The root cause was that GITHUB_REPOSITORY was checked before context, so CI's GITHUB_REPOSITORY=github/gh-aw overrode the global.context mock in every handler test file. Swapped the fallback order — context is now checked first, GITHUB_REPOSITORY second — which preserves all existing test behavior (tests that simulate the daemon explicitly delete global.context, so GITHUB_REPOSITORY still serves as their fallback).

@pelikhan pelikhan merged commit 42f9dc0 into main Mar 1, 2026
75 checks passed
@pelikhan pelikhan deleted the copilot/fix-safeoutputs-crash branch March 1, 2026 01:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug Report: safeoutputs MCP server crashes with context is not defined on create_pull_request

3 participants