# Tigerfs

> How to discover, read, write, and search data in a TigerFS-mounted PostgreSQL database using file tools.

- Skill: `timescale/tigerfs` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add timescale/tigerfs`
- Raw SKILL.md: https://api.skillmd.com/api/skills/timescale/tigerfs/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: timescale (https://skillmd.com/u/timescale)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/timescale/tigerfs

---


# Using TigerFS

TigerFS mounts PostgreSQL databases as directories. You interact with data using Read, Write, Glob, and Grep -- no SQL needed. **File-first** mode gives you a transactional, shareable filesystem backed by a database. **Data-first** mode lets you explore and manipulate an existing database using file tools. Most work uses file-first.

## Which Mode?

Each directory in a TigerFS mount is either file-first or data-first:

- **File-first:** Contains `.md` or `.txt` files. Read and write files normally. See [files.md](files.md).
- **Data-first:** Contains `.info/` directory. Access rows as files or directories. See [data.md](data.md).

## Directory Structure

```
mount/
├── notes/                  # File-first (markdown workspace)
│   ├── hello.md
│   ├── tutorials/          # Subdirectories: ls shows only real files; .history/<file>/ remains path-accessible
│   ├── .history/           # Versioned history (root level only)
│   ├── .log/               # Operation log (root level only; pipeline hidden from ls)
│   ├── .savepoint/         # Named bookmarks for undo (root level only)
│   └── .undo/              # Preview and apply undo operations (root level only)
├── snippets/               # File-first (plain text workspace)
│   └── bash-loop.txt
├── .tables/                # Backing tables in tigerfs schema
│   └── notes/              # Data-first access to notes backing table
├── users/                  # Data-first (standalone table)
│   ├── .info/
│   ├── .by/
│   └── 1/  2/  3/ ...
├── .info/                  # Mount-level metadata (user identity)
└── .build/                 # Create new workspaces
```

## File-First

A transactional, shareable filesystem backed by a database. Multiple users and agents can read and write concurrently. Create workspaces with `.build/`:

```bash
Bash "echo 'markdown' > mount/.build/notes"
Bash "echo 'markdown,history' > mount/.build/notes"    # with versioned history
Bash "echo 'plaintext' > mount/.build/snippets"         # body-only, no frontmatter
```

See [files.md](files.md) for full details on schemas, column roles, directories, and history.

### What you can build

Because the filesystem is transactional and shared, it can implement collaborative workflows. Follow the recipe exactly:

| If asked to... | Follow |
|---|---|
| Create a task list, kanban, todo, or project tracker | [recipes.md](recipes.md) Recipe 4. **Core rule: directories = states (`todo/`, `doing/`, `done/`); `mv` = transitions; do NOT use `status` frontmatter.** |
| Create a knowledge base, wiki, or documentation store | [recipes.md](recipes.md) Recipe 5 |
| Save or resume session context | [recipes.md](recipes.md) Recipe 6 |
| Keep a log of what you do | [recipes.md](recipes.md) Recipe 7 |
| Revert, roll back, or undo changes | See Safe Editing below and [files.md](files.md) |

## Safe Editing with Savepoints

Before making multiple or risky edits, always create a savepoint:

```bash
Bash "echo '{\"description\":\"Before investigating bug #42\"}' > mount/workspace/.savepoint/before-investigation.json"
```

**When to create a savepoint:** investigating a bug, debugging, refactoring, multi-file edits, or any uncertain operation.

**When to undo:** user asks to revert, agent realizes the approach isn't working, or changes were made to wrong files.

**Before undoing:** always preview first and get user confirmation. Undo is destructive.

**Undo is undoable:** undo operations are logged (type='undo'), so you can undo an undo. Create a savepoint before a major undo for extra safety -- if the result isn't what was expected, undo back to that savepoint.

### Common Workflows

**"Create a savepoint"**
```
Bash "echo '{\"description\":\"<why>\"}' > mount/workspace/.savepoint/<name>.json"
```

**"What changed since the savepoint?"**
1. Read the summary: `Read "mount/workspace/.undo/to-savepoint/<name>/.info/summary"`
2. If <= 5 files affected: for each file, read its before and current state, summarize cumulative changes in English
3. If > 5 files: present the summary table (type, filename, user, timestamp)
4. If user wants raw diffs: `Bash "cd mount/workspace && diff -ru .undo/to-savepoint/<name> . -x '.*'"`

**"What changed in this file?"**
1. Get the file's stable UUID: `Read "mount/workspace/<dir>/.history/<file>/.id"` (for a root-level file: `Read "mount/workspace/.history/<file>/.id"`)
2. Find recent edits: `Read "mount/workspace/.log/.by/file_id/<uuid>/.last/5/.export/json"`
3. For each entry, read before and after, compare them, summarize in English.
4. Present with log_ids so the user can pick one to undo. Note: a single logical edit by an agent may produce multiple log entries (see [How agent writes appear in the log](#how-agent-writes-appear-in-the-log)).

**"Show me the diff"**
- Savepoint: `Bash "cd mount/workspace && diff -ru .undo/to-savepoint/<name> . -x '.*'"`
- Single file: `Bash "diff -u --color mount/workspace/.log/<id>/before mount/workspace/.log/<id>/after"`

**"Undo to the savepoint"**
1. Read the summary: `Read "mount/workspace/.undo/to-savepoint/<name>/.info/summary"`
2. If <= 5 files: summarize cumulative changes per file in English
3. Present to user: "This will undo N changes: [summary]. Go ahead?"
4. Only if confirmed: `Bash "touch mount/workspace/.undo/to-savepoint/<name>/.apply"`

**"Undo this one change"**
1. Read the log entries around the change. If multiple entries belong to the same logical write (same `file_id`, adjacent timestamps), each must be undone — see [How agent writes appear in the log](#how-agent-writes-appear-in-the-log).
2. Read the log entry summary: `Read "mount/workspace/.undo/id/<log_id>/.info/summary"`
3. Show the diff: `Bash "diff -u --color mount/workspace/.log/<id>/before mount/workspace/.log/<id>/after"`
4. Present to user: "This will revert [description]. Go ahead?"
5. Only if confirmed: `Bash "touch mount/workspace/.undo/id/<log_id>/.apply"`. For a multi-entry group, apply each entry's undo (newest log_id first) (or `touch .undo/to-id/<id-before-the-group>/.apply` to reverse the whole group atomically).

### How agent writes appear in the log

Different write methods produce different numbers of log entries:

- **Atomic-rename writes** (Claude's `Write`/`Edit`, many editors): the operation produces multiple adjacent log entries. Typically: a `create` of a temp file like `<file>.tmp.<pid>.<hash>`, then for an overwrite a `delete` of the existing target, then a `rename` of the temp to the final name. That's **2 entries for a new file, 3 for an overwrite**. The create and rename share the temp file's `file_id`; the delete (when present) carries the original target's `file_id`.
- **Direct writes** (shell redirects like `echo "..." > file`, in-place editors): a single `create` (new file) or `edit` (existing file) log entry. No temp file, no rename.

To revert a logical operation, find its group of entries, then undo them:

1. Read the log newest-first: `Read "mount/workspace/.log/.last/8/.export/json"`.
2. Anchor on the `rename` to your target filename -- its `file_id` is the new file. The paired `create` shares that **same `file_id`** (filename `<basename>.tmp.<pid>.<hash>`); the `delete` (overwrites only) is the adjacent entry with the target filename but a *different* `file_id`. Checks: create+rename share a `file_id`; count is 2 (new file) or 3 (overwrite). `filename` is not a `.by/`-indexed column, so don't filter by it -- to get the id without scanning, read `.history/<file>/.id`, then query `.log/.by/file_id/<id>/`.
3. Undo each entry `touch .undo/id/<log_id>/.apply`, **newest first** -- order is mandatory; each step sets up the next. **Out of order is destructive**: `RPC struct is bad` on the stale `.tmp.*`, or silent deletion of the live file.

Shortcut: when the group is the latest change, `touch .undo/to-id/<id-just-before-it>/.apply` reverses it atomically in order. Never revert by re-typing content with `Write`/`Edit` -- that adds another rename group and loses the audit trail.

For advanced cases (filtered undo, per-user undo, pipeline queries), construct paths directly from [files.md](files.md). See [recipes.md](recipes.md) Recipes 1-3 for complete workflow patterns.

## Data-First

Direct access to database rows as files and directories. Use when you need column-level access, index lookups, bulk export, or structured data processing.

```
Read "mount/users/.info/count"                          # Row count
Read "mount/users/1.json"                               # Row as JSON
Read "mount/users/1/email"                              # Single column
Glob "mount/users/.by/email/alice@example.com/*"        # Index lookup
Read "mount/users/.by/status/active/.export/json"       # Filtered export
```

### Access Strategy by Table Size

| Size | Strategy |
|------|----------|
| **~100 rows or less** | Glob patterns and row-as-directory access are fine |
| **100 - 1,000 rows** | Prefer `.export/` over reading individual rows to avoid 1 query per row. Use `.by/`, `.filter/`, `.first/`, `.last/`, `.sample/` for selective access when possible |
| **1,000+ rows** | Large tables are limited to 1,000 rows by default; use `.all/` if you actually need all rows. Strongly prefer `.export/` over reading individual rows. Use `.by/`, `.filter/`, `.first/`, `.last/`, `.sample/` for selective access whenever possible |

Always check `.info/count` first to choose the right strategy.

See [data.md](data.md) for the full reference.

## Quick Reference

| Goal | Tool Call |
|------|-----------|
| **File-First** | |
| List files | `Glob "mount/workspace/*.md"` or `Glob "mount/workspace/**/*.md"` (recursive) |
| Read file | `Read "mount/workspace/file.md"` |
| Write file | `Write "mount/workspace/file.md"` with content |
| Delete file | `Bash "rm mount/workspace/file.md"` |
| Search | `Grep pattern="term" path="mount/workspace/"` |
| History versions | `Glob "mount/workspace/.history/file.md/*"` |
| Read old version | `Read "mount/workspace/.history/file.md/<timestamp>"` |
| **Savepoints & Undo** | |
| Create savepoint | `Bash "echo '{\"description\":\"Before refactoring\"}' > mount/workspace/.savepoint/name.json"` |
| Diff all changes since savepoint | `Bash "cd mount/workspace && diff -ru .undo/to-savepoint/name . -x '.*'"` |
| File drift since a change | `Bash "diff -u --color mount/workspace/.log/<id>/before mount/workspace/.log/<id>/current"` |
| View recent log | `Read "mount/workspace/.log/.last/10/.export/json"` |
| **Data-First** | |
| Row count | `Read "mount/t/.info/count"` |
| Schema / columns | `Read "mount/t/.info/schema"` or `.info/columns` |
| Read row | `Read "mount/t/pk.json"` or `Read "mount/t/pk/column"` |
| Read multiple rows (small) | `Glob "mount/t/*.json"` then read individually |
| Read multiple rows (medium/large) | `Read "mount/t/.export/tsv"` (also `.export/json`, `.csv`, `.yaml`) |
| Navigate rows | `Glob "mount/t/.first/N/*"`, `.last/N/*`, or `.sample/N/*` |
| Index lookup | `Glob "mount/t/.by/col/val/*"` |
| Filtered export | `Read "mount/t/.by/col/val/.export/json"` |
| Update | `Write "mount/t/pk/col"` or `Write "mount/t/pk.json"` (PATCH) |
| Insert row | `Write "mount/t/new.json"` with JSON |
| Delete row | `Bash "rm mount/t/pk"` |

## Directory Scanning Safety

**Virtual directory layout:** `.history/`, `.log/`, `.savepoint/`, `.undo/` only appear in `ls` at the **workspace root** level, not inside subdirectories. Subdirectories' `ls` shows only real files; `.history/<file>/` and `.history/<file>/.id` remain path-accessible from subdirectories.

**Pipeline capabilities (`.by/`, `.filter/`, `.order/`, `.export/`, etc.) behave differently depending on where they appear:**
- **Inside `.log/` or `.savepoint/`**: accessible by explicit path but hidden from `ls` (prevents recursive scanner blowup on exponential pipeline branching).
- **At a file-first workspace root or in a workspace subdirectory**: rejected with `ErrInvalidPath` and a hint pointing at `mount/.tables/<workspace>/` — that's the entry point for data-first access to a workspace's backing table.
- **On a standalone data-first table** (e.g., `mount/users/`) or under `mount/.tables/<workspace>/`: fully accessible.

**Never recursively scan these directories** -- always use targeted access patterns from the Quick Reference above:
- `.log/` -- pipeline capabilities hidden from listing, use `.log/.last/10/.export/json`
- `.savepoint/` -- same as `.log/`, use explicit paths
- `.history/` -- one entry per file version; grows with every edit
- `.undo/` -- preview trees that mirror the affected file hierarchy
- `.by/<column>/` -- lists every distinct value; each expands to filtered rows
- `.filter/<column>/` -- same as `.by/` with higher limit
- `.export/` -- reading files triggers full table/query dumps
- `.import/` -- write interface; scanning could trigger unintended operations

**Safe to scan:**
- Regular files and subdirectories in a workspace (e.g., `Glob "mount/workspace/**/*.md"`)
- `.info/` -- small, fixed set of metadata files

## Anti-Patterns

| Don't | Do Instead |
|-------|------------|
| **File-First** | |
| Put `status:` in frontmatter to track state | Use directories as states (`todo/`, `doing/`, `done/`), `mv` to transition |
| Use `Write` or `Edit` to restore prior file content -- even for a single file. The tool's write-temp-then-rename pattern creates 2-3 new log entries instead of reverting the originals. | Use TigerFS's undo machinery: `.undo/to-savepoint/<name>/.apply` for groups of changes, `.undo/id/<log_id>/.apply` for one operation. See [How agent writes appear in the log](#how-agent-writes-appear-in-the-log) for grouping multi-entry writes. |
| Undo without previewing first | Always read `.info/summary` and get user confirmation before applying |
| **Data-First** | |
| Read individual rows in a loop for large tables | Use `.export/json` or `.export/csv` for bulk access |
| Write full row JSON expecting replace semantics | JSON/CSV/TSV writes are **PATCH** -- only specified keys update |
| `Grep` across all rows of a large table | Use `.by/` index lookups for indexed columns |
| Glob a data-first directory without checking size | Read `.info/count` first, choose strategy based on table size |

## Database Management

When asked to **create, mount, fork, or manage a database or filesystem**, see [ops.md](ops.md) for tigerfs CLI commands.

## Detailed References

- [files.md](files.md) -- File-first: workspaces with files and directories, and history
- [data.md](data.md) -- Data-first: row-as-file, row-as-directory, metadata, indexes, pipeline queries
- [recipes.md](recipes.md) -- Recipes: kanban boards, knowledge bases, session context, snippets, safe exploration, compare approaches, multi-agent undo
- [ops.md](ops.md) -- Operations: mount, create, fork, status, unmount

