Skip to main content

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. AI Defect Checks​

Catch evidence-backed AI-code failure modes:

skylos . --ai-defects
SKY-L012: Phantom security helper call
SKY-D224: Real package called with invented API
SKY-A101: Test assertion weakened in this diff

5. Quality Gate​

Prevent AI debt from shipping:

skylos . --danger --quality --ai-defects --gate

Block PRs that introduce vulnerabilities or dead code.


Best Practices​

1. Run Skylos before committing
# Pre-commit hook
skylos . --danger --quality --ai-defects || 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 --ai-defects --gate

In-Loop Verification​

skylos verify is the agent-loop path for vibe coding defects. Instead of running a full project report after a feature is done, agents and editors can ask Skylos for a narrow verdict on the changed file or range:

skylos verify . --file src/app.py --range 40:75 --project-context

The verifier returns versioned JSON with only AI-code trust findings. Each finding includes rule_id, vibe_category, ai_likelihood, range, message, optional suggested_fix, confidence, severity, and category. AI-defect findings use category: "ai_defect" and full scans group them under the ai_defects output bucket.

The same schema is available through MCP as verify_change, which lets MCP-compatible coding agents verify a change after each edit. The VS Code extension also routes idle changed-function analysis through this verifier path.

For unsaved editor buffers, pass a JSON manifest through stdin:

printf '{"file":"src/app.py","code":"def handler(): pass\n","range":"1:1"}' \
| skylos verify . --stdin --no-fail

Local Defect Signals​

Skylos can record local structural signals when a developer accepts, dismisses, or trains on an AI-defect finding through the agent service. Signal capture is off by default and controlled by [tool.skylos.contribution].

[tool.skylos.contribution]
collect_local_signals = true
contribute_public_corpus = false
structural_signatures_only = true
include_source = false

Local events are written to .skylos/contribution/events.json. They contain rule/category/severity metadata, file extension, line bucket, and hashes. Raw source is not captured.

AI-Specific Detection Rules​

Skylos includes rules specifically designed to catch evidence-backed failure modes common in AI-generated code. AI-defect checks (SKY-A101 to SKY-A105, SKY-L012, SKY-L023, SKY-D222, SKY-D224, and SKY-D225) report under the ai_defects output bucket; broader AI-prone quality and security rules keep their existing buckets. See AI Defect Verification for the exact grouping method and why some AI-defect rules keep historical SKY-L or SKY-D IDs.

RuleWhat It CatchesWhy AI Does ThisVibe Category
SKY-A101Assertion weakening — exact assertions replaced with truthiness/null checks, skips, or removed exception assertionsLLMs sometimes make tests pass by weakening the test instead of preserving behaviorassertion_weakening
SKY-A102High-risk change without tests — auth, billing, validation, tenant, webhook, or similar code changed with no test file changedAI-generated PRs can touch behavior-sensitive code without adding reviewable test evidencetest_impact_gap
SKY-A103CI permission expansion — GitHub Actions diff adds write permissions or privileged workflow triggersAI-generated CI edits can accidentally broaden repository token privilegesci_permission_expansion
SKY-A104Public CLI surface drift — public CLI flag removed from argparse, Click, or Typer codeAI-generated refactors can break documented commands or automation without noticing compatibility impactpublic_api_surface_drift
SKY-A105Contract route guard missing — a route lacks a decorator required by .skylos/ai-contract.ymlLLMs add endpoints without applying repo-specific auth or tenant guardrailsmissing_contract_guardrail
SKY-L012Phantom function calls — sanitize_input() that doesn't existLLMs hallucinate security functions that were never defined or importedhallucinated_reference
SKY-L023Phantom decorators — @require_auth that doesn't existLLMs hallucinate security decorators, leaving functions unprotectedhallucinated_reference
SKY-L026Unfinished generation — function body is only pass or ...LLMs generate stubs and move on, leaving empty functions in productionincomplete_generation
SKY-L016Undefined config — os.getenv("ENABLE_CACHING") never setLLMs invent feature flags that reference config that doesn't existghost_config
SKY-L024Stale mocks — mock.patch("mod.old_func") targets deleted functionAfter refactoring, AI-generated tests still patch old function namesstale_reference
SKY-L013Insecure randomness — random.randint() for tokensLLMs reach for random instead of secrets for security values—
SKY-L014Hardcoded credentials — password="admin123"LLMs generate working examples with real-looking credentials—
SKY-L010Security TODO markers — # TODO: add auth checkLLMs leave placeholder comments as "future work" that never happens—
SKY-L011Disabled security controls — verify=FalseLLMs disable TLS/CSRF to make examples "just work"—
SKY-L017Error info disclosure — return str(e)LLMs return raw exceptions for "debugging" instead of safe error messages—
SKY-L020Broad file permissions — os.chmod(f, 0o777)LLMs use maximally permissive modes to avoid permission errors—
SKY-D222Hallucinated dependencies — importing packages that don't exist on PyPILLMs invent package names that sound right but aren't realdependency_hallucination
SKY-D224API signature hallucinations — real package, invented method or keywordLLMs confidently call APIs that look plausible but are not in the installed packageapi_signature_hallucination
SKY-D225Dependency version hallucinations — npm or Go versions that were never publishedLLMs invent semver pins that make manifests look complete but fail in buildsdependency_hallucination
SKY-D260Prompt injection — hidden instructions in comments, YAML, MarkdownHostile content targeting AI agents processing your codebase—
# Scan for AI-generated code issues
skylos . --ai-defects

The Numbers​

MetricWithout SkylosWith Skylos
Dead code from AI~30% of AI-generated code< 5%
Security vulns shipped1 in 10 PRsBlocked at CI
Average function complexity127
Time to onboard new dev3 weeks2 weeks

Next Steps​

Getting Started​

Install Skylos and scan your codebase

CI/CD Integration​

Set up quality gates to prevent AI debt