# Investigation: Interactive PRD Workflow — Three Related Bugs

**Issues**: #1001, #1002, #1003
**URLs**:
- https://github.com/coleam00/Archon/issues/1001
- https://github.com/coleam00/Archon/issues/1002
- https://github.com/coleam00/Archon/issues/1003
**Type**: BUG (all three)
**Investigated**: 2026-04-09

### Assessment

| Metric     | Value  | Reasoning |
|------------|--------|-----------|
| Severity   | HIGH   | The interactive-prd workflow is completely non-functional: user answers are lost (#1001), output file can't be written (#1002), and CLI creates fragmented conversations (#1003). No workaround exists. |
| Complexity | MEDIUM | 3 files affected, but changes are isolated — YAML config fix, one return type addition, one conversation ID lookup. No architectural changes. |
| Confidence | HIGH   | All three root causes are confirmed with code evidence. The fixes are straightforward and well-understood. |

---

## Problem Statement

The `archon-interactive-prd` workflow has three bugs that together make it completely non-functional:
1. Approval gates don't capture user responses, so all downstream nodes receive empty strings
2. The generate node writes to `.claude/` which the Claude SDK blocks
3. CLI `workflow approve` creates new conversations instead of reusing the original, fragmenting the Web UI

---

## Analysis

### Bug 1: Missing `capture_response: true` (#1001)

**Evidence Chain:**

WHY: Downstream nodes receive empty `$foundation-gate.output`, `$deepdive-gate.output`, `$scope-gate.output`
↓ BECAUSE: `approveWorkflow()` stores empty string as node output
Evidence: `packages/core/src/operations/workflow-operations.ts:176`
```typescript
const nodeOutput = approval.captureResponse === true ? approvalComment : '';
```
↓ BECAUSE: `captureResponse` is `undefined` (falsy) in the pause metadata
Evidence: `packages/workflows/src/dag-executor.ts:2337`
```typescript
captureResponse: node.approval.capture_response,  // undefined when not set in YAML
```
↓ ROOT CAUSE: All three approval gates in the YAML lack `capture_response: true`
Evidence: `.archon/workflows/defaults/archon-interactive-prd.yaml:52-55, 106-109, 172-175`

**Git History:**
- **Introduced**: `9de8cf2c` (Mar 30) — workflow created before `capture_response` existed
- **Feature added**: #936 (Apr 2) — added `capture_response` support
- **Never updated**: The workflow was not retrofitted after #936

### Bug 2: Generate node writes to blocked `.claude/` path (#1002)

**Evidence Chain:**

WHY: `generate` node fails or produces truncated output
↓ BECAUSE: Claude SDK blocks writes to `.claude/` directory (hardcoded safety boundary)
↓ ROOT CAUSE: The prompt instructs AI to write to `.claude/PRPs/prds/`
Evidence: `.archon/workflows/defaults/archon-interactive-prd.yaml:191-196`
```yaml
Generate a complete PRD file at `.claude/PRPs/prds/{kebab-case-name}.prd.md`.

First create the directory:
```bash
mkdir -p .claude/PRPs/prds
```
```

The `validate` node also reads from the same blocked path at line 246-249.

`$ARTIFACTS_DIR` is pre-created by the executor at `packages/workflows/src/executor.ts:468` and is always writable (resolves to `~/.archon/workspaces/{owner}/{repo}/artifacts/runs/{runId}/`).

### Bug 3: CLI approve creates new conversations (#1003)

**Evidence Chain:**

WHY: Each approval creates a separate conversation in the Web UI
↓ BECAUSE: `workflowRunCommand` always generates a new conversation ID
Evidence: `packages/cli/src/commands/workflow.ts:272`
```typescript
const conversationId = generateConversationId();  // always new cli-{ts}-{rand}
```
↓ BECAUSE: `workflowApproveCommand` calls `workflowRunCommand` without passing the original conversation ID
Evidence: `packages/cli/src/commands/workflow.ts:865`
```typescript
await workflowRunCommand(result.workingPath, result.workflowName, result.userMessage ?? '', {
  resume: true,
  codebaseId: result.codebaseId ?? undefined,
});
```
↓ ROOT CAUSE: `ApprovalOperationResult` doesn't include `conversationId`, so the approve command can't pass it through
Evidence: `packages/core/src/operations/workflow-operations.ts:32-38`
```typescript
export interface ApprovalOperationResult {
  workflowName: string;
  workingPath: string | null;
  userMessage: string | null;
  codebaseId: string | null;
  type: 'interactive_loop' | 'approval_gate';
}
```

The workflow run DOES store `conversation_id` (DB UUID) — `packages/core/src/db/workflows.ts:52`. And `getConversationById(id)` at `packages/core/src/db/conversations.ts:19` can retrieve the `platform_conversation_id` from the DB UUID.

### Affected Files

| File | Lines | Action | Description |
|------|-------|--------|-------------|
| `.archon/workflows/defaults/archon-interactive-prd.yaml` | 52-55, 106-109, 172-175, 191-196, 246-249 | UPDATE | Add `capture_response: true` to gates; change `.claude/PRPs/prds/` to `$ARTIFACTS_DIR/prds/` |
| `packages/core/src/operations/workflow-operations.ts` | 32-38, 202-208 | UPDATE | Add `conversationId` to `ApprovalOperationResult` and return it from `approveWorkflow` |
| `packages/cli/src/commands/workflow.ts` | 60-69, 272, 849-880 | UPDATE | Add `conversationId` to `WorkflowRunOptions`, accept it, pass it through from approve |

### Integration Points

- `packages/core/src/operations/workflow-operations.ts:202` returns result consumed by CLI approve command
- `packages/cli/src/commands/workflow.ts:865` calls `workflowRunCommand` with the result
- `packages/cli/src/commands/workflow.ts:272` generates conversation ID (needs to accept override)
- `packages/core/src/db/conversations.ts:19` `getConversationById()` — needed to look up platform ID from DB UUID
- `packages/workflows/src/dag-executor.ts:2337` reads `capture_response` from node YAML
- `packages/core/src/operations/workflow-operations.ts:176` uses `captureResponse` to decide output
- `RejectionOperationResult` at line 40-49 should also get `conversationId` for consistency (same pattern)

---

## Implementation Plan

### Step 1: Add `capture_response: true` to all three approval gates

**File**: `.archon/workflows/defaults/archon-interactive-prd.yaml`
**Action**: UPDATE

**Current code (line 52-55):**
```yaml
  - id: foundation-gate
    approval:
      message: "Answer the foundation questions above. Your answers will guide the research phase."
    depends_on: [initiate]
```

**Required change:**
```yaml
  - id: foundation-gate
    approval:
      message: "Answer the foundation questions above. Your answers will guide the research phase."
      capture_response: true
    depends_on: [initiate]
```

**Same change for `deepdive-gate` (line 106-109) and `scope-gate` (line 172-175).**

---

### Step 2: Change output path from `.claude/PRPs/prds/` to `$ARTIFACTS_DIR/prds/`

**File**: `.archon/workflows/defaults/archon-interactive-prd.yaml`
**Action**: UPDATE

**In `generate` node (line 191):**

Change:
```
Generate a complete PRD file at `.claude/PRPs/prds/{kebab-case-name}.prd.md`.

First create the directory:
```bash
mkdir -p .claude/PRPs/prds
```
```

To:
```
Generate a complete PRD file at `$ARTIFACTS_DIR/prds/{kebab-case-name}.prd.md`.

First create the directory:
```bash
mkdir -p $ARTIFACTS_DIR/prds
```
```

**In `validate` node (line 246-249):**

Change:
```
Find the PRD file — check `.claude/PRPs/prds/` for the most recently created `.prd.md` file:
```bash
ls -t .claude/PRPs/prds/*.prd.md | head -1
```
```

To:
```
Find the PRD file — check `$ARTIFACTS_DIR/prds/` for the most recently created `.prd.md` file:
```bash
ls -t $ARTIFACTS_DIR/prds/*.prd.md | head -1
```
```

---

### Step 3: Add `conversationId` to `ApprovalOperationResult` and `RejectionOperationResult`

**File**: `packages/core/src/operations/workflow-operations.ts`
**Action**: UPDATE

**Current (line 32-38):**
```typescript
export interface ApprovalOperationResult {
  workflowName: string;
  workingPath: string | null;
  userMessage: string | null;
  codebaseId: string | null;
  type: 'interactive_loop' | 'approval_gate';
}
```

**Required change:**
```typescript
export interface ApprovalOperationResult {
  workflowName: string;
  workingPath: string | null;
  userMessage: string | null;
  codebaseId: string | null;
  conversationId: string;
  type: 'interactive_loop' | 'approval_gate';
}
```

**Same for `RejectionOperationResult` (line 40-49).**

---

### Step 4: Return `conversationId` from `approveWorkflow`

**File**: `packages/core/src/operations/workflow-operations.ts`
**Action**: UPDATE

**Current return (line 166-172 for interactive_loop path):**
```typescript
return {
  workflowName: run.workflow_name,
  workingPath: run.working_path,
  userMessage: run.user_message,
  codebaseId: run.codebase_id,
  type: 'interactive_loop',
};
```

**Required change:**
```typescript
return {
  workflowName: run.workflow_name,
  workingPath: run.working_path,
  userMessage: run.user_message,
  codebaseId: run.codebase_id,
  conversationId: run.conversation_id,
  type: 'interactive_loop',
};
```

**Same for the approval_gate return at line 202-208.**

Also update `rejectWorkflow` return values (both paths) to include `conversationId: run.conversation_id`.

---

### Step 5: Look up original platform conversation ID and pass it through in CLI

**File**: `packages/cli/src/commands/workflow.ts`
**Action**: UPDATE

**5a. Add `conversationId` to `WorkflowRunOptions` (line 55-69):**
```typescript
interface WorkflowRunOptions {
  branchName?: string;
  fromBranch?: string;
  noWorktree?: boolean;
  resume?: boolean;
  codebaseId?: string;
  allowEnvKeys?: boolean;
  quiet?: boolean;
  verbose?: boolean;
  conversationId?: string;  // Reuse existing conversation (e.g., from approve)
}
```

**5b. Use provided `conversationId` instead of generating new one (line 272):**

Change:
```typescript
const conversationId = generateConversationId();
```

To:
```typescript
const conversationId = options.conversationId ?? generateConversationId();
```

**5c. Look up platform conversation ID in `workflowApproveCommand` and pass it (line 849-880):**

After `const result = await approveWorkflow(runId, comment);` (line 850), add a lookup:

```typescript
// Look up the original platform conversation ID to keep all messages in one thread
const originalConversation = await conversationDb.getConversationById(result.conversationId);
const platformConversationId = originalConversation?.platform_conversation_id;
```

Then pass it to `workflowRunCommand`:
```typescript
await workflowRunCommand(result.workingPath, result.workflowName, result.userMessage ?? '', {
  resume: true,
  codebaseId: result.codebaseId ?? undefined,
  conversationId: platformConversationId ?? undefined,
});
```

**5d. Same pattern for `workflowRejectCommand` — when the rejection triggers a retry (not cancellation), pass the original conversation ID through.**

---

### Step 6: Update tests

**Tests to verify:**

1. **YAML validation**: Run `bun run cli validate workflows archon-interactive-prd` to confirm the YAML is valid after changes
2. **Type check**: `bun run type-check` to verify the interface changes compile
3. **Existing tests**: Run `bun run test` to ensure no regressions

**Test cases to consider adding:**
- Unit test for `approveWorkflow` verifying `conversationId` is included in return value
- Integration test that `workflowApproveCommand` passes conversation ID through (may require mocking)

---

## Patterns to Follow

**From codebase — approval gate with capture_response:**
The `capture_response` field is already used elsewhere and the schema supports it:
```typescript
// SOURCE: packages/workflows/src/schemas/dag-node.ts:250
capture_response: z.boolean().optional(),
```

**From codebase — conversation lookup by DB UUID:**
```typescript
// SOURCE: packages/core/src/db/conversations.ts:19-24
export async function getConversationById(id: string): Promise<Conversation | null> {
  const result = await pool.query<Conversation>(
    'SELECT * FROM remote_agent_conversations WHERE id = $1',
    [id]
  );
  return result.rows[0] ?? null;
}
```

**From codebase — $ARTIFACTS_DIR usage in other workflows:**
`$ARTIFACTS_DIR` is already used in other default workflows and is pre-created by the executor.

---

## Edge Cases & Risks

| Risk/Edge Case | Mitigation |
|----------------|------------|
| Original conversation deleted between run start and approve | `getConversationById` returns null → fall back to generating new ID (graceful degradation) |
| Existing paused runs from before this fix | They work fine — `capture_response` defaults to `undefined`/false, same as current behavior. New runs will capture properly. |
| `$ARTIFACTS_DIR` substitution in YAML | Already supported by the executor's variable substitution. Verified in `executor.ts:178-210`. |
| Reject command also creates new conversation | Fixed in Step 5d — same pattern applied to `workflowRejectCommand` |

---

## Validation

### Automated Checks

```bash
bun run type-check
bun run test
bun run lint
bun run cli validate workflows archon-interactive-prd
```

### Manual Verification

1. Run `bun run cli workflow run archon-interactive-prd "Build a todo app"` — verify it pauses at foundation-gate
2. Run `bun run cli workflow approve <id> "My answers..."` — verify the approve reuses the same conversation
3. Check that the `research` node receives the user's answers in `$foundation-gate.output`
4. Complete all gates and verify the PRD is written to `$ARTIFACTS_DIR/prds/` (not `.claude/PRPs/prds/`)

---

## Scope Boundaries

**IN SCOPE:**
- Adding `capture_response: true` to three approval gates in `archon-interactive-prd.yaml`
- Changing output path from `.claude/PRPs/prds/` to `$ARTIFACTS_DIR/prds/` in generate and validate nodes
- Adding `conversationId` to `ApprovalOperationResult` and `RejectionOperationResult`
- Returning `conversation_id` from approve/reject operations
- Looking up and passing through original conversation ID in CLI approve/reject commands

**OUT OF SCOPE (do not touch):**
- Other workflows or default commands
- The approval gate mechanism itself (it works correctly when `capture_response` is set)
- The executor's variable substitution logic
- Database schema changes (no migration needed)
- Chat platform approve commands (Slack/Telegram already handle conversation continuity differently)

---

## Metadata

- **Investigated by**: Claude
- **Timestamp**: 2026-04-09
- **Artifact**: `.claude/PRPs/issues/issue-1001-1002-1003.md`
