Skip to content

il plan cannot create sub-issues on repositories where user lacks write/triage access#729

Draft
acreeger wants to merge 2 commits intomainfrom
fix/issue-639__no-write-access
Draft

il plan cannot create sub-issues on repositories where user lacks write/triage access#729
acreeger wants to merge 2 commits intomainfrom
fix/issue-639__no-write-access

Conversation

@acreeger
Copy link
Copy Markdown
Collaborator

Fixes #639

il plan cannot create sub-issues on repositories where user lacks write/triage access

Problem

When using il plan to decompose work on a repository the user does not have write or triage access to (e.g., contributing to an open-source project), the sub-issue creation step fails.

GitHub's sub-issues feature requires at least triage permissions on the repository to link a child issue to a parent via the addSubIssue GraphQL mutation. While anyone can create a regular issue on a public repository, the act of linking it as a sub-issue requires elevated permissions.

This means the following workflow breaks for external contributors:

  1. User forks an open-source repo and clones it locally
  2. User runs il plan to decompose an upstream issue (e.g., il plan #42)
  3. iloom's Architect agent creates child issues on the upstream repo via gh issue create -- this succeeds (public repos allow anyone to create issues)
  4. iloom then calls the addSubIssue GraphQL mutation to link the new issue as a sub-issue of the parent -- this fails because the user doesn't have triage/write access

The same limitation applies to the create_dependency REST API endpoint, which also requires write access.

How iloom is affected

In GitHubIssueManagementProvider.createChildIssue(), the workflow is:

  1. Get parent issue node ID
  2. Create child issue via gh issue create (succeeds)
  3. Get child issue node ID
  4. Call addSubIssue(parentNodeId, childNodeId) (fails with permission error)

This leaves orphaned issues -- the child issues get created but are never linked to the parent, and the error propagates up to the agent.

Relevant GitHub docs

Questions for maintainer

  1. Should this be documented as a known limitation of il plan when used with GitHub on repositories the user doesn't own/have write access to?
  2. Would it make sense to have a fallback behavior -- e.g., creating standalone issues with a markdown reference to the parent (like "Parent: #⁠42") instead of using the sub-issues API when the mutation fails?
  3. Should il plan detect the user's permission level on the target repo before attempting sub-issue creation, and adjust its strategy accordingly?
  4. Is this primarily a GitHub platform limitation that we just need to document, or is it worth building workarounds for?

Context

This doesn't affect Linear or Jira integrations -- Linear supports parent-child relationships atomically during issue creation, and Jira handles it through its own permission model. This is specific to the GitHub sub-issues API.


This PR was created automatically by iloom.

@acreeger
Copy link
Copy Markdown
Collaborator Author

acreeger commented Feb 23, 2026

Complexity Assessment

Classification: SIMPLE

Metrics:

  • Estimated files affected: 2
  • Estimated lines of code: 100
  • Breaking changes: No
  • Database migrations: No
  • Cross-cutting changes: No
  • File architecture quality: Good - Both files are well-structured and <800 LOC
  • Architectural signals triggered: None
  • Overall risk level: Low

Reasoning: The solution is straightforward: detect when createChildIssue() fails due to permission errors (when user lacks triage access) and gracefully fall back to creating a standalone issue with a markdown reference to the parent. This requires adding error handling in GitHubIssueManagementProvider.createChildIssue() to catch permission errors and calling createIssue() as a fallback, then optionally notifying the user about the fallback behavior. The change is isolated to the GitHub provider layer with no cross-cutting concerns or architectural complexity.

@acreeger
Copy link
Copy Markdown
Collaborator Author

acreeger commented Feb 23, 2026

Complexity Assessment - Issue #639

Progress:

  • Read issue and maintainer comment
  • Identify files affected by permission gating logic
  • Assess tool access restrictions (create_child_issue, create_dependency)
  • Estimate scope and complexity
  • Finalize classification

Key Files Affected

Source files to modify:

  1. src/commands/plan.ts (537 LOC) - Permission check at start + read-only mode branch
  2. src/utils/github.ts (933 LOC) - Permission checking utility
  3. src/mcp/GitHubIssueManagementProvider.ts (722 LOC) - Gating create_child_issue/create_dependency
  4. templates/prompts/plan-prompt.txt (514 LOC) - Read-only mode instructions

Test files:
5. src/commands/plan.test.ts (540 LOC) - Tests for permission checking + mode switching

Scope Summary

Files affected: 5 files (4 production + 1 test)

Estimated LOC: 260-320 total (60-100 in plan.ts, 30-50 in github.ts, 80-120 in prompt, 100-150 in tests)

Breaking changes: YES - User-visible behavior change (plan creates comments instead of child issues when user lacks write access)

Cross-cutting changes: YES

  • Permission detection in plan.ts flows through MCP server to prompt template
  • Conditional tool availability (create_child_issue, create_dependency) requires coordinated changes
  • Not a simple if/else; affects multiple architectural layers

File architecture quality: POOR

  • All files >500 LOC: plan.ts (537), github.ts (933), plan-prompt.txt (514)
  • Adding new responsibilities increases cognitive load in already-complex files
  • plan.ts handles template rendering + MCP config + permission gating
  • github.ts is already large utility handling multiple GitHub operations

Architectural complexity signals triggered:

  1. Uncertain approach - Multiple implementation options with different tradeoffs (tool hiding vs. branching vs. error handling)
  2. External constraints - GitHub's addSubIssue/createDependency APIs require upstream permissions
  3. Integration points - Coordinates between CLI, MCP server, and agent prompt instructions
  4. "How" clarity - Read-only mode fallback (comment-only planning) is new functionality requiring design

Risk assessment: MEDIUM-HIGH

  • User-visible behavior change affects existing automation
  • Tool gating complexity requires careful testing at multiple layers
  • Large files with poor architecture increase modification risk
  • New read-only fallback mode needs comprehensive testing
  • Testing burden: permission detection, tool availability, agent behavior, comment generation

Complexity Assessment

Classification: COMPLEX

Metrics:

  • Estimated files affected: 5
  • Estimated lines of code: 260-320
  • Breaking changes: Yes
  • Database migrations: No
  • Cross-cutting changes: Yes
  • File architecture quality: Poor (files 500-933 LOC with mixed concerns)
  • Architectural signals triggered: 4 (uncertain approach, external constraints, integration points, unclear implementation strategy)
  • Overall risk level: Medium-High

Reasoning: This issue appears deceptively simple (permission check) but requires cross-cutting behavioral changes. Permission detection must coordinate between CLI, MCP server tool availability, and agent prompt instructions. Read-only mode fallback is new functionality in already-large files (500+ LOC) with poor architecture. The "how to implement" (especially comment generation as fallback) is not obvious from "what to implement."

@acreeger
Copy link
Copy Markdown
Collaborator Author

acreeger commented Feb 23, 2026

Analysis: Issue #639 - Permission Gating for il plan

Executive Summary

When users run il plan on a GitHub repository they don't have write/triage access to, the child issue creation succeeds but the addSubIssue GraphQL mutation and createDependency REST API fail due to insufficient permissions, leaving orphaned issues. The maintainer's direction is to gate il plan behavior on detected write permissions: when lacking write access, the plan session should produce a detailed breakdown comment on the parent issue instead of creating child issues and dependencies. This is GitHub-only -- Linear and Jira are unaffected.

Questions and Key Decisions

Question Answer
Should read-only mode apply to both "existing issue" and "fresh planning" modes? Yes for existing issue mode (comment on parent issue). For fresh planning mode, there is no parent issue to comment on, so the agent would need to create a single standalone issue with the full breakdown as the body, OR simply output the plan to the terminal without creating any issues. Creating a standalone issue is possible since gh issue create works on public repos without write access. Recommend: in fresh planning + read-only, create a single issue with the full plan (no child issue/dependency linking).
Should the user be warned/informed that they're in read-only mode? Yes. A clear logger.warn() message at plan start, and a template variable so the prompt tells the agent about the constraint.
What viewerPermission levels constitute "has write access"? ADMIN, MAINTAIN, and WRITE should be considered sufficient. TRIAGE and READ are insufficient for sub-issues/dependencies.

Impact Summary

  • 3 files requiring modification: src/commands/plan.ts, templates/prompts/plan-prompt.txt, src/utils/github.ts
  • 1 file requiring test updates: src/commands/plan.test.ts
  • New utility function needed in github.ts for permission detection
  • Cross-cutting change: Permission flag flows through 4 layers (detection -> template variables -> prompt conditionals -> allowedTools filtering)

Complete Technical Reference (click to expand for implementation details)

Problem Space Research

Problem Understanding

External contributors to open-source repos can create issues on public repos but cannot use the addSubIssue GraphQL mutation (requires triage+) or the createDependency REST API (requires write access). The current il plan workflow assumes write access and fails partway through, creating orphaned issues.

Architectural Context

il plan is a Claude-powered interactive planning session. It assembles a prompt template, configures MCP tools, and launches Claude with specific tool access. The architect agent uses MCP tools to create issues, link child issues, and set up dependencies. The fix requires gating which MCP tools are available and adjusting prompt instructions based on detected permissions.

Edge Cases

  • Fork scenarios: User has cloned a fork. gh repo view on the fork shows WRITE access, but the upstream repo (where issues live) may have READ-only. The permission check must target the correct repo (the one issues are being created on).
  • GitHub fine-grained PATs: Some tokens may have limited scopes. The viewerPermission field should still reflect effective permissions.
  • Non-GitHub providers: Linear and Jira have different permission models. The permission check must only apply when provider === 'github'.
  • Offline/API failure: If the permission check fails, the system should default to allowing full access (fail-open) to avoid breaking existing workflows.

Third-Party Research Findings

GitHub CLI viewerPermission field

Source: WebSearch
Key Findings:

  • gh repo view <repo> --json viewerPermission returns the authenticated user's permission level
  • Possible values: ADMIN, MAINTAIN, WRITE, TRIAGE, READ, NONE (empty string for no access)
  • This is a single lightweight API call that doesn't require special scopes
  • Works with both --repo owner/name flag and current-directory detection
    Reference: gh repo view docs

GitHub Sub-Issues Permission Requirements

Source: Issue #639 body + GitHub docs
Key Findings:

  • addSubIssue GraphQL mutation requires at least triage permissions
  • createDependency REST API requires write access
  • gh issue create works on public repos for any authenticated user
  • Creating a regular issue + commenting on it does NOT require write access
    Reference: GitHub Sub-Issues docs

Codebase Research Findings

Affected Area: Plan Command (src/commands/plan.ts)

Entry Point: src/commands/plan.ts:82 - PlanCommand.execute()
Key locations:

  • Lines 190-251: Issue decomposition mode detection and context fetching
  • Lines 267-352: MCP config generation (calls generateIssueManagementMcpConfig)
  • Lines 376-394: Template variables assembly -- new READ_ONLY_MODE variable needed here
  • Lines 401-428: allowedTools array definition -- write tools must be conditionally excluded
  • Lines 434-441: Claude options assembly
  • Lines 486-504: Initial message construction -- may need read-only mode context

Dependencies:

  • Uses: generateIssueManagementMcpConfig from src/utils/mcp.ts, getRepoInfo from src/utils/github.ts, PromptTemplateManager, IssueTrackerFactory, launchClaude
  • Used By: CLI command handler in src/cli.ts

Affected Area: GitHub Utilities (src/utils/github.ts)

Entry Point: New function needed
Key locations:

  • Lines 13-32: executeGhCommand() -- the base wrapper for gh CLI calls
  • Lines 424-439: getRepoInfo() -- existing pattern for gh repo view --json calls. New getRepoPermission() function should follow this pattern.

Similar Patterns Found:

  • getRepoInfo() at src/utils/github.ts:425-439 -- uses executeGhCommand with gh repo view --json owner,name. The new permission function should follow the same pattern with --json viewerPermission.
  • checkGhAuth() at src/utils/github.ts:35-104 -- returns structured auth status. Permission check can follow a similar return pattern.

Affected Area: Plan Prompt Template (templates/prompts/plan-prompt.txt)

Entry Point: templates/prompts/plan-prompt.txt:1 (full file, 514 lines)
Key locations:

  • Lines 273-295: "Issue Creation" section -- must be conditionally replaced in read-only mode
  • Lines 296-378: "Issue Decomposition Mode" and "Fresh Planning Mode" -- must have read-only alternatives
  • Lines 339-350: Creation order steps (create child issues, set up dependencies, post ADR comment) -- must be replaced with comment-based output in read-only mode
  • Lines 358-378: Fresh planning mode steps -- must be replaced with single-issue creation in read-only mode

Affected Area: Allowed Tools Filtering (src/commands/plan.ts:401-428)

Current write tools that must be removed in read-only mode:

  • mcp__issue_management__create_issue (line 403) -- keep for fresh planning mode (can create standalone issues on public repos)
  • mcp__issue_management__create_child_issue (line 404) -- REMOVE (requires sub-issue linking)
  • mcp__issue_management__create_dependency (line 410) -- REMOVE (requires write access)
  • mcp__issue_management__remove_dependency (line 412) -- REMOVE (requires write access)

Tools to KEEP in read-only mode:

  • All read tools (get_issue, get_child_issues, get_comment, get_dependencies)
  • mcp__issue_management__create_comment (line 407) -- commenting works without write access
  • mcp__issue_management__create_issue -- creating standalone issues works on public repos
  • All codebase exploration tools (Read, Glob, Grep, Task, WebFetch, WebSearch, Bash git)

Architectural Flow Analysis

Data Flow: hasWriteAccess permission flag

Entry Point: src/commands/plan.ts:~270 (after MCP config generation, before template variables) -- new permission check call

Flow Path:

  1. src/utils/github.ts (new function) -- getRepoPermission(repo?) calls gh repo view --json viewerPermission and returns permission level string
  2. src/commands/plan.ts:~270 -- calls getRepoPermission(), determines hasWriteAccess = ['ADMIN', 'MAINTAIN', 'WRITE'].includes(permission)
  3. src/commands/plan.ts:376-394 -- adds READ_ONLY_MODE: !hasWriteAccess to templateVariables
  4. src/commands/plan.ts:401-428 -- conditionally filters allowedTools based on hasWriteAccess
  5. templates/prompts/plan-prompt.txt -- Handlebars {{#if READ_ONLY_MODE}} conditionals change agent instructions

Affected Interfaces (ALL must be updated):

  • No TypeScript interface changes needed -- the permission flag is a local boolean in plan.ts that feeds into template variables (a Record<string, unknown>) and the allowedTools array (a string[]). Both are already flexible types.

Critical Implementation Note: This is a cross-cutting change affecting 4 layers but requires NO interface changes. The main risk is ensuring consistency between the tools removed from allowedTools and the prompt template instructions. If the prompt tells the agent to use create_child_issue but the tool isn't available, the agent will fail.

Affected Files

  • /Users/adam/Documents/Projects/iloom-cli/fix-issue-639__no-write-access/src/utils/github.ts:424-439 -- Add new getRepoPermission() function following getRepoInfo() pattern
  • /Users/adam/Documents/Projects/iloom-cli/fix-issue-639__no-write-access/src/commands/plan.ts:267-428 -- Add permission detection after MCP config generation, add READ_ONLY_MODE template variable, conditionally filter allowedTools
  • /Users/adam/Documents/Projects/iloom-cli/fix-issue-639__no-write-access/templates/prompts/plan-prompt.txt:273-378 -- Add {{#if READ_ONLY_MODE}} conditionals for the "Issue Creation" section with alternative instructions for comment-based output
  • /Users/adam/Documents/Projects/iloom-cli/fix-issue-639__no-write-access/src/commands/plan.test.ts -- Add tests for read-only mode behavior (tool filtering, template variables)

Integration Points

  • plan.ts calls getRepoInfo() indirectly via generateIssueManagementMcpConfig() at line 272 -- the new getRepoPermission() call should be placed after this succeeds (since it also calls gh repo view)
  • The allowedTools array at line 401 directly controls what the launched Claude session can do
  • The templateVariables at line 376 directly control what instructions the prompt template renders
  • The prompt template's "Issue Creation" section (line 273+) is the primary driver of agent behavior

Medium Severity Risks

  • Fork detection: If the user has forked a repo and is working from the fork, gh repo view (without --repo) will check the fork's permissions (which the user owns). The permission check should use the same repo that MCP tools target (the upstream repo if that's where issues are created). The getRepoInfo() call at src/utils/mcp.ts:53 already resolves the correct owner/name from git remote.
  • API rate limiting: Adding one more gh repo view call per il plan invocation is minimal overhead, but if the call is made in addition to the existing getRepoInfo() call inside generateIssueManagementMcpConfig, it's a second API call that could be combined into one gh repo view --json viewerPermission,owner,name.

@acreeger
Copy link
Copy Markdown
Collaborator Author

acreeger commented Feb 23, 2026

Implementation Plan for Issue #639

Summary

When users run il plan on a GitHub repository where they lack write/triage access, child issue creation succeeds but sub-issue linking and dependency creation fail, leaving orphaned issues. The fix gates il plan behavior on detected repo permissions: when the user lacks write access, the plan session produces a detailed breakdown comment on the parent issue (or a single standalone issue in fresh planning mode) instead of creating child issues and dependencies. This is GitHub-only; Linear and Jira are unaffected.

Questions and Key Decisions

Question Answer Rationale
What permission levels constitute "has write access"? ADMIN, MAINTAIN, WRITE = write access; TRIAGE, READ, empty = read-only addSubIssue requires triage+ but createDependency requires write. Using WRITE as the threshold covers both operations consistently.
Should create_issue be available in read-only mode? Yes Creating standalone issues works on public repos without write access. Needed for fresh planning mode (single issue with full plan).
Should create_comment be available in read-only mode? Yes Commenting on public issues works without write access. Needed for existing issue mode (post breakdown as comment).
Does fork detection require special handling? No Both getRepoInfo() (used by MCP config) and the new getRepoPermission() resolve the same repo from git remote. They are inherently consistent.
Should the permission check be a new function or extend getRepoInfo()? New standalone getRepoPermission() function Avoids changing the return type of getRepoInfo() which has many callers. Follows the same pattern.
Should non-GitHub providers be affected? No Linear and Jira handle permissions differently. Permission check only runs when provider === 'github'.
What happens if the permission API call fails? Fail-open (assume write access) Avoids breaking existing workflows. Log a debug warning.

High-Level Execution Phases

  1. Add getRepoPermission() to github.ts: New utility function calling gh repo view --json viewerPermission
  2. Add READ_ONLY_MODE to TemplateVariables interface: Update the strict TypeScript interface in PromptTemplateManager.ts
  3. Add permission detection to plan.ts: Call getRepoPermission() after MCP config generation, compute hasWriteAccess boolean, add READ_ONLY_MODE template variable, conditionally filter allowedTools, track telemetry
  4. Add read-only mode instructions to plan-prompt.txt: Handlebars conditionals wrapping the "Issue Creation" section with alternative instructions for comment-based output; update "Important Guidelines" and "Completion Message" sections
  5. Add tests to plan.test.ts and github.test.ts: Permission detection tests and read-only mode behavior tests
  6. Update documentation: Add read-only mode behavior to docs/iloom-commands.md

Quick Stats

  • 0 files for deletion
  • 5 files to modify (src/utils/github.ts, src/lib/PromptTemplateManager.ts, src/commands/plan.ts, templates/prompts/plan-prompt.txt, docs/iloom-commands.md)
  • 0 new files to create
  • 2 test files to modify (src/utils/github.test.ts, src/commands/plan.test.ts)
  • 1 telemetry type file to modify (src/types/telemetry.ts)
  • Dependencies: None
  • Estimated complexity: Medium

Complete Implementation Guide (click to expand for step-by-step details)

Automated Test Cases to Create

Test File: src/utils/github.test.ts (MODIFY)

Purpose: Test the new getRepoPermission() function

Click to expand test structure (20 lines)
describe('getRepoPermission', () => {
  it('should return permission level for current repo', async () => {
    // Mock execa to return { viewerPermission: 'WRITE' }
    // Call getRepoPermission()
    // Expect result to be 'WRITE'
    // Verify gh repo view --json viewerPermission was called
  })

  it('should return permission level for specified repo', async () => {
    // Mock execa to return { viewerPermission: 'READ' }
    // Call getRepoPermission('owner/repo')
    // Verify --repo owner/repo flag was passed
  })

  it('should propagate errors from gh CLI', async () => {
    // Mock execa to reject with error
    // Expect getRepoPermission() to throw
  })
})

Test File: src/commands/plan.test.ts (MODIFY)

Purpose: Test permission detection, read-only mode template variable, and tool filtering

Click to expand test structure (55 lines)
// Add import for github utils mock
import * as githubUtils from '../utils/github.js'
vi.mock('../utils/github.js')

describe('read-only mode (no write access)', () => {
  beforeEach(() => {
    // Mock getRepoPermission to return 'READ'
    vi.mocked(githubUtils.getRepoPermission).mockResolvedValue('READ')
  })

  it('should pass READ_ONLY_MODE: true when user lacks write access', async () => {
    await command.execute()
    expect(mockTemplateManager.getPrompt).toHaveBeenCalledWith(
      'plan',
      expect.objectContaining({ READ_ONLY_MODE: true })
    )
  })

  it('should exclude write tools from allowedTools when READ_ONLY_MODE', async () => {
    await command.execute()
    const launchCall = vi.mocked(claudeUtils.launchClaude).mock.calls[0]
    const allowedTools = launchCall[1].allowedTools as string[]
    expect(allowedTools).not.toContain('mcp__issue_management__create_child_issue')
    expect(allowedTools).not.toContain('mcp__issue_management__create_dependency')
    expect(allowedTools).not.toContain('mcp__issue_management__remove_dependency')
    // These should still be present
    expect(allowedTools).toContain('mcp__issue_management__create_issue')
    expect(allowedTools).toContain('mcp__issue_management__create_comment')
    expect(allowedTools).toContain('mcp__issue_management__get_issue')
  })

  it('should log warning about read-only mode', async () => {
    const { logger } = await import('../utils/logger.js')
    await command.execute()
    expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('read-only'))
  })
})

describe('write access mode (default)', () => {
  beforeEach(() => {
    vi.mocked(githubUtils.getRepoPermission).mockResolvedValue('WRITE')
  })

  it('should pass READ_ONLY_MODE: false when user has write access', async () => {
    await command.execute()
    expect(mockTemplateManager.getPrompt).toHaveBeenCalledWith(
      'plan',
      expect.objectContaining({ READ_ONLY_MODE: false })
    )
  })

  it('should include all tools when user has write access', async () => {
    await command.execute()
    const launchCall = vi.mocked(claudeUtils.launchClaude).mock.calls[0]
    const allowedTools = launchCall[1].allowedTools as string[]
    expect(allowedTools).toContain('mcp__issue_management__create_child_issue')
    expect(allowedTools).toContain('mcp__issue_management__create_dependency')
  })
})

describe('permission check failure (fail-open)', () => {
  it('should default to write access if permission check fails', async () => {
    vi.mocked(githubUtils.getRepoPermission).mockRejectedValue(new Error('API error'))
    await command.execute()
    expect(mockTemplateManager.getPrompt).toHaveBeenCalledWith(
      'plan',
      expect.objectContaining({ READ_ONLY_MODE: false })
    )
  })
})

describe('non-GitHub provider', () => {
  it('should skip permission check for Linear provider', async () => {
    vi.mocked(IssueTrackerFactory.getProviderName).mockReturnValue('linear')
    await command.execute()
    expect(githubUtils.getRepoPermission).not.toHaveBeenCalled()
    expect(mockTemplateManager.getPrompt).toHaveBeenCalledWith(
      'plan',
      expect.objectContaining({ READ_ONLY_MODE: false })
    )
  })
})

Files to Modify

1. src/utils/github.ts:425-439

Change: Add new getRepoPermission() function after the existing getRepoInfo() function (after line 439).

The function follows the same pattern as getRepoInfo(): calls gh repo view --json viewerPermission and returns the permission level string. Accepts an optional repo parameter for targeting a specific repo.

// Pseudocode - add after line 439
export async function getRepoPermission(repo?: string): Promise<string> {
  // logger.debug('Fetching repository permission')
  // const args = ['repo', 'view', '--json', 'viewerPermission']
  // if (repo) args.splice(2, 0, '--repo', repo)
  // const result = await executeGhCommand<{ viewerPermission: string }>(args)
  // return result.viewerPermission
}

2. src/lib/PromptTemplateManager.ts:116

Change: Add READ_ONLY_MODE?: boolean to the TemplateVariables interface.

The TemplateVariables interface is a strict TypeScript interface with no index signature (lines 16-117). Without this addition, passing READ_ONLY_MODE as a template variable from plan.ts will cause a TypeScript compilation error. Insert after line 116 (before the closing }), alongside the other planning mode variables:

// Add at line 116, before the closing brace
READ_ONLY_MODE?: boolean  // True when user lacks write access to GitHub repo (plan command only)

3. src/types/telemetry.ts:42-45 and 103

Change: Add read_only_mode property to EpicPlannedProperties and add a new PlanReadOnlyProperties event type.

At line 42-45, add read_only_mode: boolean to the existing EpicPlannedProperties interface:

export interface EpicPlannedProperties {
  child_count: number
  tracker: string
  read_only_mode: boolean  // True when user lacked write access
}

This is the most minimal approach -- the epic.planned event already fires after plan sessions, so adding a boolean flag avoids a new event type entirely. The existing tracking at src/commands/plan.ts:516 already fires epic.planned for decomposition sessions; the read_only_mode property will be passed through from the hasWriteAccess variable computed earlier.

4. src/commands/plan.ts:190 (import), 352 (permission check), 376-393 (template variable), 401-428 (tool filtering), 516 (telemetry)

Change A (line ~1): Add import for getRepoPermission from ../utils/github.js.

Change B (lines 267-352, after MCP config): Add permission detection block after the MCP config generation succeeds (after line 352, before line 354). The permission check:

  • Only runs when provider === 'github'
  • Calls getRepoPermission() and checks if result is in ['ADMIN', 'MAINTAIN', 'WRITE']
  • Catches errors and defaults to true (fail-open)
  • Logs a logger.warn() message when read-only mode is detected
// Pseudocode - insert after line 352 (after mcpConfig is generated)
let hasWriteAccess = true // default for non-GitHub or if check fails
if (provider === 'github') {
  try {
    const permission = await getRepoPermission()
    hasWriteAccess = ['ADMIN', 'MAINTAIN', 'WRITE'].includes(permission)
    if (!hasWriteAccess) {
      logger.warn('You do not have write access to this repository. Running in read-only mode — the plan will be posted as a comment instead of creating child issues.')
    }
  } catch (error) {
    logger.debug('Permission check failed, defaulting to write access', { error message })
  }
}

Change C (lines 376-393, template variables): Add READ_ONLY_MODE: !hasWriteAccess to the templateVariables object.

Change D (lines 401-428, allowedTools): Conditionally filter the allowedTools array based on hasWriteAccess. When !hasWriteAccess, remove:

  • mcp__issue_management__create_child_issue (line 404)
  • mcp__issue_management__create_dependency (line 410)
  • mcp__issue_management__remove_dependency (line 412)

Keep all other tools including create_issue and create_comment.

// Pseudocode - modify the allowedTools definition
const allowedTools = [
  'mcp__issue_management__create_issue',
  ...(hasWriteAccess ? ['mcp__issue_management__create_child_issue'] : []),
  'mcp__issue_management__get_issue',
  'mcp__issue_management__get_child_issues',
  'mcp__issue_management__get_comment',
  'mcp__issue_management__create_comment',
  ...(hasWriteAccess ? [
    'mcp__issue_management__create_dependency',
  ] : []),
  'mcp__issue_management__get_dependencies',
  ...(hasWriteAccess ? [
    'mcp__issue_management__remove_dependency',
  ] : []),
  // ... all read-only tools unchanged
]

Change E (line 516, telemetry): Pass read_only_mode: !hasWriteAccess to the existing epic.planned telemetry event:

// Modify existing tracking call at line 516
TelemetryService.getInstance().track('epic.planned', {
  child_count: children.length,
  tracker: provider,
  read_only_mode: !hasWriteAccess,  // Add this property
})

5. templates/prompts/plan-prompt.txt:273-378 (Issue Creation section), 480-482 (Important Guidelines), 488-514 (Session Flow + Completion)

Change A (lines 273-468, Issue Creation section): Wrap the "Issue Creation" section in a {{#unless READ_ONLY_MODE}}...{{/unless}} block and add a {{#if READ_ONLY_MODE}} alternative block with read-only mode instructions.

The read-only mode instructions replace the entire "Issue Creation" section (lines 273-468) with:

  • A notice explaining the user lacks write access and why the behavior is different
  • Modified tool listing (only create_issue, create_comment, get_issue, get_dependencies)
  • For existing issue mode: Instructions to post a single comprehensive comment on the parent issue containing: the full breakdown, suggested child issues as a markdown checklist, a dependency diagram (Mermaid/ASCII), and ADR
  • For fresh planning mode: Instructions to create a single issue containing the full breakdown plan in its body

Change B (line 480-484, Important Guidelines): Line 482 currently says "Create issues as the primary output artifact" which is misleading when READ_ONLY_MODE is active (the user cannot create child issues). Wrap this section in a conditional:

{{#if READ_ONLY_MODE}}
**Do:**
- Use the conversation to refine understanding iteratively
- Post the plan as a comment or single issue (your primary output artifact)
- Ask for clarification rather than making assumptions
- Keep the user informed about your reasoning
{{else}}
**Do:**
- Use the conversation to refine understanding iteratively
- Create issues as the primary output artifact
- Ask for clarification rather than making assumptions
- Keep the user informed about your reasoning
{{/if}}

Change C (lines 488-496, Session Flow Summary): Add {{#if READ_ONLY_MODE}} alternative that replaces steps 4-7 (Decompose/Confirm/Create/Summarize) with read-only equivalents (Draft plan/Confirm/Post comment or create single issue/Summarize next steps for maintainer).

Change D (lines 500-514, Completion Message): Add {{#if READ_ONLY_MODE}} alternative that directs users to the comment/issue and suggests a maintainer with write access can create the child issues from the breakdown.

Click to expand read-only mode prompt template pseudocode (60 lines)
{{#if READ_ONLY_MODE}}
## Plan Output (Read-Only Mode)

**You do not have write access to this repository.** You cannot create child issues or set up dependencies. Instead, you will produce the complete plan as a single detailed artifact.

**Available Tools:**
- `mcp__issue_management__create_issue`: Create a standalone issue (for fresh planning)
- `mcp__issue_management__create_comment`: Post comments on existing issues
- `mcp__issue_management__get_issue`: Fetch issue details for context
- `mcp__issue_management__get_child_issues`: Check for existing child issues
- `mcp__issue_management__get_dependencies`: Query existing dependencies

{{#if EXISTING_ISSUE_MODE}}
**Issue Decomposition Mode (Read-Only):**

You are creating a plan for an existing issue:
- **Parent Issue:** #{{PARENT_ISSUE_NUMBER}}
- **Title:** {{PARENT_ISSUE_TITLE}}
- **Body:**
{{PARENT_ISSUE_BODY}}

[Include same dependency analysis with subagents section as normal mode]

**Output: Post a single comprehensive comment on the parent issue.**

Use `create_comment` with `number: "{{PARENT_ISSUE_NUMBER}}"`, `type: "issue"` to post a comment containing:

1. **Executive summary** of the decomposition plan
2. **Suggested child issues** as a markdown checklist table:
   | # | Title | Description | Dependencies | Complexity |
   |---|-------|-------------|--------------|------------|
   | 1 | [Title] | [One-sentence description] | None | Simple |
   | 2 | [Title] | [One-sentence description] | #1 | Simple |
3. **Dependency diagram** (Mermaid for GitHub)
4. **Architectural Decision Record**: Design rationale, key decisions, trade-offs
5. **Suggested execution order** for whoever will create the actual issues

**Next steps message:** "This plan has been posted as a comment on issue #{{PARENT_ISSUE_NUMBER}}. A maintainer with write access can create the child issues and dependencies from this breakdown."

{{else}}
**Fresh Planning Mode (Read-Only):**

[Same planning process phases 1-3 as normal mode]

**Output: Create a single issue with the full plan.**

Use `create_issue` to create one issue containing:
- Title: "Plan: [Feature Name]"
- Body: The full decomposition plan (same format as the comment above)

**Next steps message:** "This plan has been created as issue #X. A maintainer with write access can decompose it into child issues and dependencies."

{{/if}}
{{else}}
[... existing Issue Creation section unchanged ...]
{{/if}}

6. docs/iloom-commands.md:1149-1313 (il plan section)

Change: Add a "Read-Only Mode" subsection after the existing "YOLO Mode" section (around line 1248). Document:

  • When it activates (GitHub repos where user lacks write access)
  • What it does differently (posts plan as comment instead of creating child issues)
  • How to verify your permission level (gh repo view --json viewerPermission)

7. src/utils/github.test.ts (MODIFY)

Change: Add getRepoPermission to the import list (line 17) and add a new describe('getRepoPermission', ...) test block after the existing getRepoInfo tests (after line 852). See test structure above.

8. src/commands/plan.test.ts (MODIFY)

Change: Add import for ../utils/github.js and vi.mock('../utils/github.js'). Add new describe blocks for read-only mode behavior. See test structure above. The default mock for getRepoPermission should return 'WRITE' (in the beforeEach at the top level) to avoid breaking existing tests.

Detailed Execution Order

Phase 1 (parallel): Core Implementation

Step 1a: Add getRepoPermission() to github.ts + tests

Files: src/utils/github.ts, src/utils/github.test.ts
Contract: getRepoPermission(repo?: string): Promise<string> -- returns viewerPermission string ('ADMIN', 'MAINTAIN', 'WRITE', 'TRIAGE', 'READ', or empty string)

  1. Add getRepoPermission() function at src/utils/github.ts:440 (after getRepoInfo()) -> Verify: function exists and follows getRepoInfo() pattern
  2. Add import of getRepoPermission to src/utils/github.test.ts:17
  3. Add test block after line 852 -> Verify: pnpm test src/utils/github.test.ts passes

Step 1b: Add read-only mode prompt instructions to plan-prompt.txt

Files: templates/prompts/plan-prompt.txt

  1. At line 273, wrap the entire "Issue Creation" section (lines 273-468) in {{#unless READ_ONLY_MODE}}...{{/unless}}
  2. Before the {{#unless}}, add a {{#if READ_ONLY_MODE}} block with the read-only instructions (see pseudocode above)
  3. At line 480-484, wrap the "Important Guidelines" **Do:** list in {{#if READ_ONLY_MODE}}...{{else}}...{{/if}} to change "Create issues as the primary output artifact" to "Post the plan as a comment or single issue (your primary output artifact)" in read-only mode
  4. Update "Session Flow Summary" (line 488-496) with {{#if READ_ONLY_MODE}} alternative
  5. Update "Completion Message" (line 500-514) with {{#if READ_ONLY_MODE}} alternative
    -> Verify: Template renders correctly with both READ_ONLY_MODE: true and READ_ONLY_MODE: false

Step 1c: Update documentation + TemplateVariables interface + telemetry types

Files: docs/iloom-commands.md, src/lib/PromptTemplateManager.ts, src/types/telemetry.ts

  1. Add READ_ONLY_MODE?: boolean to TemplateVariables interface at src/lib/PromptTemplateManager.ts:116 (before closing brace) -> Verify: pnpm build will accept READ_ONLY_MODE in template variable objects
  2. Add read_only_mode: boolean to EpicPlannedProperties at src/types/telemetry.ts:44 -> Verify: TypeScript accepts the new property in track('epic.planned', ...) calls
  3. Add "Read-Only Mode" subsection after the YOLO Mode section (~line 1248 of docs/iloom-commands.md) with 10-15 lines of documentation -> Verify: Documentation is clear and consistent

Phase 2 (sequential, depends on Phase 1): Permission detection + tool filtering in plan.ts + tests

Step 2: Wire permission check into plan.ts and add tests

Files: src/commands/plan.ts, src/commands/plan.test.ts
Depends on: Steps 1a (needs getRepoPermission() to exist), 1c (needs TemplateVariables and telemetry types updated)

  1. Add import for getRepoPermission at src/commands/plan.ts:1 (import section)
  2. Add permission detection block after line 352 (after MCP config generation)
  3. Add READ_ONLY_MODE: !hasWriteAccess to templateVariables at line 376-393
  4. Modify allowedTools array at lines 401-428 to conditionally include write tools
  5. Pass read_only_mode: !hasWriteAccess to existing epic.planned telemetry call at line 516
  6. Add vi.mock('../utils/github.js') and default mock to src/commands/plan.test.ts
  7. Add read-only mode test blocks to src/commands/plan.test.ts
  8. Run pnpm build to verify TypeScript compiles
    -> Verify: pnpm test src/commands/plan.test.ts passes; pnpm build succeeds

Execution Plan

  1. Run Steps 1a, 1b, 1c in parallel (github.ts + tests, plan-prompt.txt, docs/types/interface -- all different files, shared contract: getRepoPermission(repo?: string): Promise<string>)
  2. Run Step 2 (plan.ts + plan.test.ts -- depends on Phase 1 for the function, interface, and telemetry type to exist)

Dependencies and Configuration

None

@acreeger
Copy link
Copy Markdown
Collaborator Author

acreeger commented Feb 23, 2026

Implementation Complete

Summary

Added read-only mode to il plan that automatically activates when the user lacks write/triage permissions on the target GitHub repository. Instead of failing when trying to create child issues and dependencies, plan mode now detects permissions upfront and produces a detailed issue comment with the suggested breakdown.

Changes Made

  • src/utils/github.ts: Added getRepoPermission() function
  • src/commands/plan.ts: Permission detection, tool gating, template variable, telemetry
  • templates/prompts/plan-prompt.txt: Read-only mode Handlebars conditionals for all relevant sections
  • src/lib/PromptTemplateManager.ts: Added READ_ONLY_MODE to TemplateVariables interface
  • src/types/telemetry.ts: Added read_only_mode to EpicPlannedProperties
  • docs/iloom-commands.md: Documented Read-Only Mode

Validation Results

  • ✅ Tests: 91 passed (46 github.test.ts + 45 plan.test.ts)
  • ✅ Typecheck: Passed (pnpm compile clean)
  • ✅ Build: Passed (pnpm build clean)
  • ⚠️ 5 pre-existing failures in ClaudeService.test.ts (due to ILOOM_VSCODE env var, unrelated)

Detailed Changes by File (click to expand)

src/utils/github.ts

Changes: Added getRepoPermission() utility function

  • Calls gh repo view --json viewerPermission to get user's permission level
  • Accepts optional repo parameter for specifying target repository
  • Returns permission string (ADMIN, MAINTAIN, WRITE, TRIAGE, READ)

src/utils/github.test.ts

Changes: Added 5 tests for getRepoPermission()

  • Tests current repo and specified repo permission checks
  • Tests ADMIN and TRIAGE permission levels
  • Tests error propagation

src/commands/plan.ts

Changes: Wired permission detection into plan command

  • Imports and calls getRepoPermission() after MCP config generation
  • Determines write access (ADMIN/MAINTAIN/WRITE = true, others = false)
  • Fails open on error (defaults to write access)
  • Sets READ_ONLY_MODE template variable
  • Conditionally removes create_child_issue, create_dependency, remove_dependency from allowed tools
  • Passes read_only_mode to existing telemetry event

src/commands/plan.test.ts

Changes: Added 12 new tests for permission-based behavior

  • Read-only mode activation for READ/TRIAGE permissions
  • Write access mode for WRITE/ADMIN/MAINTAIN permissions
  • Fail-open behavior on permission check errors
  • Non-GitHub provider skips permission check

templates/prompts/plan-prompt.txt

Changes: Added read-only mode Handlebars conditionals

  • Wrapped Issue Creation section (lines 273-468) in {{#unless READ_ONLY_MODE}}
  • Added {{#if READ_ONLY_MODE}} alternative with comment-based output instructions
  • Updated Important Guidelines with conditional wording
  • Updated Session Flow Summary with read-only alternatives
  • Updated Completion Message with read-only alternatives

src/lib/PromptTemplateManager.ts

Changes: Added READ_ONLY_MODE?: boolean to TemplateVariables interface

src/types/telemetry.ts

Changes: Added read_only_mode: boolean to EpicPlannedProperties interface

docs/iloom-commands.md

Changes: Added "Read-Only Mode (No Write Access)" documentation subsection

When a user lacks write/triage permissions on a GitHub repository,
`il plan` now automatically detects this and switches to read-only
mode. Instead of failing when trying to create child issues and
dependencies, plan mode produces a detailed issue comment with the
suggested breakdown that a maintainer can act on.

Changes:
- Add getRepoPermission() utility to check GitHub repo permissions
- Gate plan command behavior based on detected permission level
- Add READ_ONLY_MODE Handlebars conditionals to plan prompt template
- Filter write-only MCP tools (create_child_issue, create_dependency,
  remove_dependency) when in read-only mode
- Track read_only_mode in epic.planned telemetry event
- Document Read-Only Mode in iloom-commands.md

Fixes #639
@acreeger acreeger force-pushed the fix/issue-639__no-write-access branch from aceee61 to 4738494 Compare February 23, 2026 14:18
@acreeger
Copy link
Copy Markdown
Collaborator Author

acreeger commented Feb 23, 2026

Analysis: Permission check uses wrong repo in fork workflows

  • Read plan.ts permission check and MCP config generation
  • Read mcp.ts - generateIssueManagementMcpConfig() return value
  • Read IssueManagementProviderFactory.ts - repo resolution
  • Read getRepoPermission() and getRepoInfo() in github utils
  • Trace how gh repo view resolves in a fork context vs MCP env vars
  • Trace how hasWriteAccess flows into the plan prompt/template
  • Document findings

ETA: ~2 minutes


Executive Summary

The il plan permission check at plan.ts:363 calls getRepoPermission() with no arguments, which runs gh repo view --json viewerPermission against the current directory's git remote. In a fork clone, this checks permission on the fork (where the user has ADMIN), not the upstream repo where MCP issue management tools actually operate. This means read-only mode never activates for external contributors, and they hit permission errors when addSubIssue/createDependency are attempted on the upstream repo.

Additionally, getRepoPermission() has a secondary bug: when a repo argument IS provided, it incorrectly uses --repo flag with gh repo view, but gh repo view takes the repo as a positional argument, not a --repo flag.

Question Answer
Where does the target repo (upstream) become available? It does NOT currently. Both getRepoInfo() and getRepoPermission() use gh repo view which returns fork metadata in a fork clone. The upstream must be explicitly detected.
Can we extract REPO_OWNER/REPO_NAME from the MCP config? Technically yes (it's buried in mcpConfig[0].mcpServers.issue_management.env), but this is fragile. Better to have generateIssueManagementMcpConfig return the resolved repo, or resolve it independently.
Does gh CLI have fork-aware behavior for issues? Yes -- gh issue view N in a fork clone resolves to the parent repo's issues. But gh repo view returns the fork's own metadata. This asymmetry is the root cause.

HIGH/CRITICAL Risks

  • getRepoPermission() broken when repo arg provided: The function uses --repo flag with gh repo view, but this command expects repo as a positional argument. Any fix that passes repo to getRepoPermission() will need to also fix the arg construction.

Impact Summary

  • 2 files need modification: src/commands/plan.ts and src/utils/github.ts
  • Possibly src/utils/mcp.ts if the MCP config function is changed to return the resolved repo
  • Key decision: How to detect the upstream repo in a fork context

Complete Technical Reference (click to expand for implementation details)

Problem Space Research

Problem Understanding

When a contributor forks an open-source repo and runs il plan #42, the permission check should detect they lack write access to the upstream repo and activate read-only mode. Instead, it checks their fork (where they're ADMIN) and proceeds with full write mode, causing sub-issue/dependency creation to fail on the upstream repo.

Architectural Context

The plan command has two separate repo resolution paths that should agree but don't in fork scenarios:

  1. MCP config generation (mcp.ts:53): Calls getRepoInfo() which runs gh repo view --json owner,name -- returns the fork
  2. Permission check (plan.ts:363): Calls getRepoPermission() which runs gh repo view --json viewerPermission -- checks the fork

Both use gh repo view without specifying a repo, so both resolve to whatever the default git remote points to (the fork).

Meanwhile, gh issue view N in a fork clone resolves to the parent repo's issues (GitHub CLI's fork-aware behavior). This means MCP tools operating through gh issue create, gh issue view etc. may actually target the upstream, while the MCP config's REPO_OWNER/REPO_NAME env vars point to the fork.

Edge Cases Identified

  • User cloned upstream directly (not a fork): gh repo view returns the upstream, permission check works correctly
  • Fork with no upstream remote: gh repo view returns the fork, behavior same as bug case
  • Non-GitHub providers: hasWriteAccess defaults to true for non-GitHub, which is correct (Linear/Jira have different auth)

Codebase Research Findings

Affected Area: Permission check in plan command

Entry Point: src/commands/plan.ts:359-373 - Permission check block

The flow:

  1. plan.ts:273 calls generateIssueManagementMcpConfig(undefined, undefined, provider, settings) -- repo param is undefined
  2. mcp.ts:53 calls getRepoInfo() since no repo was provided
  3. github.ts:428-433 runs gh repo view --json owner,name -- returns fork owner/name in fork context
  4. mcp.ts:62-68 sets REPO_OWNER and REPO_NAME env vars to fork values
  5. plan.ts:363 calls getRepoPermission() with no args
  6. github.ts:449 runs gh repo view --json viewerPermission -- checks fork permission (ADMIN)
  7. plan.ts:364 sees ADMIN, sets hasWriteAccess = true
  8. plan.ts:409 sets READ_ONLY_MODE: false in template
  9. plan.ts:422,428,430 includes create_child_issue, create_dependency, remove_dependency in allowed tools

How hasWriteAccess flows downstream

hasWriteAccess is used in three ways:

  1. Template variable (plan.ts:409): READ_ONLY_MODE: !hasWriteAccess -- controls prompt behavior
  2. Allowed tools list (plan.ts:422,428,430): Conditionally includes create_child_issue, create_dependency, remove_dependency
  3. Telemetry (plan.ts:537): read_only_mode: !hasWriteAccess

Bug in getRepoPermission() with repo argument

src/utils/github.ts:449-452:

const args = ['repo', 'view', '--json', 'viewerPermission']
if (repo) {
    args.splice(2, 0, '--repo', repo)
}

This produces gh repo view --repo owner/name --json viewerPermission. But gh repo view takes repo as a positional argument: gh repo view owner/name --json viewerPermission. The --repo flag is used by gh issue/gh pr subcommands, not gh repo view.

The correct construction should be:

const args = repo
    ? ['repo', 'view', repo, '--json', 'viewerPermission']
    : ['repo', 'view', '--json', 'viewerPermission']

generateIssueManagementMcpConfig does not expose resolved repo

src/utils/mcp.ts:19-25: The function signature returns Promise<Record<string, unknown>[]>. The resolved owner/name is computed internally (lines 42-56) and embedded in the env vars of the config object, but never returned to the caller.

To make the resolved repo available to plan.ts, either:

  • Change return type to include the resolved repo (e.g., { configs: Record<string, unknown>[], repo?: { owner: string, name: string } })
  • Have plan.ts call getRepoInfo() separately (duplicates the gh repo view call)
  • Extract from config (fragile: mcpConfig[0].mcpServers.issue_management.env.REPO_OWNER)

How to detect upstream repo in a fork

gh repo view --json parent returns the parent repo info if the current repo is a fork. Example response:

{ "parent": { "owner": { "login": "org" }, "name": "repo" } }

If parent is null/missing, the repo is not a fork.

Alternatively, gh api repos/:owner/:repo --jq '.parent.full_name' returns the upstream owner/repo string.

Existing fork detection pattern

The codebase already has fork awareness in src/lib/LoomManager.ts:701:

const isForkPR = input.type === 'pr' && issueData && 'isFork' in issueData && (issueData as PullRequest).isFork === true

And src/lib/GitHubService.ts:429 maps isCrossRepository to isFork. But this is for PR-based workflows, not for il plan which operates at the repo level.

Affected Files

  • src/commands/plan.ts:359-373 - Permission check block that needs the upstream repo
  • src/utils/github.ts:446-456 - getRepoPermission() function with broken --repo flag handling
  • src/utils/github.ts:425-439 - getRepoInfo() function that could be enhanced to detect fork parent
  • src/utils/mcp.ts:19-77 - generateIssueManagementMcpConfig() that resolves repo but doesn't expose it

Architectural Flow Analysis

Data Flow: hasWriteAccess

Entry Point: src/commands/plan.ts:360 - let hasWriteAccess = true

Flow Path:

  1. src/commands/plan.ts:363 - Calls getRepoPermission() (no args = checks fork)
  2. src/utils/github.ts:449 - Runs gh repo view --json viewerPermission (resolves to fork)
  3. src/commands/plan.ts:364 - Compares against ['ADMIN', 'MAINTAIN', 'WRITE']
  4. src/commands/plan.ts:409 - Sets READ_ONLY_MODE: !hasWriteAccess template variable
  5. src/commands/plan.ts:422,428,430 - Controls which MCP tools are in allowedTools
  6. src/commands/plan.ts:537 - Tracked in telemetry

Data Flow: REPO_OWNER/REPO_NAME (MCP target repo)

Entry Point: src/commands/plan.ts:273 - Calls generateIssueManagementMcpConfig()

Flow Path:

  1. src/utils/mcp.ts:53 - getRepoInfo() auto-detects from git remote (returns fork)
  2. src/utils/mcp.ts:64-65 - Sets REPO_OWNER/REPO_NAME env vars in MCP config
  3. src/mcp/issue-management-server.ts:52 - MCP server reads REPO_OWNER/REPO_NAME on startup
  4. MCP server passes them to provider operations (issue creation, sub-issue linking, etc.)

Critical note: The MCP tools use gh issue view, gh issue create etc. which in a fork clone may resolve to the upstream repo (gh CLI fork-aware behavior), creating a mismatch with the REPO_OWNER/REPO_NAME values from getRepoInfo().

Medium Severity Risks

  • MCP REPO_OWNER/REPO_NAME may also be wrong in fork context: The same getRepoInfo() call that feeds the permission check also feeds the MCP config env vars, meaning the MCP server may have the fork's owner/name while gh CLI commands resolve to upstream -- this could cause subtle issues in API path construction.

- Fix getRepoPermission() to use positional arg instead of broken --repo flag
- Have generateIssueManagementMcpConfig() return the resolved repo
- Pass resolved repo to permission check so it targets upstream, not fork
- Fix IssueEnhancementService test mock not re-seeded after mockReset
- Clarify read-only prompt: epic body is overview, breakdown goes in comment
- Strengthen prompt language to prevent creating child issues as standalone

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

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

il plan cannot create sub-issues on repositories where user lacks write/triage access

1 participant