# Understanding loop-session.lock.d vs Regular Session Locking in Claude-Code-Harness

> Discover the differences between loop-session.lock.d and legacy session locking in Claude-Code-Harness. Learn how meta.json enhances idempotency and WIP warnings for improved session management.

- Repository: [Chachamaru/claude-code-harness](https://github.com/Chachamaru127/claude-code-harness)
- Tags: deep-dive
- Published: 2026-05-28

---

**The `loop-session.lock.d` directory-based lock stores session metadata in a [`meta.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/meta.json) file to enable precise idempotency guards and selective WIP warning suppression, while the legacy `loop-session.lock` file acts as a simple binary flag that blocks all subsequent invocations regardless of session ownership.**

Claude-Code-Harness employs two distinct locking mechanisms to prevent concurrent execution of long-running harness-loop sessions. Understanding the difference between the modern directory-based `loop-session.lock.d` approach and the legacy file-based `loop-session.lock` method is crucial for implementing proper session management and avoiding race conditions in CI/CD pipelines.

## Directory-Based Locking: The loop-session.lock.d Approach

The current implementation uses a directory structure located at `./.claude/state/locks/loop-session.lock.d/` to manage exclusive access to harness-loop sessions. This approach provides rich session context through embedded metadata.

### Structure and Metadata Storage

Unlike simple file locks, the directory-based mechanism contains a [`meta.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/meta.json) file that stores the active session identifier. According to the flow documentation in [`skills/harness-loop/references/flow.md`](https://github.com/Chachamaru127/claude-code-harness/blob/main/skills/harness-loop/references/flow.md) (line 49), this structure enables the harness to distinguish between different session owners.

The metadata payload typically includes:

```json
{
  "session_id": "2026-05-28-01-abcdef"
}

```

This JSON structure allows the **PreCompact** hook and Go implementation to perform intelligent session comparison rather than merely checking for file existence.

### Creation and Detection Logic

To acquire the directory-based lock, the harness creates the directory structure and writes the session metadata:

```bash

# Acquire lock at loop start

LOCK_DIR=".claude/state/locks/loop-session.lock.d"
mkdir -p "$LOCK_DIR"

# Store session metadata

echo '{"session_id":"2026-05-28-01-abcdef"}' > "$LOCK_DIR/meta.json"

```

Detection combines directory existence checks with session ID validation:

```bash

# Verify lock ownership

if [ -d "$LOCK_DIR" ]; then
  LOCK_SESSION=$(jq -r '.session_id' "$LOCK_DIR/meta.json")
  if [ "$LOCK_SESSION" = "$CURRENT_SESSION_ID" ]; then
    # Same session - suppress WIP warnings and exit early

    exit 0
  fi
fi

```

### Implementation in PreCompact Hooks

The Go implementation in [`go/cmd/harness/pre_compact.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/cmd/harness/pre_compact.go) (line 187) reads the [`meta.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/meta.json) file to determine whether to emit WIP warnings or return early. This logic enables selective suppression of pre-compact warnings when the lock belongs to the active loop, a feature impossible with the legacy file-based approach.

## File-Based Locking: The Legacy loop-session.lock Method

The original implementation used a plain file at `./.claude/state/locks/loop-session.lock` as a simple mutex. This mechanism persists primarily in test suites but lacks the sophistication required for modern session management.

### Legacy Implementation Characteristics

The file-based lock functions as a binary semaphore:

```bash

# Legacy lock acquisition

LOCK_FILE=".claude/state/locks/loop-session.lock"
mkdir -p "$(dirname "$LOCK_FILE")"
> "$LOCK_FILE"  # Creates empty file

```

Since this file contains no embedded session identifier—sometimes only an empty JSON object or no content at all—any existing lock blocks **all** subsequent invocations, even those originating from the same session.

### Current Usage in Tests

The legacy lock remains relevant in [`tests/test-harness-loop-guard.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/tests/test-harness-loop-guard.sh) (line 11), where it simulates the original exclusive-run guard. This test script validates basic mutual exclusion but does not exercise the session-aware logic required for production harness-loop flows.

## Key Differences Compared

| Feature | `loop-session.lock.d` (Directory) | `loop-session.lock` (File) |
|---------|-----------------------------------|----------------------------|
| **Storage** | Directory containing [`meta.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/meta.json) | Plain file (often empty) |
| **Session Tracking** | Stores `session_id` for ownership validation | No session metadata |
| **Idempotency** | Recognizes same-session re-entry | Blocks all invocations uniformly |
| **Use Case** | Production harness-loop sessions | Legacy tests and early versions |
| **Location** | `.claude/state/locks/loop-session.lock.d/` | `.claude/state/locks/loop-session.lock` |

The directory-based approach enables **precise idempotency guards** and **selective WIP warning suppression**, while the file-based approach provides only a simple "is someone running?" flag without ownership context.

## Practical Implementation Examples

### Acquiring the Directory-Based Lock

Production code should implement the directory-based pattern as defined in the harness-loop flow:

```bash
#!/bin/bash
set -euo pipefail

LOCK_DIR=".claude/state/locks/loop-session.lock.d"
SESSION_ID="${HARNESS_SESSION_ID:-$(date +%Y%m%d-%H%M%S)}"

# Create directory lock

mkdir -p "$LOCK_DIR"

# Write metadata

cat > "$LOCK_DIR/meta.json" <<EOF
{
  "session_id": "${SESSION_ID}"
}
EOF

echo "Lock acquired for session: $SESSION_ID"

```

### Checking Session Ownership

Hooks and pre-compact checks should validate session continuity, as implemented in [`go/cmd/harness/pre_compact.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/cmd/harness/pre_compact.go):

```bash
check_session_ownership() {
  local current_session="$1"
  local lock_dir=".claude/state/locks/loop-session.lock.d"
  
  if [ ! -d "$lock_dir" ]; then
    return 1  # No lock exists

  fi
  
  local locked_session
  locked_session=$(jq -r '.session_id' "$lock_dir/meta.json" 2>/dev/null || echo "")
  
  if [ "$locked_session" = "$current_session" ]; then
    return 0  # Same session

  else
    return 1  # Different session

  fi
}

```

### Legacy File Lock Pattern

For reference, the legacy pattern used in [`tests/test-harness-loop-guard.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/tests/test-harness-loop-guard.sh) appears as:

```bash
LOCK_FILE="${PLUGIN_ROOT}/.claude/state/locks/loop-session.lock"

# Simple existence check

if [ -f "$LOCK_FILE" ]; then
  echo "Another loop is already running" >&2
  exit 1
fi

# Create lock

> "$LOCK_FILE"

```

## Cleaning Up Locks

Regardless of the locking mechanism, proper cleanup prevents orphaned locks:

```bash

# Cleanup for directory-based locks (production)

rm -rf ".claude/state/locks/loop-session.lock.d"

# Cleanup for legacy file locks (tests)

rm -f ".claude/state/locks/loop-session.lock"

```

Integration tests in [`tests/integration/loop-max-cycles.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/tests/integration/loop-max-cycles.sh) (line 14) demonstrate proper teardown of the directory-based lock after successful harness-loop completion.

## Summary

- **Directory-based locking** (`loop-session.lock.d`) is the current production standard, storing session metadata in [`meta.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/meta.json) to enable intelligent session validation and WIP warning suppression.
- **File-based locking** (`loop-session.lock`) is a legacy mechanism that provides basic mutual exclusion but cannot distinguish between different session owners.
- The Go implementation in [`pre_compact.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/pre_compact.go) relies on the directory structure to make idempotency decisions during pre-compact hooks.
- Production implementations should always use the directory-based approach defined in [`skills/harness-loop/references/flow.md`](https://github.com/Chachamaru127/claude-code-harness/blob/main/skills/harness-loop/references/flow.md) to support long-running harness sessions properly.

## Frequently Asked Questions

### Why does claude-code-harness use a directory instead of a simple lock file?

The directory structure with an embedded [`meta.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/meta.json) file allows the harness to store session-specific metadata, including the `session_id`. This enables **idempotent session handling**, where the same harness-loop session can re-enter without triggering false-positive "already running" errors or redundant WIP warnings. According to the design documentation in [`docs/long-running-harness.md`](https://github.com/Chachamaru127/claude-code-harness/blob/main/docs/long-running-harness.md) (section 4-2), this approach supports future extensibility for adding timestamps and owner PIDs.

### Can I safely remove the legacy loop-session.lock file from my codebase?

Yes. The plain file lock at `.claude/state/locks/loop-session.lock` is only referenced in historic test suites like [`tests/test-harness-loop-guard.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/tests/test-harness-loop-guard.sh). Production code should rely exclusively on the directory-based mechanism. Removing legacy lock file references reduces confusion and ensures consistent behavior with the current Go implementation in [`go/cmd/harness/pre_compact.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/cmd/harness/pre_compact.go).

### How does the PreCompact hook determine whether to suppress WIP warnings?

The PreCompact hook reads [`.claude/state/locks/loop-session.lock.d/meta.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/.claude/state/locks/loop-session.lock.d/meta.json) and compares the stored `session_id` against the incoming hook's session identifier. If they match, the hook recognizes this as a continuation of the same long-running session and suppresses redundant WIP warnings. This logic, implemented at line 187 of [`pre_compact.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/pre_compact.go), is impossible with the legacy file-based lock because it lacks session context.

### What happens if a harness-loop crashes without cleaning up the lock directory?

Since the lock persists as a directory in `.claude/state/locks/loop-session.lock.d/`, subsequent sessions will detect the existing lock and check the `session_id`. If the crashed session cannot be resumed, manual intervention or a timeout-based cleanup mechanism would be required to remove the orphaned lock. Unlike the legacy file lock, the metadata in [`meta.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/meta.json) allows operators to identify which specific session left the lock behind.