# How the Hashline Edit Format Uses Content-Hash Anchors in Oh-My-Pi

> Learn how the hashline edit format uses content-hash anchors to make reliable atomic patches in oh-my-pi. Ensure edits target exact lines with content-derived hashes.

- Repository: [Can Bölük/oh-my-pi](https://github.com/can1357/oh-my-pi)
- Tags: internals
- Published: 2026-05-21

---

**The hashline edit format uses content-hash anchors—pairs of line numbers and two-character hashes derived solely from line content—to ensure edits target exact lines even when surrounding content changes, enabling reliable, atomic patches in the oh-my-pi coding agent.**

The **hashline** edit language is a compact, line-anchored patch format implemented in the `can1357/oh-my-pi` repository. Unlike traditional line-number-based diffs that break when preceding lines are inserted or deleted, hashline anchors remain stable because they depend only on the target line's text, not its position.

## What Are Content-Hash Anchors?

A **content-hash anchor** (also called a LID anchor) combines a line number with a content-derived hash. This pairing allows the system to verify that the line being edited matches the line the model originally saw, preventing accidental modifications to the wrong text.

### Anchor Structure and Format

Each anchor follows the pattern `LINE+HASH`, producing strings like `42sr`:

```

42sr   → line 42, hash "sr"

```

The two-character hash is generated by `computeLineHash()` in [[`hash.ts`](https://github.com/can1357/oh-my-pi/blob/main/hash.ts)](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/hashline/hash.ts#L48-L57). Because the hash derives exclusively from the line's cleaned text, the anchor stays valid even if surrounding lines shift, provided the line's own content remains unchanged.

### The Hash Computation Algorithm

The `computeLineHash()` function implements a deterministic hashing pipeline designed for stability:

```ts
// hash.ts – core of the anchor generation
export function computeLineHash(idx: number, line: string): string {
    void idx;                              // line number is ignored for stability
    line = line.replace(/\r/g, "").trimEnd();
    return HL_BIGRAMS[Bun.hash.xxHash32(line, 0) % HL_BIGRAMS_COUNT];
}

```

The algorithm executes four steps:

1. **Normalize** the line by stripping `\r` and trailing whitespace
2. **Hash** the cleaned string using **xxHash32** with a fixed seed of `0`
3. **Select** a bigram by taking the modulo of `HL_BIGRAMS_COUNT` (647 pre-generated English letter pairs)
4. **Return** the two-character bigram as the hash suffix

## Where Anchors Appear in the Hashline Format

Anchors serve dual roles: they provide human-readable file displays and act as machine-verifiable edit targets.

### File Headers and Display Format

When displaying a file to the model, the system prefixes each line with its anchor using `formatHashLine()` in [[`hash.ts`](https://github.com/can1357/oh-my-pi/blob/main/hash.ts)](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/hashline/hash.ts#L70-L94):

```ts
export function formatHashLine(lineNumber: number, line: string): string {
    return `${lineNumber}${computeLineHash(lineNumber, line)}${HL_BODY_SEP}${line}`;
}

```

This produces output where `HL_BODY_SEP` (the pipe character `|`) separates the anchor from the content:

```

1bm|function hi() {
2er|    return;
3ab|}

```

### Diff Operations and Validation

Every edit operation (`+`, `<`, `-`, `=`) in a hashline diff must reference a **full anchor** (`LINE+HASH`). The parser validates these anchors using the `LID_CAPTURE_RE` regular expression defined in [[`parser.ts`](https://github.com/can1357/oh-my-pi/blob/main/parser.ts)](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/hashline/parser.ts#L5-L16):

```ts
const LID_CAPTURE_RE = new RegExp(`^${HL_HASH_CAPTURE_RE_RAW}$`);
function parseLid(raw: string, lineNum: number): Anchor {
    const match = LID_CAPTURE_RE.exec(raw);
    if (!match) throw new Error(`line ${lineNum}: expected a full anchor …`);
    return { line: Number.parseInt(match[1], 10), hash: match[2] };
}

```

## How the Engine Validates Anchors

Before applying any mutations, the system verifies that all anchors in the patch still match the current file content.

### Re-hashing and Mismatch Detection

The `applyHashlineEdits` function in [[`apply.ts`](https://github.com/can1357/oh-my-pi/blob/main/apply.ts)](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/hashline/apply.ts) re-hashes the target file line-by-line during application. It compares the freshly computed hash against the anchor in the patch:

- **Match**: The line is confirmed as the correct target
- **Mismatch**: A `HashlineMismatchError` is thrown, aborting the operation

This verification happens *before* any write operations, ensuring that partial edits cannot corrupt the file.

### Atomic Application Guarantees

Because all anchors are validated prior to mutation, the hashline format provides **atomicity**: either every anchor matches and the entire edit succeeds, or the operation fails without modifying the file. This prevents scenarios where a line number shift causes an edit to land on the wrong line mid-patch.

## Recovering from Stale Anchors

When external tools modify a file after the model has read it, anchors may become stale (the line numbers shift while content remains). The system implements automatic recovery via caching.

### The Read-Snapshot Cache Mechanism

The coding-agent maintains a **read-snapshot cache** (`FileReadCache`) that stores previously seen file versions. When `executeHashlineSingle` in [[`execute.ts`](https://github.com/can1357/oh-my-pi/blob/main/execute.ts)](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/hashline/execute.ts#L73-L118) detects a hash mismatch, it invokes `tryRecoverHashlineWithCache()` from [[`recovery.ts`](https://github.com/can1357/oh-my-pi/blob/main/recovery.ts)](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/hashline/recovery.ts#L89-L107):

1. The system attempts to apply the patch against the cached snapshot where the anchor was valid
2. If successful, the edit is committed and a warning is emitted
3. If recovery fails, the original `HashlineMismatchError` bubbles up to the caller

This recovery path allows the model to succeed even when the filesystem has drifted from the expected state.

## Why Two-Character Hashes?

The hashline format specifically uses two-character hashes from a set of 647 bigrams (`HL_BIGRAMS`) for three reasons:

* **Stability**: The bigram set is frozen forever; changing it would invalidate all stored anchors
* **Token efficiency**: Each bigram tokenizes as a **single token** in modern BPE vocabularies (Claude, GPT-4o), keeping the anchor as a one-token feature in LLM prompts
* **Collision resistance**: The ~10 bits of entropy provided by 647 possibilities makes accidental collisions extremely unlikely for normal source files while keeping the format compact

## Practical Code Examples

### Generating a Hashline View

```ts
import { formatHashLines } from "@oh-my-pi/pi-coding-agent/hashline";

const source = `function greet(name) {\n    return "hi " + name;\n}\n`;
console.log(formatHashLines(source));
/* Output:
1bm|function greet(name) {
2er|    return "hi " + name;
3ab|}
*/

```

*Source*: [[`hash.ts`](https://github.com/can1357/oh-my-pi/blob/main/hash.ts) – `formatHashLines`](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/hashline/hash.ts#L85-L94)

### Applying an Edit with Anchor Verification

```ts
const diff = `
= 2er..2er
~return "hello " + name;
`.trim();

const result = applyHashlineEdits(source, parseHashline(diff));
console.log(result.lines);
/* → 
1bm|function greet(name) {
2er|return "hello " + name;
3ab|}
*/

```

This example uses `parseHashline` ([[`parser.ts`](https://github.com/can1357/oh-my-pi/blob/main/parser.ts)](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/hashline/parser.ts#L47-L55)) to validate the `2er` anchor, then `applyHashlineEdits` ([[`apply.ts`](https://github.com/can1357/oh-my-pi/blob/main/apply.ts)](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/hashline/apply.ts#L640)) recomputes the hash for line 2 to confirm the match before replacing the payload.

## Summary

- **Content-hash anchors** combine line numbers with two-character hashes derived from xxHash32 output, creating stable references that survive line insertions and deletions elsewhere in the file
- **`computeLineHash()`** in [`hash.ts`](https://github.com/can1357/oh-my-pi/blob/main/hash.ts) generates these anchors by normalizing line text and selecting from 647 stable bigrams
- **Validation** occurs in `applyHashlineEdits` before any mutations, ensuring atomic operations that either fully succeed or fail without side effects
- **Stale-anchor recovery** uses the `FileReadCache` to replay edits against cached snapshots when external modifications cause hash mismatches
- The two-character format optimizes for LLM tokenization while providing sufficient entropy to prevent collisions in typical codebases

## Frequently Asked Questions

### What happens if a line's content changes after the model sees it?

If the line content changes, the computed hash will not match the anchor in the patch, causing `applyHashlineEdits` to throw a `HashlineMismatchError`. The system will then attempt to recover using the read-snapshot cache if the original version was cached; otherwise, the edit fails and must be regenerated by the model.

### Why does hashline use bigrams instead of hex or base64 encoding?

The 647 English-letter bigrams are specifically chosen because they tokenize as single tokens in modern BPE models like Claude and GPT-4o, reducing prompt size, while shorter encodings like hex would require two tokens per hash. This design keeps the anchor as a compact, model-efficient feature.

### How does the system handle Windows-style line endings (\r\n)?

The `computeLineHash()` function explicitly strips `\r` characters and trailing spaces before hashing, ensuring that line endings do not affect the hash. This normalization guarantees that anchors remain consistent across different operating systems and editor configurations.

### Can I use hashline edits without the oh-my-pi coding agent?

While the hashline format is designed for the oh-my-pi coding agent, the standalone functions in the `hashline` package (`parseHashline`, `applyHashlineEdits`, `formatHashLines`) can be imported and used independently. However, the stale-anchor recovery mechanism requires the `FileReadCache` infrastructure provided by the agent's execution context.