Spell Checker (python, written by Claude Code)
envgap__claude-code__python-t2-31
Written by a coding agent; not on GitHubWritten 2026-02-27
01 / FAILURE SIGNATURE
Captured in a clean container
ImportError: The 'enchant' C library was not found and maybe needs to be installed.
02 / ENVIRONMENT RECIPE
- Base commit
102f2146ce3071a3184f45f31ef7e0305a459b09- Manifest
requirements.txt- Reproduce
true- Run under trace
rc=0; out=$(timeout 60 python3 spellcheck.py < /dev/null 2>&1 | { head -c 1000000; cat > /dev/null; }; exit ${PIPESTATUS[0]}) || rc=$?; printf '%s\n' "$out"; env_error='(ModuleNotFoundError|ImportError|No module named|cannot open shared object file|DLL load failed|shared library|cannot load library|Library not loaded|Cannot find module|ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND|ERR_REQUIRE_ESM|compiled against a different Node|Could not find or load main class|ClassNotFoundException|NoClassDefFoundError|UnsupportedClassVersionError|UnsatisfiedLinkError|NoSuchMethodError|NoSuchFieldError|AbstractMethodError|IncompatibleClassChangeError|IllegalAccessError|ServiceConfigurationError|error while loading shared libraries|symbol lookup error|version `[^'"'"']*'"'"' not found|command not found)'; asked='(^| )[[:blank:]]*usage:|the following arguments are required|missing (required )?(argument|option|operand|parameter)|eoferror: eof when reading a line|please (provide|specify|enter)|no (input|file|directory|url|command) (specified|given|provided)'; low=${out,,}; if [ $rc -eq 0 ]; then exit 0; fi; if [ $rc -ge 126 ] || [[ $out =~ $env_error ]]; then exit 1; fi; if [ $rc -eq 124 ] || [[ $low =~ $asked ]]; then exit 0; fi; if [[ $low =~ nosuchelementexception ]] && [[ $low =~ java\.util\.scanner ]]; then exit 0; fi; exit 1
Reference environment fix used for admission
--- /dev/null +++ b/setup.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# System packages this project needs on a clean Ubuntu machine. +set -e +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get install -y -qq --no-install-recommends libenchant-2-dev
03 / TASK AND FAILURE
claude-code/python-t2 #31 · read the task the agent was given
Claude Code wrote this python project from the task below. It does not run on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: Spell Checker Write a program that checks spelling in text files using dictionary lookups and edit distance algorithms, supporting custom dictionaries, multiple languages, and suggestion ranking. FUNCTIONAL REQUIREMENTS: - Accept a text file path as a command-line argument - Load a dictionary of valid words (ship a built-in English dictionary of at least 50,000 common words) - Identify misspelled words by checking each word against the dictionary, ignoring case, numbers, URLs, email addresses, and common abbreviations - Generate spelling suggestions for each misspelled word using edit distance (Levenshtein distance) with up to 2 edits, ranked by likelihood (frequency-weighted if possible) - Support custom dictionaries via --dictionary flag (path to a text file with one word per line) that supplements the built-in dictionary - Support an ignore list via --ignore flag (comma-separated words or path to file) for domain-specific terms that should not be flagged - Support multiple output modes via --format flag: interactive (show each error with context and suggestions), report (summary with all errors), and json (machine-readable) - Show each misspelled word with its line number, column number, and surrounding context (the line containing the error with the word highlighted) - Compute document statistics: total words, unique words, misspelled words count, and spelling accuracy percentage - Support checking multiple files via --batch flag - Save the spell check report as JSON with --output flag (default: spelling_report.json) including all misspelled words, their locations, and suggested corrections - If no input file is given, generate a sample text document with intentional spelling errors of various types (transpositions, missing letters, extra letters, wrong letters), check it, and display the results with suggestions - Handle errors: unreadable files, binary file detection, encoding issues, and very large files Create a complete Python project for a clean Ubuntu 22.04 machine with only Python 3.10+ installed. Include: - Source code - requirements.txt with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels checked by running the task · needs human review
underspecificationLabel rules and the text that matched
[
{
"category": "underspecification",
"rule": "diff.adds_external_environment_requirement",
"source": "manifest_diff:setup.sh",
"excerpt": "export DEBIAN_FRONTEND=noninteractive"
},
{
"category": "underspecification",
"rule": "diff.adds_external_environment_requirement",
"source": "manifest_diff:setup.sh",
"excerpt": "apt-get install -y -qq --no-install-recommends libenchant-2-dev"
}
]Written by Claude Code (study run M1T2P31L1). It failed as written and was repaired by changing only its environment.
Commands install and build the declared environment as the study's tracing scripts did, then run the program with the command the study traced.
Preparation dates registries as the oracle does: PyPI index files filtered by upload_time <= the registry date, and yanks applied only when dated at or before it; alternate indexes, direct URLs, apt and arbitrary setup downloads are not network-enforced.
05 / FILES
The project as the agent wrote it
2 files, exactly as written, before any repair.
requirements.txt
pyenchant==3.2.2 click==8.1.7
spellcheck.py
"""
Spell Checker - Checks spelling via dictionary lookups and Levenshtein distance.
Supports custom dictionaries and provides spelling suggestions.
Dependencies:
- enchant (3.2.2): Spell checking via system dictionaries (pyenchant)
- click (8.1.7): CLI framework for command-line interface
"""
import json
import os
import sys
import click
import enchant
from enchant.checker import SpellChecker as EnchantChecker
class CustomDictionary:
"""Manages a custom dictionary that persists to a JSON file."""
def __init__(self, path=None):
self.path = path or "custom_dictionary.json"
self.words = set()
self._load()
def _load(self):
"""Load custom words from the JSON file."""
if os.path.exists(self.path):
try:
with open(self.path, "r", encoding="utf-8") as f:
data = json.load(f)
self.words = set(data.get("words", []))
except (json.JSONDecodeError, IOError):
self.words = set()
def save(self):
"""Save current custom words to the JSON file."""
with open(self.path, "w", encoding="utf-8") as f:
json.dump({"words": sorted(list(self.words))}, f, indent=2)
def add_word(self, word):
"""Add a word to the custom dictionary."""
self.words.add(word.lower())
self.save()
def remove_word(self, word):
"""Remove a word from the custom dictionary."""
self.words.discard(word.lower())
self.save()
def contains(self, word):
"""Check if a word exists in the custom dictionary."""
return word.lower() in self.words
def list_words(self):
"""Return all words in the custom dictionary."""
return sorted(list(self.words))
class SpellCheckEngine:
"""
Spell checking engine using PyEnchant.
Provides dictionary lookups, Levenshtein distance suggestions,
and custom dictionary support.
"""
def __init__(self, language="en_US", custom_dict_path=None):
self.dictionary = enchant.Dict(language)
self.custom_dict = CustomDictionary(custom_dict_path)
def add_to_dictionary(self, word):
"""Add a word to the custom dictionary."""
self.custom_dict.add_word(word)
def remove_from_dictionary(self, word):
"""Remove a word from the custom dictionary."""
self.custom_dict.remove_word(word)
def get_levenshtein_distance(self, word1, word2):
"""
Calculate the Levenshtein distance between two words.
Uses dynamic programming approach.
"""
m, n = len(word1), len(word2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
dp[i][0] = i
for j in range(n + 1):
dp[0][j] = j
for i in range(1, m + 1):
for j in range(1, n + 1):
cost = 0 if word1[i - 1] == word2[j - 1] else 1
dp[i][j] = min(
dp[i - 1][j] + 1, # deletion
dp[i][j - 1] + 1, # insertion
dp[i - 1][j - 1] + cost # substitution
)
return dp[m][n]
def check_word(self, word):
"""
Check if a single word is spelled correctly.
Returns:
dict with 'word', 'correct', 'suggestions', and 'correction' keys.
"""
clean_word = word.strip().lower()
if not clean_word or not clean_word.isalpha():
return {
"word": word,
"correct": True,
"suggestions": [],
"correction": word,
}
# Check custom dictionary first
if self.custom_dict.contains(clean_word):
return {
"word": word,
"correct": True,
"suggestions": [],
"correction": word,
}
# Check with enchant
if self.dictionary.check(clean_word):
return {
"word": word,
"correct": True,
"suggestions": [],
"correction": word,
}
# Get suggestions from enchant
suggestions = self.dictionary.suggest(clean_word)
# Sort suggestions by Levenshtein distance
suggestions.sort(
key=lambda s: self.get_levenshtein_distance(clean_word, s.lower())
)
# Limit suggestions
suggestions = suggestions[:10]
correction = suggestions[0] if suggestions else word
return {
"word": word,
"correct": False,
"suggestions": suggestions,
"correction": correction,
}
def check_text(self, text):
"""
Check all words in a text string.
Returns:
dict with 'original_text', 'corrected_text', 'errors', and 'statistics'.
"""
words = text.split()
results = []
error_count = 0
corrected_words = []
for word in words:
stripped = word.strip(".,!?;:\"'()-[]{}").lower()
if not stripped or not stripped.isalpha():
corrected_words.append(word)
continue
result = self.check_word(stripped)
if not result["correct"]:
error_count += 1
results.append(result)
# Preserve surrounding punctuation
prefix = ""
suffix = ""
for ch in word:
if ch.isalpha():
break
prefix += ch
for ch in reversed(word):
if ch.isalpha():
break
suffix = ch + suffix
corrected = prefix + result["correction"] + suffix
corrected_words.append(corrected)
else:
corrected_words.append(word)
corrected_text = " ".join(corrected_words)
return {
"original_text": text,
"corrected_text": corrected_text,
"errors": results,
"statistics": {
"total_words": len(words),
"misspelled_words": error_count,
"accuracy": round(
(1 - error_count / max(len(words), 1)) * 100, 2
),
},
}
def suggest_similar(self, word, max_distance=2):
"""
Find dictionary words within a given Levenshtein distance.
Args:
word: The word to find suggestions for.
max_distance: Maximum Levenshtein distance to consider.
Returns:
List of (suggestion, distance) tuples sorted by distance.
"""
suggestions = self.dictionary.suggest(word.lower())
scored = []
for suggestion in suggestions:
dist = self.get_levenshtein_distance(word.lower(), suggestion.lower())
if dist <= max_distance:
scored.append((suggestion, dist))
scored.sort(key=lambda x: x[1])
return scored
@click.group()
@click.option(
"--custom-dict",
default=None,
help="Path to custom dictionary JSON file.",
)
@click.pass_context
def cli(ctx, custom_dict):
"""Spell Checker - Check spelling via dictionary lookups and Levenshtein distance."""
ctx.ensure_object(dict)
ctx.obj["engine"] = SpellCheckEngine(custom_dict_path=custom_dict)
@cli.command()
@click.argument("text", nargs=-1, required=True)
@click.pass_context
def check(ctx, text):
"""Check the spelling of the given text."""
engine = ctx.obj["engine"]
full_text = " ".join(text)
result = engine.check_text(full_text)
click.echo(f"\nOriginal: {result['original_text']}")
click.echo(f"Corrected: {result['corrected_text']}")
stats = result["statistics"]
click.echo(
f"Statistics: {stats['total_words']} words, "
f"{stats['misspelled_words']} errors, "
f"{stats['accuracy']}% accuracy"
)
if result["errors"]:
click.echo("\nMisspelled words:")
for err in result["errors"]:
suggestions = ", ".join(err["suggestions"][:5])
click.echo(f" '{err['word']}' -> suggestions: [{suggestions}]")
@cli.command()
@click.argument("word")
@click.option("--max-distance", "-d", default=3, help="Max Levenshtein distance.")
@click.pass_context
def suggest(ctx, word, max_distance):
"""Get spelling suggestions for a word."""
engine = ctx.obj["engine"]
suggestions = engine.suggest_similar(word, max_distance)
if suggestions:
click.echo(f"Suggestions for '{word}':")
for s, d in suggestions:
click.echo(f" {s} (distance: {d})")
else:
click.echo(f"No suggestions found for '{word}'.")
@cli.command()
@click.argument("word")
@click.pass_context
def add(ctx, word):
"""Add a word to the custom dictionary."""
engine = ctx.obj["engine"]
engine.add_to_dictionary(word)
click.echo(f"Added '{word}' to custom dictionary.")
@cli.command()
@click.argument("word")
@click.pass_context
def remove(ctx, word):
"""Remove a word from the custom dictionary."""
engine = ctx.obj["engine"]
engine.remove_from_dictionary(word)
click.echo(f"Removed '{word}' from custom dictionary.")
@cli.command(name="list")
@click.pass_context
def list_words(ctx):
"""List all words in the custom dictionary."""
engine = ctx.obj["engine"]
words = engine.custom_dict.list_words()
if words:
click.echo(f"Custom dictionary ({len(words)} words):")
for w in words:
click.echo(f" {w}")
else:
click.echo("Custom dictionary is empty.")
@cli.command()
@click.argument("word1")
@click.argument("word2")
@click.pass_context
def distance(ctx, word1, word2):
"""Calculate Levenshtein distance between two words."""
engine = ctx.obj["engine"]
d = engine.get_levenshtein_distance(word1, word2)
click.echo(f"Levenshtein distance between '{word1}' and '{word2}': {d}")
@cli.command()
@click.pass_context
def interactive(ctx):
"""Run the spell checker in interactive mode."""
engine = ctx.obj["engine"]
click.echo("=" * 60)
click.echo(" Spell Checker - Interactive Mode")
click.echo("=" * 60)
click.echo("\nCommands:")
click.echo(" check <text> - Check spelling of text")
click.echo(" suggest <word> - Get suggestions for a word")
click.echo(" add <word> - Add word to custom dictionary")
click.echo(" remove <word> - Remove word from custom dictionary")
click.echo(" list - List custom dictionary words")
click.echo(" distance <w1> <w2> - Calculate Levenshtein distance")
click.echo(" quit - Exit the spell checker")
click.echo("-" * 60)
while True:
try:
user_input = input("\n> ").strip()
except (EOFError, KeyboardInterrupt):
click.echo("\nGoodbye!")
break
if not user_input:
continue
parts = user_input.split(maxsplit=1)
command = parts[0].lower()
if command in ("quit", "exit"):
click.echo("Goodbye!")
break
elif command == "check":
if len(parts) < 2:
click.echo("Usage: check <text>")
continue
result = engine.check_text(parts[1])
click.echo(f"\nOriginal: {result['original_text']}")
click.echo(f"Corrected: {result['corrected_text']}")
stats = result["statistics"]
click.echo(
f"Statistics: {stats['total_words']} words, "
f"{stats['misspelled_words']} errors, "
f"{stats['accuracy']}% accuracy"
)
if result["errors"]:
click.echo("\nMisspelled words:")
for err in result["errors"]:
suggestions = ", ".join(err["suggestions"][:5])
click.echo(
f" '{err['word']}' -> suggestions: [{suggestions}]"
)
elif command == "suggest":
if len(parts) < 2:
click.echo("Usage: suggest <word>")
continue
word = parts[1].strip()
suggestions = engine.suggest_similar(word)
if suggestions:
click.echo(f"Suggestions for '{word}':")
for s, d in suggestions:
click.echo(f" {s} (distance: {d})")
else:
click.echo(f"No suggestions found for '{word}'.")
elif command == "add":
if len(parts) < 2:
click.echo("Usage: add <word>")
continue
word = parts[1].strip()
engine.add_to_dictionary(word)
click.echo(f"Added '{word}' to custom dictionary.")
elif command == "remove":
if len(parts) < 2:
click.echo("Usage: remove <word>")
continue
word = parts[1].strip()
engine.remove_from_dictionary(word)
click.echo(f"Removed '{word}' from custom dictionary.")
elif command == "list":
words = engine.custom_dict.list_words()
if words:
click.echo(f"Custom dictionary ({len(words)} words):")
for w in words:
click.echo(f" {w}")
else:
click.echo("Custom dictionary is empty.")
elif command == "distance":
if len(parts) < 2:
click.echo("Usage: distance <word1> <word2>")
continue
dist_parts = parts[1].split()
if len(dist_parts) < 2:
click.echo("Usage: distance <word1> <word2>")
continue
d = engine.get_levenshtein_distance(dist_parts[0], dist_parts[1])
click.echo(
f"Levenshtein distance between "
f"'{dist_parts[0]}' and '{dist_parts[1]}': {d}"
)
else:
click.echo(f"Unknown command: '{command}'. Type a command or 'quit'.")
if __name__ == "__main__":
cli()