feat(zed): add known_human checkpoint extension#1048
Conversation
Adds a Zed extension that fires git-ai checkpoint known_human on file save with 500ms debounce per repo root, plus a ZedInstaller that auto-installs the extension source and format_on_save hook to the Zed config directory. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| CONTENT="$(cat)" | ||
|
|
||
| # ------------------------------------------------------------------ | ||
| # 2. Emit content unchanged (the formatter contract). | ||
| # ------------------------------------------------------------------ | ||
| printf '%s' "$CONTENT" |
There was a problem hiding this comment.
🔴 Bash command substitution strips trailing newlines, silently modifying every saved file
CONTENT="$(cat)" at line 25 uses bash command substitution, which always strips trailing newlines from the captured output. When the content is re-emitted at line 30 with printf '%s' "$CONTENT", any trailing newline(s) from the original file are lost. Since this script acts as Zed's format_on_save formatter, every file save will silently remove the file's trailing newline — causing Zed to see a diff and apply the modification. Most text files (and POSIX convention) end with a trailing newline, so this would alter virtually every file on every save.
Standard fix for preserving trailing newlines in bash
CONTENT="$(cat; printf x)"
CONTENT="${CONTENT%x}"This appends a sentinel character before the command substitution strips newlines, then removes it, preserving the original trailing newlines.
| CONTENT="$(cat)" | |
| # ------------------------------------------------------------------ | |
| # 2. Emit content unchanged (the formatter contract). | |
| # ------------------------------------------------------------------ | |
| printf '%s' "$CONTENT" | |
| CONTENT="$(cat; printf x)" | |
| CONTENT="${CONTENT%x}" | |
| # ------------------------------------------------------------------ | |
| # 2. Emit content unchanged (the formatter contract). | |
| # ------------------------------------------------------------------ | |
| printf '%s' "$CONTENT" |
Was this helpful? React with 👍 or 👎 to provide feedback.
| mkdir -p "$LOCK_DIR" | ||
|
|
||
| # Hash the repo root path to create a safe filename | ||
| REPO_HASH="$(printf '%s' "$REPO_ROOT" | sha256sum | cut -c1-16 2>/dev/null || printf '%s' "$REPO_ROOT" | md5sum | cut -c1-16)" |
There was a problem hiding this comment.
🔴 sha256sum and md5sum unavailable on stock macOS causes script to exit non-zero, breaking file saves
Line 70 tries sha256sum then falls back to md5sum, but neither command exists on stock macOS (which ships shasum and md5 instead). With set -euo pipefail (line 20), when both pipelines fail, the command substitution exits non-zero, causing the script to terminate with an error. Since the script's own comment at line 7 states "Non-zero exit causes Zed to show an error and discard stdout", this means file saves would fail on macOS — Zed's primary platform. The project's own install.sh:110-113 handles this correctly by falling back to shasum -a 256.
| REPO_HASH="$(printf '%s' "$REPO_ROOT" | sha256sum | cut -c1-16 2>/dev/null || printf '%s' "$REPO_ROOT" | md5sum | cut -c1-16)" | |
| REPO_HASH="$(printf '%s' "$REPO_ROOT" | sha256sum 2>/dev/null | cut -c1-16 || printf '%s' "$REPO_ROOT" | shasum -a 256 2>/dev/null | cut -c1-16 || printf '%s' "$REPO_ROOT" | md5sum 2>/dev/null | cut -c1-16 || printf '%s' "$REPO_ROOT" | md5 2>/dev/null | cut -c1-16 || printf '%s' "$REPO_ROOT" | cksum | cut -d' ' -f1)" |
Was this helpful? React with 👍 or 👎 to provide feedback.
| let mut j = i + 1; | ||
| while j < n | ||
| && (lines[j].trim() == "]," | ||
| || lines[j].trim() == "]" | ||
| || lines[j].trim() == "}," | ||
| || lines[j].trim() == "}" | ||
| || lines[j].contains("\"arguments\"") | ||
| || lines[j].contains("\"format_on_save\"")) | ||
| { | ||
| j += 1; | ||
| } | ||
| skip_count = j - i - 1; |
There was a problem hiding this comment.
🔴 remove_formatter_block skip-ahead loop consumes the top-level closing } of settings.json
The forward-skip loop at lines 284-293 greedily skips any line whose trimmed content equals } or },. After removing the formatter block's own closing braces, the loop continues and also matches the top-level JSON closing } (e.g., the final } of settings.json). This causes the uninstall to produce invalid JSON missing its closing brace.
Trace through test case at zed.rs:579-595
Given settings content:
{
"other_setting": true,
"formatter": { ... our block ... },
"format_on_save": "on"
}When the script-path line (index 4) is matched, the skip-ahead loop starting at j=5 walks through "arguments", }, },, "format_on_save", and then also } (the top-level closing brace at index 9) because lines[9].trim() == "}" is true. The result is {\n "other_setting": true,\n — invalid JSON missing its closing brace. The existing test only asserts the removed content is gone and other_setting is present, so it passes despite the corruption.
Was this helpful? React with 👍 or 👎 to provide feedback.
…ection, and fix fragile uninstall - Escape backslashes and double-quotes in file path and repo root when python3 is unavailable in the fallback path of git-ai-zed-hook.sh - Skip formatter injection in install_settings if a \"formatter\" key already exists in settings.json to avoid invalid JSON with duplicate keys - Restrict remove_formatter_block to only pop lines containing \"external\" or matching the exact \"formatter\" key prefix, preventing accidental removal of unrelated keys like \"default_formatter\" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| // Find the last `}` and insert before it. | ||
| let trimmed = original.trim_end_matches(['\n', '\r']); | ||
| if let Some(pos) = trimmed.rfind('}') { | ||
| let (before, _after) = trimmed.split_at(pos); | ||
| let before = before.trim_end_matches(','); | ||
| format!("{before},\n{formatter_snippet}\n}}\n") |
There was a problem hiding this comment.
🔴 install_settings produces invalid JSON when settings.json is an empty object {}
When Zed's settings.json contains an empty JSON object (e.g. {} or {\n}), the install_settings method at src/mdm/agents/zed.rs:175-183 enters the else branch, finds the last }, splits before it, then unconditionally formats as "{before},\n{snippet}\n}}". For an empty object, before is "{" (or "{\n"), producing {,\n "formatter"... which is invalid JSON — a comma immediately after the opening brace with no preceding entry.
This is a realistic scenario because new Zed installations often have settings.json with just {} or {\n}. The corrupted settings file would prevent Zed from loading its configuration.
Trace for settings.json = "{}":
original.trim().is_empty()→ false,original.contains('{')→ true → enters else branchtrimmed = "{}",rfind('}')= 1before = "{", after trimming commas still"{"- Result:
"{,\n \"formatter\": ...\n}\n"→ invalid JSONC
| // Find the last `}` and insert before it. | |
| let trimmed = original.trim_end_matches(['\n', '\r']); | |
| if let Some(pos) = trimmed.rfind('}') { | |
| let (before, _after) = trimmed.split_at(pos); | |
| let before = before.trim_end_matches(','); | |
| format!("{before},\n{formatter_snippet}\n}}\n") | |
| // Find the last `}` and insert before it. | |
| let trimmed = original.trim_end_matches(['\n', '\r']); | |
| if let Some(pos) = trimmed.rfind('}') { | |
| let (before, _after) = trimmed.split_at(pos); | |
| let before_trimmed = before.trim_end_matches(','); | |
| // If the object is empty (before is just `{` plus whitespace), don't add comma | |
| if before_trimmed.trim_end().ends_with('{') { | |
| format!("{before_trimmed}\n{formatter_snippet}\n}}\n") | |
| } else { | |
| format!("{before_trimmed},\n{formatter_snippet}\n}}\n") | |
| } |
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
agent-support/zed/) that firesgit-ai checkpoint known_human --hook-input stdinon file save with 500ms debounce per repo rootZedInstaller(src/mdm/agents/zed.rs) that auto-installs the extension source, the hook wrapper script, and configuresformat_on_savein Zed'ssettings.jsoninclude_str!to embed all extension source files at compile timeExtension API approach
Finding: The Zed WASM extension API (v0.5–v0.7) has no file-save callbacks.
The
Extensiontrait exposes only: language server hooks, debug adapter hooks, slash commands, context servers, and docs indexing. There is noon_save,on_buffer_change, or equivalent. See: https://docs.rs/zed_extension_api/0.7.0/zed_extension_api/trait.Extension.htmlApproach used:
format_on_saveexternal commandZed exposes a
"formatter": { "external": { "command": "...", "arguments": [] } }setting that fires on every file save and receives the full file content via stdin. TheZedInstallerwrites a wrapper shell script (git-ai-zed-hook.sh) and injects theformat_on_saveconfiguration into~/.config/zed/settings.json(Linux) or~/Library/Application Support/Zed/settings.json(macOS).The wrapper script:
git -C <dir> rev-parse --show-toplevelto find the repo rootgit-ai checkpoint known_human --hook-input stdinin the background with a 500ms debounce per repo rootThe WASM extension stub is still installed in the Zed extensions directory so Zed loads and displays a "git-ai" extension entry.
Manual install
Then restart Zed.
Test plan
cargo clippy -- -D warningspassescargo testpasses (all unit tests forZedInstallerpass)git-ai mdm install, save a file in Zed, verifygit-ai checkpoint known_humanfires🤖 Generated with Claude Code