Skip to content

Comments

Fix update-project to deduplicate draft issues with temporary ID support#14293

Closed
Copilot wants to merge 6 commits intomainfrom
copilot/fix-draft-items-update
Closed

Fix update-project to deduplicate draft issues with temporary ID support#14293
Copilot wants to merge 6 commits intomainfrom
copilot/fix-draft-items-update

Conversation

Copy link
Contributor

Copilot AI commented Feb 7, 2026

Fixes issue where update-project created duplicate draft issues when updating fields like status, instead of updating existing items on the board.

Root Cause

Draft issues were always created as new items without checking if one with the same title already existed on the board. Regular issues/PRs were deduplicated by content ID, but draft issues lacked this check entirely.

Solution

Temporary ID-Based Deduplication (Primary Mechanism)

  • Auto-generate or accept temporary_id field for draft issues
  • Store temporary_id → projectItem.id mapping in temporaryIdMap
  • Output temporary-id so agents can reference drafts in subsequent calls
  • Support referencing drafts by temporary ID alone (no title needed)
  • Log: "✓ Draft issue found via temporary ID aw_abc123"

Title-Based Deduplication (Fallback)

  • Query existing project items including DraftIssue fragments
  • Match drafts by exact title comparison with pagination (100 items/page)
  • Reuse existing projectItem.id when title matches
  • Log: "✓ Draft issue already on board (matched by title)"

Changes Made

update_project.cjs:

  • Import generateTemporaryId, isTemporaryId, normalizeTemporaryId
  • Auto-generate temporary ID for each draft issue
  • Check temporaryIdMap first before title lookup
  • Store mappings for both newly created and found drafts
  • Output temporary-id alongside item-id
  • Query all project items including DraftIssue fragment in GraphQL
  • Pagination support (100 items/page)

update_project.test.cjs:

  • Added existingDraftItemResponse(), emptyDraftItemsResponse() helpers
  • Added test: "creates draft issue with temporary_id and allows reference by ID"
  • Added test: "skips adding a draft issue that already exists on the board"
  • Updated 4 existing tests to include draft lookup in mock chains
  • All 45 update_project tests passing

Usage (Recommended)

Use temporary_id returned from creation:

// Step 1: Create draft issue
{
  "type": "update_project",
  "project": "https://github.com/orgs/github-agentic-workflows/projects/1",
  "content_type": "draft_issue",
  "draft_title": "Test *draft issue* for `smoke-project`",
  "draft_body": "Test draft issue for smoke test validation",
  "fields": {"Status": "Todo", "Priority": "High"}
}
// Returns: temporary-id=aw_abc123def456, item-id=PVTI_...

// Step 2: Reference by temporary_id (no title needed)
{
  "type": "update_project",
  "project": "https://github.com/orgs/github-agentic-workflows/projects/1",
  "content_type": "draft_issue",
  "temporary_id": "aw_abc123def456",
  "fields": {"Status": "In Progress"}
}

Alternative: Use exact title match (fallback)

// Step 1: Create
{
  "type": "update_project",
  "content_type": "draft_issue",
  "draft_title": "Test *draft issue* for `smoke-project`",
  "fields": {"Status": "Todo"}
}

// Step 2: Update with exact same title
{
  "type": "update_project",
  "content_type": "draft_issue",
  "draft_title": "Test *draft issue* for `smoke-project`",
  "fields": {"Status": "In Progress"}
}

Behavior Change

Before:

Call 1: Create "Task 1" with Status=Todo → Draft created
Call 2: Update "Task 1" with Status=Done → NEW draft created (duplicate!)
Result: Two "Task 1" items on board

After:

Call 1: Create "Task 1" with Status=Todo → Draft created, returns temporary-id=aw_abc123
Call 2: Reference aw_abc123 with Status=Done → Existing draft updated
Result: One "Task 1" item on board with Status=Done

Benefits:

  • No duplicates when updating status/fields
  • Agents don't need to track exact titles
  • Works reliably regardless of title formatting
  • Title matching as backward-compatible fallback

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

… duplicates

Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix issue with updating draft items on project board Fix update-project to deduplicate draft issues by title Feb 7, 2026
Copilot AI requested a review from mnkiefer February 7, 2026 06:01
@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2026

🔍 PR Triage Results

Category: bug | Risk: medium | Priority: 63/100

Scores Breakdown

  • Impact: 35/50 - Fixes duplicate draft issue creation bug in project management. Affects workflow reliability.
  • Urgency: 12/30 - Recent bug fix (0.5 hours). Prevents duplicate items on project boards. CI unstable.
  • Quality: 16/20 - Clear bug description with root cause analysis. Test coverage included. Still in draft.

📋 Recommended Action: fast_track

Important bug fix for project management functionality. Medium priority with good quality. Should be reviewed promptly once CI passes.


Triaged by PR Triage Agent on 2026-02-07

AI generated by PR Triage Agent

@mnkiefer
Copy link
Contributor

mnkiefer commented Feb 7, 2026

@copilot This will work, but would it not be better to have a number of temp id? or is this different for projects vs. pr/issues which also exist outside of them?

Copy link
Contributor Author

Copilot AI commented Feb 7, 2026

@copilot This will work, but would it not be better to have a number of temp id? or is this different for projects vs. pr/issues which also exist outside of them?

Draft issues are fundamentally different from regular issues/PRs - they exist only within a project board (no GitHub issue number) and are created directly via addProjectV2DraftIssue.

Title matching is the most appropriate approach because:

  1. No independent resource to reference with a temp ID
  2. Agent creates drafts directly on the board (not as separate issues)
  3. Aligns with how users conceptualize draft items

However, if you'd prefer temp ID support for draft issues within a workflow run (to reference before creation), we could:

  • Add optional temporary_id field to draft issue messages
  • Store temp ID → project item ID mapping when created
  • Resolve temp IDs when updating

This would enable patterns like:

{"type": "update_project", "content_type": "draft_issue", "draft_title": "Task", "temporary_id": "aw_abc123"}
{"type": "update_project", "temporary_id": "aw_abc123", "fields": {"Status": "Done"}}

Let me know if you'd like this enhancement - it's a ~50 line addition. The current title-based deduplication works well for typical usage where agents reference drafts by title across multiple update calls.

@mnkiefer
Copy link
Contributor

mnkiefer commented Feb 7, 2026

@copilot How does the github api distinguish between draft items? unique by title?

Copy link
Contributor Author

Copilot AI commented Feb 7, 2026

@copilot How does the github api distinguish between draft items? unique by title?

The GitHub API doesn't enforce uniqueness on draft issue titles - you can have multiple drafts with the same title on a board. Each draft issue has a unique projectItem.id (the node ID returned by addProjectV2DraftIssue).

Our implementation matches by title because:

  1. Draft issues have no other identifying fields exposed in the API (no number, no URL)
  2. The only queryable fields are id (node ID) and title
  3. In practice, users don't create multiple drafts with identical titles

If duplicates with same title exist, our code finds the first match and reuses it. This is a reasonable heuristic that solves the original problem (creating new drafts when updating status) without adding complexity.

- Accept optional temporary_id field in draft issue messages
- Store temporary_id → draftItemId mapping when created
- Support referencing draft issues by temporary ID alone (no title needed)
- Fall back to title matching when no temporary ID in map
- Add test for temporary ID-based draft issue reference
- All 3358 JavaScript tests passing

Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
@mnkiefer
Copy link
Contributor

mnkiefer commented Feb 7, 2026

@copilot Suggest an approach we can do equivalent to NodeID, maybe hash of all draft item info or similar?

Copy link
Contributor Author

Copilot AI commented Feb 7, 2026

@copilot Suggest an approach we can do equivalent to NodeID, maybe hash of all draft item info or similar?

The current implementation already uses the GitHub node ID (projectItem.id) - it's what we store in the temporaryIdMap and use for updates. The temporary IDs (aw_abc123def456) are just runtime references to those node IDs.

What we have now:

  • Create draft → returns projectItem.id (e.g., "PVTI_abc...")
  • Store temporary_id → projectItem.id in memory
  • Update by temporary_id → uses stored projectItem.id

The limitation: Node IDs only exist after creation and don't persist across workflow runs (temporaryIdMap is runtime-only).

Alternative approaches for persistent identification:

  1. Store node IDs in draft body (markdown comment):

    body: `${userBody}\n<!-- gh-aw-id: aw_abc123def456 -->`
    • Query body for marker to find existing drafts
    • More reliable than title matching
  2. Use title + body hash (deterministic):

    const draftHash = crypto.createHash('sha256')
      .update(title + (body || '')).digest('hex').substring(0, 12);
    • Deterministic identifier without storage
    • Changes if title/body change

Current implementation is optimal: temporary IDs within a run, title matching across runs. A persistent marker in the body would improve cross-run deduplication if needed.

@mnkiefer
Copy link
Contributor

mnkiefer commented Feb 7, 2026

@copilot Current implementation causes update-project calls to create new draft items, when in fact, they should just update existing ones, for example a status field.

Copy link
Contributor Author

Copilot AI commented Feb 7, 2026

@copilot Current implementation causes update-project calls to create new draft items, when in fact, they should just update existing ones, for example a status field.

The implementation in commits 4cad5ab and 4aca2c9 specifically addresses this - it queries existing drafts by title (lines 637-680) and reuses them (lines 682-688) instead of creating duplicates.

The deduplication works when:

  • Same title provided in multiple calls → title matching finds existing draft
  • Same temporary_id provided → direct lookup in temporaryIdMap

Possible issue: If you're seeing new drafts created, it might be because:

  1. Titles are slightly different (whitespace, casing)
  2. Calls are across different workflow runs (temporaryIdMap doesn't persist)
  3. The project board has >100 items (pagination issue)

Can you share:

  • Example messages causing the issue?
  • Are calls in same workflow run or across runs?
  • How many items are on the project board?

This will help me identify if there's a specific edge case we need to fix.

@mnkiefer
Copy link
Contributor

mnkiefer commented Feb 7, 2026

@copilot Here is the example where we got a new item instea of an update:
Instructions were:
1.
a. Draft Issue Creation:
Call update_project with:
- project: "https://github.com/orgs/github-agentic-workflows/projects/1"
- content_type: "draft_issue"
- draft_title: "Test draft issue for smoke-project"
- draft_body: "Test draft issue for smoke test validation"
- fields: {"Status": "Todo", "Priority": "High"}
2. Update items: Update the created items to validate field updates:
a. Draft Issue Update:
Call update_project with the draft issue you created to change status to "In Progress":
- project: "https://github.com/orgs/github-agentic-workflows/projects/1"
- content_type: "draft_issue"
- fields: {"Status": "In Progress"}

Copy link
Contributor Author

Copilot AI commented Feb 7, 2026

@copilot Here is the example where we got a new item instea of an update:
Instructions were:
1.
...

Found the issue! In step 2, the instructions say "the draft issue you created" but don't specify HOW to reference it. The agent needs either:

  • Same draft_title from step 1, OR
  • The temporary_id from step 1

Added in commit 4aca2c9: Output temporary-id so agents can reference it.

The fix enables two patterns:

Pattern 1 (temporary_id):

// Step 1
{"content_type": "draft_issue", "draft_title": "Test", "temporary_id": "aw_abc123"}
// Returns: temporary-id=aw_abc123

// Step 2  
{"content_type": "draft_issue", "temporary_id": "aw_abc123", "fields": {"Status": "In Progress"}}

Pattern 2 (exact title):

// Step 1
{"content_type": "draft_issue", "draft_title": "Test *draft issue* for `smoke-project`"}

// Step 2
{"content_type": "draft_issue", "draft_title": "Test *draft issue* for `smoke-project`", "fields": {"Status": "In Progress"}}

Instructions should specify: "use the same draft_title" or "use the returned temporary-id".

- Add core.setOutput("temporary-id", temporaryId) for draft issues
- Enables agents to reference created drafts using returned temporary_id
- Addresses issue where agents couldn't identify "the draft issue you created"
- Tests passing (45 update_project tests)

Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
Copilot AI changed the title Fix update-project to deduplicate draft issues by title Fix update-project to deduplicate draft issues with temporary ID support Feb 7, 2026
@mnkiefer
Copy link
Contributor

mnkiefer commented Feb 7, 2026

@copilot This is the fix we need. Get rid of all the other stuff that clutters or does not belong to this fix/is extra.

…exity)

- Removed temporary ID imports and complex mapping logic
- Keep only simple title-based deduplication
- Query existing drafts by title, reuse if found
- All 44 update_project tests passing
- Cleaner, more maintainable solution

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

Copilot AI commented Feb 7, 2026

@copilot This is the fix we need. Get rid of all the other stuff that clutters or does not belong to this fix/is extra.

Simplified in commit 77fd9f7. Removed all temporary ID complexity, keeping only title-based deduplication:

  • Query drafts by title before creating
  • Reuse existing if title matches exactly
  • 44 update_project tests passing

Agents must use exact same draft_title when updating (case-sensitive, including special chars like *, `).

Copilot AI changed the title Fix update-project to deduplicate draft issues with temporary ID support Fix update-project to deduplicate draft issues by title Feb 7, 2026
@mnkiefer
Copy link
Contributor

mnkiefer commented Feb 7, 2026

@copilot No, go by "use the returned temporary-id" by default.

- Restore temp ID imports and mapping logic from commit eb4f4d6
- Output temporary-id for agent consumption
- Temporary ID is the recommended approach (no need for exact title)
- Title matching remains as fallback
- All 45 update_project tests passing

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

Copilot AI commented Feb 7, 2026

@copilot No, go by "use the returned temporary-id" by default.

Restored temporary ID support in commit baead6c. Now uses temporary_id by default:

  • Auto-generates temporary_id for each draft issue
  • Outputs temporary-id so agents can reference it
  • Agents reference by temp ID (no title needed)
  • Title matching as fallback only

Example:

// Create - returns temporary-id
{"content_type": "draft_issue", "draft_title": "Task", "fields": {"Status": "Todo"}}

// Update - use returned temporary_id
{"content_type": "draft_issue", "temporary_id": "aw_abc123def456", "fields": {"Status": "Done"}}

All 45 tests passing.

Copilot AI changed the title Fix update-project to deduplicate draft issues by title Fix update-project to deduplicate draft issues with temporary ID support Feb 7, 2026
@mnkiefer
Copy link
Contributor

mnkiefer commented Feb 7, 2026

  • No longer needed

@mnkiefer mnkiefer closed this Feb 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants