The hashline edit subsystem provides cryptographically-validated file editing for agent operations. It implements a **read-side annotation + write-side validation** pattern that prevents stale edits and ensures edits are always applied to the correct line content.
Installs into .claude/skills of the current project.
Are you the author of 2498 Hashline Subsystem 921790b7?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tools-only-2498-hashline-subsystem-921790b7)
---
title: Hashline Edit Subsystem
summary: Cryptographically-validated file editing with content-hash line tagging and in-memory cache validation
read_when:
- You need to understand how file edits are validated
- You're implementing a new file editing tool
- Debugging hash mismatch errors in file edits
- Working on the read_file → hashline_edit workflow
depends_on:
- docs/modules/tools/index.md
- docs/modules/core/ui_api.md
feeds_into:
- docs/modules/tools/read_file.md
- docs/modules/tools/file_editing.md
---
# Hashline Edit Subsystem
## What
The hashline edit subsystem provides cryptographically-validated file editing for agent operations. It implements a **read-side annotation + write-side validation** pattern that prevents stale edits and ensures edits are always applied to the correct line content.
Each line read by the agent is tagged with a short MD5 content hash (2 hex characters = 256 buckets). When the agent requests an edit, the hash is validated against the cached file state. If the hash doesn't match, the edit is rejected with a clear error message instructing the model to re-read the file.
### Key Features
- **Content-hash validation**: Every line is validated before editing
- **Three edit operations**: `replace`, `replace_range`, `insert_after`
- **Side-by-side diff visualization**: NeXTSTEP-style panel with before/after view
- **Automatic cache updates**: Cache stays synchronized after every edit
- **Unified diff output**: Standard diff format for review and verification
### Workflow Flow Diagram
```
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│ │ │ │ │ │ │ │
│ read_file │────▶│ hashline.py │────▶│ line_cache │────▶│ hashline_edit│
│ │ │ (tag lines) │ │ (store) │ │ (validate) │
└─────────────┘ └──────────────┘ └─────────────┘ └──────┬───────┘
│
┌──────────────┐ │
│ │◄──────────────────────────────┘
│ File │ (write changes)
│ │
└──────────────┘
│
▼
┌──────────────┐
│ Diff │────▶ UI Renderer
│ Output │ (side-by-side)
└──────────────┘
```
## Key Files
### Core Components
| File | Purpose |
|------|---------|
| `src/tunacode/tools/hashline.py` | Content-hash line tagging and validation primitives |
| `src/tunacode/tools/line_cache.py` | In-memory cache for hashed lines keyed by file path |
| `src/tunacode/tools/hashline_edit.py` | Main edit tool with three operations (replace, replace_range, insert_after) |
| `src/tunacode/tools/read_file.py` | File reading that populates cache with hash-tagged output |
| `src/tunacode/ui/renderers/tools/hashline_edit.py` | Side-by-side diff display with NeXTSTEP panel styling |
### File Relationships
```
read_file.py
│
├──▶ hashline.py
│ ├── tag_lines()
│ ├── content_hash()
│ └── format_hashline()
│
└──▶ line_cache.py (store)
│
│ ▲
│ │ (validate & update)
▼ │
hashline_edit.py
│
├── _validate_ref() ──▶ line_cache.py (get_line, validate_ref)
├── _apply_replace()
├── _apply_replace_range()
├── _apply_insert_after()
│
└──▶ UI renderer ────▶ hashline_edit_renderer.py
```
## How
### 1. Content Hashing (`hashline.py`)
Each line is hashed using MD5 (2 hex characters = 256 buckets), giving enough entropy to detect stale references without bloating the context window.
```python
HASH_LENGTH = 2
LINE_HASH_SEPARATOR = ":"
HASH_SEPARATOR = "|"
# Example output format
# 1:a3|function hello() {
# 2:f1| console.log("world");
# 3:0e|}
```
**Key Functions:**
| Function | Purpose |
|----------|---------|
| `content_hash(line)` | Compute MD5 hash of line content (2 hex chars) |
| `tag_lines(content, offset)` | Split content and tag each line with hash |
| `format_hashline(HashedLine)` | Format as `line:hash|content` string |
| `format_hashlines(content)` | Tag all lines in content for display |
| `parse_line_ref(ref)` | Parse `line:hash` reference, e.g., `"2:f1"` |
### 2. Line Cache (`line_cache.py`)
Stores `{path: {line_number: HashedLine}}` as a module-level singleton. The cache is replaced (not merged) on each `read_file` call.
**Cache Operations:**
| Function | Purpose |
|----------|---------|
| `store(filepath, lines)` | Cache hashed lines for a file |
| `get(filepath)` | Return read-only view of cached lines |
| `get_line(filepath, line_number)` | Return single cached line or None |
| `validate_ref(filepath, line, expected_hash)` | Check if line hash matches cached state |
| `update_lines(filepath, updates)` | Update cached lines after an edit |
| `replace_range(filepath, start, end, new_lines)` | Replace a range and re-number subsequent lines |
| `invalidate(filepath)` | Remove cached state for a file |
| `clear()` | Clear entire cache (testing) |
### 3. Edit Tool (`hashline_edit.py`)
Three edit operations validate against the cache before applying changes.
**Validation Flow:**
```
┌─────────────────┐
│ hashline_edit │
│ (operation) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ _validate_ref │──── Parse "line:hash" reference
│ │──── Check file is cached
│ │──── Verify line exists in cache
│ │──── Compare expected vs actual hash
└────────┬────────┘
│
┌────┴────┐
│ │
▼ ▼
┌──────┐ ┌──────┐
│ Pass │ │ Fail │
└──┬───┘ └──┬───┘
│ │
▼ ▼
┌──────┐ ┌─────────────┐
│ Apply│ │ ToolRetryError│
│ Edit │ │ "hash mismatch│
└──┬───┘ │ Re-read..." │
│ └─────────────┘
▼
┌──────────┐
│ Update │
│ Cache │
└──────────┘
```
**Three Operations:**
| Operation | Parameters | Description |
|-----------|------------|-------------|
| `replace` | `line`, `new` | Replace a single line identified by hash |
| `replace_range` | `start`, `end`, `new` | Replace contiguous range of lines |
| `insert_after` | `after`, `new` | Insert new lines after a referenced line |
**Error Messages:**
```python
STALE_REF_MESSAGE = (
"File has changed since last read — line {line} hash mismatch "
"(expected '{expected}', cached '{actual}'). Re-read the file first."
)
UNCACHED_FILE_MESSAGE = "File '{filepath}' has no cached state. Read the file first with read_file."
LINE_NOT_CACHED_MESSAGE = (
"Line {line} is not in the cached state for '{filepath}'. "
"The file may have been read with an offset that skipped this line. "
"Re-read the file to include this line."
)
```
### 4. Read Integration (`read_file.py`)
`read_file` populates the line cache with every read, enabling `hashline_edit` to validate references.
```python
# Format: line:hash|content
# Example output:
1:a3|def main():
2:f1| pass
3:0e|
```
**Key Points:**
- Each `read_file` call **replaces** the cache for that file
- Paginated reads do not merge prior cache windows
- `hashline_edit` can only edit lines present in the current cache
- Missing lines cause `ToolRetryError` with offset hints
### 5. UI Renderer (`hashline_edit_renderer.py`)
NeXTSTEP-style panel with four zones:
```
┌─────────────────────────────────────────────┐
│ filename.js +3 -2 ← Zone 1: Header│
│ │
│ /path/to/filename.js ← Zone 2: Path│
│ ───────────────────────────── │
│ 1│function old() { │ 1│function new() { ← Zone 3: Side-by-side
│ - 2│ return old; │+2│ return new; │ diff viewport
│ 3│} │ 3│} │
│ ───────────────────────────── │
│ │
│ 1 hunk [45ms] ← Zone 4: Status │
└─────────────────────────────────────────────┘
```
**Rendering Pipeline:**
```
hashline_edit result (unified diff text)
│
▼
parse_result()
│
├──▶ Extract filepath
├──▶ Count additions (+)
├──▶ Count deletions (-)
└──▶ Count hunks (@@)
│
▼
_parse_side_by_side_rows()
│
├──▶ Parse hunk headers (@@ -1,3 +1,3 @@)
├──▶ Categorize lines: context, insert, delete, meta
└──▶ Build DiffSideBySideLine objects
│
▼
_build_side_by_side_viewport()
│
├──▶ Calculate column widths
├──▶ Apply syntax highlighting styles
└──▶ Build Rich Table with gutters
```
## Why
### Prevents Stale Edits
Without hash validation, an agent might reference a line that was:
- Deleted by another process
- Modified by a previous edit
- Never actually existed at that line number
The hash acts as a **content-addressed pointer**. If the content changes, the hash changes, and the edit is rejected before touching the filesystem.
### Provides Clear Edit Visualization
Side-by-side diff makes changes explicit:
```
Before │ After
──────────────────────────┼──────────────────────────
1│old_function() { │ 1│new_function() {
- 2│ return old_value; │+ 2│ return new_value;
3│} │ 3│}
```
### Enables Safe Concurrent Editing
The cache is session-local and updated immediately after each edit. This allows:
1. Multiple edits to the same file in sequence
2. Cache stays synchronized with filesystem
3. Next edit validates against updated hashes
### Minimal Overhead
- 2 hex characters = 256 buckets (0.4% collision probability)
- Cache is in-memory only (no disk I/O)
- Unified diff output is standard and reviewable
## Usage Example
```python
# 1. Read file (populates cache)
result = await read_file("/path/to/file.py")
# Output: 1:a3|def main():
# 2:f1| pass
# 2. Edit a line using hash reference
result = await hashline_edit(
filepath="/path/to/file.py",
operation="replace",
line="2:f1", # Line 2, hash f1
new=" print('hello')"
)
# 3. If hash mismatches (file changed)
# Raises: ToolRetryError("File has changed since last read...")
```
## Related Documentation
- `docs/modules/tools/read_file.md` - File reading and cache population
- `docs/modules/tools/file_editing.md` - General file editing patterns
- `docs/modules/core/ui_api.md` - Panel rendering and UI constants