# Unlock Story - Release Story Lock The workflow execution engine is governed by: {project-root}/_bmad/core/tasks/workflow.xml You MUST have already loaded and processed: {installed_path}/workflow.yaml TEAM COORDINATION: Releasing locks makes stories available for others ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🔓 STORY UNLOCK - Release Lock ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ❌ ERROR: story_key parameter required Usage: /unlock-story story_key=2-5-auth /unlock-story story_key=2-5-auth reason="Blocked on design" /unlock-story story_key=2-5-auth --force reason="Developer unavailable" HALTING HALT 📦 Story: {{story_key}} Call: mcp__github__get_me() ❌ CRITICAL: GitHub MCP not accessible Cannot unlock story without GitHub API access. HALTING HALT current_user = response.login ✅ GitHub connected as @{{current_user}} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🔍 Checking Lock Status ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Call: mcp__github__search_issues({ query: "repo:{{github_owner}}/{{github_repo}} label:story:{{story_key}}" }) ❌ ERROR: Story not found in GitHub Story "{{story_key}}" does not exist. HALTING HALT issue = response.items[0] issue_number = issue.number current_assignee = issue.assignee?.login or null ℹ️ Story is not locked Story {{story_key}} has no assignee. Nothing to unlock. Issue: #{{issue_number}} Exit (already unlocked) ❌ PERMISSION DENIED Story {{story_key}} is locked by @{{current_assignee}} You can only unlock stories you have checked out. Options: 1. Ask @{{current_assignee}} to unlock it 2. If you are a Scrum Master, use --force: /unlock-story story_key={{story_key}} --force reason="Developer unavailable" HALTING HALT Verify current_user is in scrum_masters list ❌ PERMISSION DENIED --force requires Scrum Master permissions. Current Scrum Masters: {{#each scrum_masters}} - @{{this}} {{/each}} Your user: @{{current_user}} HALTING HALT ⚠️ FORCE UNLOCK Scrum Master @{{current_user}} is unlocking story owned by @{{current_assignee}} {{#if reason}} Reason: {{reason}} {{else}} WARNING: No reason provided. Consider adding: /unlock-story story_key={{story_key}} --force reason="..." {{/if}} Set force_unlock = true Set notify_owner = true ✅ You own this lock - proceeding with unlock Set force_unlock = false ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🔐 Releasing Lock ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ATOMIC UNLOCK with retry: attempt = 0 max_attempts = 4 WHILE attempt < max_attempts: TRY: # 1. Remove assignee Call: mcp__github__issue_write({ method: "update", owner: {{github_owner}}, repo: {{github_repo}}, issue_number: {{issue_number}}, assignees: [] }) # 2. Update status label back to ready-for-dev # Get current labels first current_labels = issue.labels.map(l => l.name) # Remove in-progress, add ready-for-dev new_labels = current_labels .filter(l => l != "status:in-progress") # Only add ready-for-dev if story wasn't completed IF NOT current_labels.includes("status:done"): new_labels.push("status:ready-for-dev") Call: mcp__github__issue_write({ method: "update", owner: {{github_owner}}, repo: {{github_repo}}, issue_number: {{issue_number}}, labels: new_labels }) # 3. Add unlock comment comment_body = "🔓 **Story unlocked**\n\n" IF force_unlock: comment_body += "Unlocked by Scrum Master @{{current_user}}\n" comment_body += "Previous owner: @{{current_assignee}}\n" IF reason: comment_body += "Reason: {{reason}}\n" ELSE: comment_body += "Released by @{{current_user}}\n" IF reason: comment_body += "Reason: {{reason}}\n" comment_body += "\n_Story is now available for checkout._" Call: mcp__github__add_issue_comment({ owner: {{github_owner}}, repo: {{github_repo}}, issue_number: {{issue_number}}, body: comment_body }) # 4. Verify unlock sleep 1 second verification = Call: mcp__github__issue_read({ method: "get", owner: {{github_owner}}, repo: {{github_repo}}, issue_number: {{issue_number}} }) IF verification.assignees.length > 0: THROW "Unlock verification failed - still has assignees" output: "✅ GitHub Issue unassigned and verified" BREAK CATCH error: attempt++ IF attempt < max_attempts: backoff = [1000, 3000, 9000][attempt - 1] sleep backoff ms output: "⚠️ Retry {{attempt}}/3: {{error}}" ELSE: output: "❌ FAILED to unlock after 3 retries: {{error}}" output: "" output: "The lock may still be active in GitHub." output: "Try again or manually unassign in GitHub UI." HALT lock_file = {{lock_dir}}/{{story_key}}.lock Delete lock_file ✅ Local lock file removed ℹ️ No local lock file found (already removed or on different machine) Update cache meta to clear lock: cache_meta = load {{cache_dir}}/.bmad-cache-meta.json IF cache_meta.stories[{{story_key}}]: cache_meta.stories[{{story_key}}].locked_by = null cache_meta.stories[{{story_key}}].locked_until = null save cache_meta ✅ Cache metadata updated 📧 Notification sent to @{{current_assignee}}: "Your lock on story {{story_key}} has been released by Scrum Master @{{current_user}}. {{#if reason}}Reason: {{reason}}{{/if}} The story is now available for other developers. If you were working on this, please coordinate with your team." ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✅ UNLOCK COMPLETE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ **Story:** {{story_key}} **Issue:** #{{issue_number}} **Previous Owner:** @{{current_assignee}} **Status:** Available for checkout {{#if reason}} **Reason:** {{reason}} {{/if}} {{#if force_unlock}} **Force Unlock:** Yes (by Scrum Master @{{current_user}}) {{/if}} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ **Story is now available.** Other developers can checkout with: /checkout-story story_key={{story_key}} View available stories: /available-stories ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━