Vibe Coding & AI-Generated Debt
"Vibe coding" is the practice of coding with AI assistants—accepting suggestions from Copilot, ChatGPT, or Claude without fully understanding every line. It's fast, it's productive, and it's creating a new category of technical debt.
The Rise of AI-Assisted Development
AI coding assistants are now mainstream:
- GitHub Copilot: 1.3M+ paid subscribers
- ChatGPT/Claude: Used daily by millions of developers
- Cursor, Windsurf, Replit: AI-native IDEs growing fast
The productivity gains are real. But so are the problems.
The Hidden Costs of Vibe Coding
1. Dead Code Accumulation
AI suggests complete solutions. You use 60% of it:
# You asked: "function to parse JSON config"
# AI gave you:
import json
import yaml # only need JSON
import toml # only needed JSON
from pathlib import Path
from typing import Dict, Any, Optional, List # List unused
def parse_config(path: str) -> Dict[str, Any]:
file_path = Path(path)
if file_path.suffix == '.json':
with open(file_path) as f:
return json.load(f)
elif file_path.suffix in ['.yml', '.yaml']: # never use YAML
with open(file_path) as f:
return yaml.safe_load(f)
elif file_path.suffix == '.toml': # never use TOML
with open(file_path) as f:
return toml.load(f)
else:
raise ValueError(f"Unsupported format: {file_path.suffix}")
def validate_config(config: Dict[str, Any]) -> bool: # never called this
required_keys = ['database', 'api_key', 'debug']
return all(key in config for key in required_keys)
You wanted 10 lines. You got 25. Half is dead.
2. Security Vulnerabilities
AI doesn't understand your security context:
# You asked: "function to run shell command"
# AI gave you:
import subprocess
def run_command(cmd: str) -> str:
result = subprocess.run(
cmd,
shell=True, # CRITICAL: Command injection vulnerability!
capture_output=True,
text=True
)
return result.stdout
# AI didn't ask: "Is cmd from user input?"
# AI didn't know: This is called from a web endpoint
Result: Exploitable vulnerability shipped to production.
3. Complexity Creep
AI writes verbose, "safe" code that handles cases you don't have:
# You asked: "sum a list of numbers"
# You expected: sum(numbers)
# AI gave you:
from typing import List, Union, Optional
from decimal import Decimal
import logging
logger = logging.getLogger(__name__)
def sum_numbers(
numbers: List[Union[int, float, Decimal]],
initial: Optional[Union[int, float, Decimal]] = None,
ignore_none: bool = True,
round_result: Optional[int] = None
) -> Union[int, float, Decimal]:
if not numbers:
logger.warning("Empty list provided to sum_numbers")
return initial or 0
total = initial or 0
for num in numbers:
if num is None and ignore_none:
continue
if num is None:
raise ValueError("None value in list")
total += num
if round_result is not None:
total = round(total, round_result)
return total
30 lines instead of 1. Complexity: 6 instead of 1.
4. Orphaned Functions
AI creates helper functions you never call:
5. Copy-Paste Propagation
You accept AI suggestions across multiple files. Same pattern, same bugs:
# file1.py - AI wrote this
data = request.json() # No validation
db.execute(f"INSERT INTO t VALUES ({data['id']})")
# file2.py - You asked AI for similar code
data = request.json() # Same vulnerability copied
db.execute(f"INSERT INTO other VALUES ({data['id']})")
# file3.py - Pattern continues...
The Vibe Coding Cycle
The problem isn't AI—it's accepting without reviewing.
Why Traditional Tools Miss This
Linters Don't Catch Logic Issues
# Passes all linters!
def process(user_id):
query = f"SELECT * FROM users WHERE id = {user_id}" # SQL injection
return db.execute(query)
Tests Only Cover What You Test
AI-generated code often has:
- Untested error branches
- Unused parameters (no test exercises them)
- Dead functions (no test calls them)
Code Review Doesn't Scale
Average PR size with AI assistance: 2.5x larger
Code review time: Fixed
Result: Less scrutiny per line
How Skylos Fixes Vibe Coding Debt
1. Dead Code Detection
Find the AI bloat:
skylos . --confidence 70
Unused imports: 23
Unused functions: 8
Unused parameters: 12
2. Security Scanning
Catch what AI didn't consider:
skylos . --danger
SKY-D210 CRITICAL: SQL injection in api/users.py:45
SKY-D212 CRITICAL: Command injection in utils/shell.py:12
3. Complexity Alerts
Flag AI's over-engineering:
skylos . --quality
SKY-Q301: Function 'sum_numbers' has complexity 6 (threshold: 10)
SKY-Q304: Function 'parse_config' has 5 parameters (threshold: 5)
4. Quality Gate
Prevent AI debt from shipping:
skylos . --danger --quality --gate
Block PRs that introduce vulnerabilities or dead code.
Best Practices
1. Run Skylos before committing
# Pre-commit hook
skylos . --danger --quality || exit 1
Catches AI-introduced issues immediately.
2. Delete AI's unused suggestions
AI gives you 5 functions. You need 2. Delete the other 3.
skylos . -i # Interactive mode
3. Question AI's security assumptions
AI doesn't know:
- Where your data comes from
- Who can call this function
- What's sensitive in your context
Ask: "Is this input trusted?"
4. Simplify AI's over-engineering
# AI gave you 30 lines for summing
# You need:
total = sum(numbers)
Use Skylos complexity warnings as a signal.
5. Set up CI gates
Don't let AI debt accumulate:
- run: skylos . --danger --quality --gate
AI-Specific Detection Rules
Skylos includes rules specifically designed to catch patterns common in AI-generated code:
| Rule | What It Catches | Why AI Does This | Vibe Category |
|---|---|---|---|
SKY-L012 | Phantom function calls — sanitize_input() that doesn't exist | LLMs hallucinate security functions that were never defined or imported | hallucinated_reference |
SKY-L023 | Phantom decorators — @require_auth that doesn't exist | LLMs hallucinate security decorators, leaving functions unprotected | hallucinated_reference |
SKY-L026 | Unfinished generation — function body is only pass or ... | LLMs generate stubs and move on, leaving empty functions in production | incomplete_generation |
SKY-L016 | Undefined config — os.getenv("ENABLE_CACHING") never set | LLMs invent feature flags that reference config that doesn't exist | ghost_config |
SKY-L024 | Stale mocks — mock.patch("mod.old_func") targets deleted function | After refactoring, AI-generated tests still patch old function names | stale_reference |
SKY-L013 | Insecure randomness — random.randint() for tokens | LLMs reach for random instead of secrets for security values | — |
SKY-L014 | Hardcoded credentials — password="admin123" | LLMs generate working examples with real-looking credentials | — |
SKY-L010 | Security TODO markers — # TODO: add auth check | LLMs leave placeholder comments as "future work" that never happens | — |
SKY-L011 | Disabled security controls — verify=False | LLMs disable TLS/CSRF to make examples "just work" | — |
SKY-L017 | Error info disclosure — return str(e) | LLMs return raw exceptions for "debugging" instead of safe error messages | — |
SKY-L020 | Broad file permissions — os.chmod(f, 0o777) | LLMs use maximally permissive modes to avoid permission errors | — |
SKY-D222 | Hallucinated dependencies — importing packages that don't exist on PyPI | LLMs invent package names that sound right but aren't real | — |
SKY-D260 | Prompt injection — hidden instructions in comments, YAML, Markdown | Hostile content targeting AI agents processing your codebase | — |
# Scan for AI-generated code issues
skylos . --danger --quality --table
The Numbers
| Metric | Without Skylos | With Skylos |
|---|---|---|
| Dead code from AI | ~30% of AI-generated code | < 5% |
| Security vulns shipped | 1 in 10 PRs | Blocked at CI |
| Average function complexity | 12 | 7 |
| Time to onboard new dev | 3 weeks | 2 weeks |
Next Steps
Getting Started
Install Skylos and scan your codebase
CI/CD Integration
Set up quality gates to prevent AI debt