fix: Enhancement race condition - add headless mode that waits

Phase 1: Fix race condition where main console exits before enhancement completes

Changes to enhance_skill_local.py:
- Add headless mode (default) using subprocess.run() which WAITS for completion
- Add timeout protection (default 10 minutes, configurable)
- Verify SKILL.md was actually updated (check mtime and size)
- Add --interactive-enhancement flag to use old terminal mode
- Detailed progress messages and error handling
- Clean up temp files after completion

Changes to doc_scraper.py:
- Use skill-seekers-enhance entry point instead of direct python path
- Pass --interactive-enhancement flag through if requested
- Update help text to reflect new headless default behavior
- Show proper status messages (HEADLESS vs INTERACTIVE)

Benefits:
- Main console now waits for enhancement to complete
- No more "Package your skill" message while enhancement is running
- Timeout prevents infinite hangs
- Terminal mode still available for users who want it
- Better error messages and progress tracking

Fixes user request: "make sure 1. console wait for it to finish"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
yusyus
2025-11-12 22:53:01 +03:00
parent 4cbd0a0a3c
commit e279ed6ca8
2 changed files with 182 additions and 22 deletions

View File

@@ -1506,7 +1506,9 @@ def setup_argument_parser() -> argparse.ArgumentParser:
parser.add_argument('--enhance', action='store_true',
help='Enhance SKILL.md using Claude API after building (requires API key)')
parser.add_argument('--enhance-local', action='store_true',
help='Enhance SKILL.md using Claude Code in new terminal (no API key needed)')
help='Enhance SKILL.md using Claude Code (no API key needed, runs in background)')
parser.add_argument('--interactive-enhancement', action='store_true',
help='Open terminal window for enhancement (use with --enhance-local)')
parser.add_argument('--api-key', type=str,
help='Anthropic API key for --enhance (or set ANTHROPIC_API_KEY)')
parser.add_argument('--resume', action='store_true',
@@ -1740,16 +1742,25 @@ def execute_enhancement(config: Dict[str, Any], args: argparse.Namespace) -> Non
# Optional enhancement with Claude Code (local, no API key)
if args.enhance_local:
logger.info("\n" + "=" * 60)
logger.info("ENHANCING SKILL.MD WITH CLAUDE CODE (LOCAL)")
if args.interactive_enhancement:
logger.info("ENHANCING SKILL.MD WITH CLAUDE CODE (INTERACTIVE)")
else:
logger.info("ENHANCING SKILL.MD WITH CLAUDE CODE (HEADLESS)")
logger.info("=" * 60 + "\n")
try:
enhance_cmd = ['python3', 'cli/enhance_skill_local.py', f'output/{config["name"]}/']
subprocess.run(enhance_cmd, check=True)
enhance_cmd = ['skill-seekers-enhance', f'output/{config["name"]}/']
if args.interactive_enhancement:
enhance_cmd.append('--interactive-enhancement')
result = subprocess.run(enhance_cmd, check=True)
if result.returncode == 0:
logger.info("\n✅ Enhancement complete!")
except subprocess.CalledProcessError:
logger.warning("\n⚠ Enhancement failed, but skill was still built")
except FileNotFoundError:
logger.warning("\nenhance_skill_local.py not found. Run manually:")
logger.warning("\nskill-seekers-enhance command not found. Run manually:")
logger.info(" skill-seekers-enhance output/%s/", config['name'])
# Print packaging instructions
@@ -1759,10 +1770,11 @@ def execute_enhancement(config: Dict[str, Any], args: argparse.Namespace) -> Non
# Suggest enhancement if not done
if not args.enhance and not args.enhance_local:
logger.info("\n💡 Optional: Enhance SKILL.md with Claude:")
logger.info(" API-based: skill-seekers-enhance output/%s/", config['name'])
logger.info(" or re-run with: --enhance")
logger.info(" Local (no API key): skill-seekers-enhance output/%s/", config['name'])
logger.info(" or re-run with: --enhance-local")
logger.info(" Local (recommended): skill-seekers-enhance output/%s/", config['name'])
logger.info(" or re-run with: --enhance-local")
logger.info(" API-based: skill-seekers-enhance-api output/%s/", config['name'])
logger.info(" or re-run with: --enhance")
logger.info("\n💡 Tip: Use --interactive-enhancement with --enhance-local to open terminal window")
def main() -> None:

View File

@@ -166,8 +166,13 @@ First, backup the original to: {self.skill_md_path.with_suffix('.md.backup').abs
return prompt
def run(self):
"""Main enhancement workflow"""
def run(self, headless=True, timeout=600):
"""Main enhancement workflow
Args:
headless: If True, run claude directly without opening terminal (default: True)
timeout: Maximum time to wait for enhancement in seconds (default: 600 = 10 minutes)
"""
print(f"\n{'='*60}")
print(f"LOCAL ENHANCEMENT: {self.skill_dir.name}")
print(f"{'='*60}\n")
@@ -207,7 +212,11 @@ First, backup the original to: {self.skill_md_path.with_suffix('.md.backup').abs
print(f" ✓ Prompt saved ({len(prompt):,} characters)\n")
# Launch Claude Code in new terminal
# Headless mode: Run claude directly without opening terminal
if headless:
return self._run_headless(prompt_file, timeout)
# Terminal mode: Launch Claude Code in new terminal
print("🚀 Launching Claude Code in new terminal...")
print(" This will:")
print(" 1. Open a new terminal window")
@@ -281,20 +290,159 @@ rm {prompt_file}
return True
def _run_headless(self, prompt_file, timeout):
"""Run Claude enhancement in headless mode (no terminal window)
Args:
prompt_file: Path to prompt file
timeout: Maximum seconds to wait
Returns:
bool: True if enhancement succeeded
"""
import time
from pathlib import Path
print("✨ Running Claude Code enhancement (headless mode)...")
print(f" Timeout: {timeout} seconds ({timeout//60} minutes)")
print()
# Record initial state
initial_mtime = self.skill_md_path.stat().st_mtime if self.skill_md_path.exists() else 0
initial_size = self.skill_md_path.stat().st_size if self.skill_md_path.exists() else 0
# Start timer
start_time = time.time()
try:
# Run claude command directly (this WAITS for completion)
print(" Running: claude {prompt_file}")
print(" ⏳ Please wait...")
print()
result = subprocess.run(
['claude', prompt_file],
capture_output=True,
text=True,
timeout=timeout
)
elapsed = time.time() - start_time
# Check if successful
if result.returncode == 0:
# Verify SKILL.md was actually updated
if self.skill_md_path.exists():
new_mtime = self.skill_md_path.stat().st_mtime
new_size = self.skill_md_path.stat().st_size
if new_mtime > initial_mtime and new_size > initial_size:
print(f"✅ Enhancement complete! ({elapsed:.1f} seconds)")
print(f" SKILL.md updated: {new_size:,} bytes")
print()
# Clean up prompt file
try:
os.unlink(prompt_file)
except:
pass
return True
else:
print(f"⚠️ Claude finished but SKILL.md was not updated")
print(f" This might indicate an error during enhancement")
print()
return False
else:
print(f"❌ SKILL.md not found after enhancement")
return False
else:
print(f"❌ Claude Code returned error (exit code: {result.returncode})")
if result.stderr:
print(f" Error: {result.stderr[:200]}")
return False
except subprocess.TimeoutExpired:
elapsed = time.time() - start_time
print(f"\n⚠️ Enhancement timed out after {elapsed:.0f} seconds")
print(f" Timeout limit: {timeout} seconds")
print()
print(" Possible reasons:")
print(" - Skill is very large (many references)")
print(" - Claude is taking longer than usual")
print(" - Network issues")
print()
print(" Try:")
print(" 1. Use terminal mode: --interactive-enhancement")
print(" 2. Reduce reference content")
print(" 3. Try again later")
# Clean up
try:
os.unlink(prompt_file)
except:
pass
return False
except FileNotFoundError:
print("'claude' command not found")
print()
print(" Make sure Claude Code CLI is installed:")
print(" See: https://docs.claude.com/claude-code")
print()
print(" Try terminal mode instead: --interactive-enhancement")
return False
except Exception as e:
print(f"❌ Unexpected error: {e}")
return False
def main():
if len(sys.argv) < 2:
print("Usage: skill-seekers enhance <skill_directory>")
print()
print("Examples:")
print(" skill-seekers enhance output/steam-inventory/")
print(" skill-seekers enhance output/react/")
sys.exit(1)
import argparse
skill_dir = sys.argv[1]
parser = argparse.ArgumentParser(
description="Enhance a skill with Claude Code (local)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Headless mode (default - runs in background)
skill-seekers enhance output/react/
enhancer = LocalSkillEnhancer(skill_dir)
success = enhancer.run()
# Interactive mode (opens terminal window)
skill-seekers enhance output/react/ --interactive-enhancement
# Custom timeout
skill-seekers enhance output/react/ --timeout 1200
"""
)
parser.add_argument(
'skill_directory',
help='Path to skill directory (e.g., output/react/)'
)
parser.add_argument(
'--interactive-enhancement',
action='store_true',
help='Open terminal window for enhancement (default: headless mode)'
)
parser.add_argument(
'--timeout',
type=int,
default=600,
help='Timeout in seconds for headless mode (default: 600 = 10 minutes)'
)
args = parser.parse_args()
# Run enhancement
enhancer = LocalSkillEnhancer(args.skill_directory)
headless = not args.interactive_enhancement # Invert: default is headless
success = enhancer.run(headless=headless, timeout=args.timeout)
sys.exit(0 if success else 1)