Conversation
claude-sync wraps the re-add + commit + push loop for files chezmoi tracks under ~/.claude/ (specifically ~/.claude/CLAUDE.md and per-project memory dirs). Avoids re-typing the 3-command chezmoi recipe after every session that writes new memory. Subcommands: claude-sync re-add + push (idempotent if nothing changed) claude-sync --status show chezmoi diff for ~/.claude/ claude-sync --no-push re-add + commit only claude-sync --add <p> bring a new ~/.claude/... path into tracking Sourced from zsh/.zshrc next to bg-agents.zsh. No new dependencies beyond chezmoi (already required by the dotfiles repo). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chezmoi sometimes creates empty auto-commits (re-add on already-tracked files with no diff). These leave the local branch ahead of origin/main, so the old logic 'if git log origin/main..HEAD non-empty: push' triggered a push even when there was nothing to actually transfer. git then reports 'Everything up-to-date' but the function announced 'pushed'. The fix captures git push output and parses for 'Everything up-to-date' to distinguish 'synced (empty auto-commit)' from a real push. Also adds explicit error handling so push failures surface stderr instead of being swallowed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`chezmoi cd` opens an interactive subshell and silently no-ops when
sourced inside a function (no TTY). The previous `(chezmoi cd && { git
... })` pattern meant the git commands ran against the session's CWD
repo instead of the chezmoi source repo. From rforge/dev that yields
spurious "Everything up-to-date" (rforge's local main matches origin),
masking unpushed commits in dotfiles.
Replaced with `git -C "$(chezmoi source-path)"` which is non-interactive
and explicit. Adds a guard for the case where source-path can't be
resolved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
test-doctor.zsh was timing out under run-all.sh's 30-second wrapper because three redundant $(doctor ...) calls each spent 5-8s on curl https://api.github.com/user (commands/doctor.zsh:405). Root cause: - Standalone: 38.86s wall (passes 23/23) — over the 30s limit - Wrapped: 30.01s, SIGKILL on _doctor_missing_brew test - user+sys: 13.4s - Network wait: ~25s (real - CPU) Fix: cache doctor --verbose once in setup() (matching the pattern used for CACHED_DOCTOR_DEFAULT and CACHED_DOCTOR_HELP) and reuse for both the --verbose and -v tests. -v is an alias for --verbose per commands/doctor.zsh:39, so they exercise identical output. Result: - 38.86s -> 23.51s wall time (40% reduction) - exit 0 under timeout 30 zsh - 23/23 tests still passing - 6.5s headroom under the 30s ceiling Out of scope (filed in .STATUS Pending): commands/doctor.zsh:405 calls curl directly without consulting lib/doctor-cache.zsh, so end-user flow doctor invocations are also slower than they need to be. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New worktree at ~/.git-worktrees/flow-cli/wire-doctor-cache on branch feature/wire-doctor-cache. Planning ORCHESTRATE file for wiring lib/doctor-cache.zsh into commands/doctor.zsh:405 (the GitHub token validation curl that currently bypasses the existing file-based cache). Implementation will happen in a separate session per the workflow documented in CLAUDE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…idation Plans the wiring of lib/doctor-cache.zsh into commands/doctor.zsh:405, which currently calls curl https://api.github.com/user directly without consulting the existing file-based cache. End-user flow doctor runs spend ~5-8s on this network call every invocation; caching the result should drop warm runs to ~1s. Plan covers: API surface to read (lib/doctor-cache.zsh:722-793 for the token cache helpers, plus low-level get/set context), target shape for the refactor at doctor.zsh:404-417, four user decisions to confirm before coding (TTL, key derivation strategy, --no-cache flag, storage format), a test strategy that avoids regressing the run-all.sh 30s ceiling fixed in 74af31b, and verification steps. This is a planning-only commit. Implementation requires a fresh claude session in this worktree per flow-cli CLAUDE.md Step 3 (STOP). Related: commit 74af31b (test-side caching that this work makes redundant) Related: .STATUS Pending "Doctor command bypasses its own cache" Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires lib/doctor-cache.zsh into commands/doctor.zsh:404-455 GitHub token
validation so warm doctor runs skip the api.github.com/user curl. Cache key
is sha256(token)[:12] so token rotation auto-invalidates without a manual
clear path. TTL is 1 hour. Adds --no-cache flag for forced fresh validation.
Empirical timing on a developer machine: cold 11.3s → warm 10.8s (~5% off
per warm run; smaller than the original spec estimate because the
_dots_doctor_integration GitHub curl is already cached via the existing
provider-key path, so this PR only eliminates the legacy section's curl).
Test isolation: DOCTOR_CACHE_DIR is now exported in test-doctor.zsh setup
before plugin load (the cache lib marks it readonly post-source) so tests
no longer pollute developer's ~/.flow/cache/doctor/.
+4 doctor tests: cache_hit_skips_curl, cache_miss_triggers_curl,
no_cache_flag_bypasses, envelope_format. Doctor 27/27, full suite 52/52.
Side discoveries logged in .STATUS Pending:
- tests/test-framework.zsh create_mock save/restore is broken for
binaries (tail -n +2 strips opening { but keeps closing }) — worked
around via direct functions[] manipulation in test helpers
- lib/doctor-cache.zsh _doctor_cache_acquire_lock mkdir-fallback leaks
state across in-process invocations, causing cache_set rc=1 on
second call with same key — worked around with unique test keys
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pulls the legacy GitHub TOKEN validation block out of doctor() into a standalone helper, allowing cache tests to exercise the wiring without invoking the full ~10s system-check pipeline. Fixes a regression introduced in 0880f92: that commit's 3 mock-curl tests each ran full `doctor`, pushing test-doctor over the 30s ceiling in tests/run-all.sh. The commit message and .STATUS both wrongly claimed "52/52 passing" — actual reading was "2 timeout" (test-doctor was the undocumented one). Caught when verifying the optional Step 4 follow-up with `time timeout 30 zsh tests/test-doctor.zsh`. After this refactor: - test-doctor standalone: 40s -> 26s - test-doctor under timeout 30: exit 124 -> exit 0 - full suite: 52 passed / 2 timeout -> 53 passed / 1 timeout (only e2e-em-dispatcher remains, which is documented as expected) The helper also unifies the validation path so future work could route the --dot cache scheme through the same function. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sync user-facing and reference docs to the wiring + refactor commits on this branch: - docs/commands/doctor.md: add --no-cache to Options table - docs/reference/REFCARD-DOCTOR.md: add flow doctor --no-cache row - docs/reference/MASTER-API-REFERENCE.md: add _doctor_check_github_token entry under a new "Cache Consumers" subsection (signature, args, side effects, cache key derivation, why fingerprint over provider key) - docs/architecture/DOCTOR-TOKEN-ARCHITECTURE.md: add "Two Cache Key Schemes" subsection covering the provider-key (--dot path) vs fingerprint-key (legacy path) tradeoff, when each is preferable, and the dual-scheme file layout currently on disk - CLAUDE.md: correct test count drift (52/52 + 2 timeouts -> 53/53 + 1 timeout) after the refactor commit moved test-doctor back to ✅ markdownlint: 0 errors across the 5 files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ZSH completion for `flow doctor` was missing several flags. Backfilled all currently-shipping flags so tab-completion surfaces them: --no-cache (this PR), --quiet/-q, --dot, --fix-token (prior PRs) Also added doctor --no-cache to the Entry Points block in DOCTOR-TOKEN-ARCHITECTURE.md (the v5.17.0 token-automation entries already there list the other modes; --no-cache belongs in the same set). Live `doctor --help` already includes --no-cache (from 0880f92). markdownlint: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o 45s Adds coverage for previously-uncovered branches of _doctor_check_github_token: - empty token path tagged "missing" in _doctor_token_issues - invalid token (http != 200) tagged "invalid" AND cache file NOT written (cache-only-on-success guard) - doctor --no-cache CLI flag end-to-end (verifies argparse → no_cache=true → helper invocation chain) - fingerprint determinism (sha256 prefix collision-resistance + same token → same key invariant) The E2E test runs full `doctor` and pushes test-doctor standalone runtime from 26s to 31s, breaching the 30s harness ceiling in tests/run-all.sh. Made run_test() accept an optional second arg for per-test timeout, and bumped test-doctor specifically to 45s. Result: 31/31 doctor tests pass, full suite 53/53 + 1 expected timeout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per the workflow rule in CLAUDE.md: ORCHESTRATE files are working artifacts that belong on feature branches during development, not on dev after merge. Removing it now keeps the merge commit clean of ephemeral planning artifacts. The plan it described is preserved in the commit history (f8879fa introduced it, this commit removes it) for anyone who wants to read the original implementation intent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaced while watching CI on PR #446: the required status check "ZSH Plugin Tests" only runs test-flow.zsh + test-install.sh per the explicit "Smoke tests only" comment in test.yml. The full 205-file suite (including test-doctor.zsh and the new branch tests in this PR) is never executed by CI. Filed as Pending to track separately — addressing it is a workflow PR, not feature work, and bundling it here would be scope creep. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(doctor): cache GitHub token validation with fingerprint key
PR #446 wired GitHub token validation into lib/doctor-cache.zsh's fingerprint cache, so the production cache already deduplicates repeated `doctor --verbose` invocations across the disk-shared DOCTOR_CACHE_DIR. The setup-side cache becomes redundant. Side benefit: test_doctor_v_flag now actually exercises the `-v` alias rather than asserting the cached `--verbose` output exists. Verified: 31/31 doctor tests pass in 42.6s (under 45s harness ceiling).
…on-scoped sourcing The "lock leak" bug filed 2026-05-13 was misdiagnosed. Root cause: in zsh, `readonly FOO=bar` declared inside a function context (or sourced from one) creates a *function-local* readonly. When the calling function returns, the variable vanishes. Chain: test-doctor.zsh setup() (a function) sources flow.plugin.zsh -> commands/doctor.zsh -> lib/doctor-cache.zsh, where `readonly DOCTOR_CACHE_*` became local to setup(). When setup() returned, the constants were gone. The load guard (`typeset -g _FLOW_DOCTOR_CACHE_LOADED=1`, line 50) IS global and persists, so re-sourcing is suppressed and the constants stay undefined. Symptom: `max_attempts=$((DOCTOR_CACHE_LOCK_TIMEOUT * 10))` evaluated to `0 * 10 = 0`. The mkdir loop ran zero iterations and `_doctor_cache_set` returned rc=1 immediately, surfacing as "Failed to acquire cache lock for writing" — independent of any actual lock state. Fix: declare the four module constants with `typeset -gr` instead of `readonly`. Added a comment block explaining the trap so a future cleanup doesn't silently re-introduce it. Verified: test-doctor envelope test now passes cleanly with no warning; full ./tests/run-all.sh: 53 passed, 0 failed, 1 expected timeout (e2e-em-dispatcher). Production was unaffected because users source flow.plugin.zsh from .zshrc at top level, where readonlys are global.
Defensive: same zsh function-scope trap as commit f193907 (doctor-cache). Both libs declare module-level constants with bare `readonly` and currently work only because flow.plugin.zsh sources them at top level. If a future test harness or helper sources either lib from inside a function, the constants would silently become undefined when the caller returns — same failure mode as the doctor-cache "lock leak." Files: - lib/analysis-cache.zsh: 3 constants (SCHEMA_VERSION, DEFAULT_TTL_HOURS, LOCK_TIMEOUT) - lib/macro-parser.zsh: 3 string constants + 1 array (MACRO_FORMAT_*, MACRO_AUTO_DISCOVER_PATHS — uses `-gar` for the array) Verified: ./tests/run-all.sh — 53 passed, 0 failed, 1 expected timeout.
Mirrors tests/test-local-path-regression.zsh. Prevents reintroduction of the function-scope bug fixed in f193907 + 9db49db. Five checks: 1. lib/*.zsh has no bare `readonly` declarations 2. commands/*.zsh has no bare `readonly` declarations 3. setup/ and hooks/ have no bare `readonly` declarations 4. Functional proof: `readonly` inside a function vanishes; `typeset -gr` survives 5. Lib-pattern proof: bare readonly arithmetic evaluates to 0 after the sourcing function returns; `typeset -gr` evaluates correctly Wired into ./tests/run-all.sh under the existing "Regression tests" section. Test counts updated across CLAUDE.md, .STATUS, and docs/guides/TESTING.md (205 -> 206 test files, 53/53 -> 54/54 suites).
The previous `whence -f $fn | tail -n +2` save mechanism was broken:
`whence -f` outputs `name () {\n body\n}`, so `tail -n +2` skips only
the opening `name () {` line, leaving the trailing `}` in the body.
Wrapping that in a new `funcname() { ... }` template produced unbalanced
braces — `{...} }` — and a silent eval parse error.
Cascading failure: the silent save failure meant `_original_mock_${fn}`
was never created, so `reset_mocks` couldn't find the saved original
and fell into the `unset -f $fn_name` branch — DESTROYING the original
function instead of restoring it. After the first reset_mocks, the
real function was permanently gone.
Fix: replace eval-string save/restore with zsh's `${functions[name]}`
associative array. `functions[name]` returns the body without surrounding
braces, and assignment to `functions[name]=$saved` round-trips losslessly.
Save state lives in a new `_ORIGINAL_FUNCTIONS` global assoc array.
Verified: standalone repro now correctly restores the original function
after both first and second mock cycles. Full ./tests/run-all.sh: 54
passed (deploy-v2 timeouts under run-all's 30s wrapper are pre-existing
flakes unrelated to mocks — both pass 71/71 in isolation).
Workaround functions like `_test_install_curl_mock` in tests/test-doctor.zsh
already use the same pattern and could now be replaced with create_mock,
but that's a separate cleanup not bundled here.
- Add 5 entries to root + docs CHANGELOG [Unreleased] mirroring this
session's commits:
Added: regression test for bare-readonly zsh trap
Fixed: doctor-cache constants (typeset -gr)
Fixed: preventive same fix in analysis-cache + macro-parser
Fixed: create_mock save/restore (functions[] instead of whence-tail)
Changed: drop CACHED_DOCTOR_VERBOSE workaround now that production
cache deduplicates
- Update CLAUDE.md tree comment: 205 → 206 test files
The two CHANGELOG files are kept in sync via diff verification:
diff <(awk '/Unreleased/,/[7.6.0]/' CHANGELOG.md) \
<(awk '/Unreleased/,/[7.6.0]/' docs/CHANGELOG.md)
Headlines: doctor cache + zsh-scope hardening
- Bump FLOW_VERSION (flow.plugin.zsh), package.json, CLAUDE.md, .STATUS
- Sweep 47 doc files: header/footer "**Version:** v7.6.0" → v7.7.0
(narrow patterns; historical "New in v7.6.0" content untouched)
- Rename CHANGELOG [Unreleased] → [7.7.0] — 2026-05-15 in both
CHANGELOG.md and docs/CHANGELOG.md
- Update CLAUDE.md "Last Updated" footer to 2026-05-15
What ships in v7.7.0:
Added:
- flow doctor GitHub token validation cache (1h TTL)
- flow doctor --no-cache flag
- tests/test-readonly-scope-regression.zsh
Fixed:
- doctor-cache silent write failures (typeset -gr scope fix)
- Same fix preventively in analysis-cache + macro-parser
- create_mock save/restore destroyed originals (functions[] fix)
Changed:
- Drop CACHED_DOCTOR_VERBOSE workaround
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
flow doctorGitHub token validation cache (1h TTL, fingerprint-keyed) +--no-cacheflag + a regression-guard test for the bare-readonlyzsh scope trap.analysis-cacheandmacro-parser;create_mocksave/restore that silently destroyed original functions.CACHED_DOCTOR_VERBOSEtest workaround now that the production cache deduplicates.Production users were unaffected by the bugs (top-level sourcing keeps zsh globals intact); the failures only surfaced through test harnesses that source via
setup().Highlights
flow doctorper hour validates the GitHub token; subsequent calls are ~5–8s faster (cache hit).typeset -grfor module constants; test framework'screate_mockcorrectly restores originals afterreset_mocks.test-readonly-scope-regression.zsh) wired intorun-all.sh— 5 checks including a functional proof that the trap still exists in current zsh.Test plan
./tests/run-all.sh— 54/54 pass, 1 expected IMAP timeout./tests/test-doctor.zsh— 31/31, no "Failed to acquire cache lock" warning./tests/test-doctor-cache.zsh— 19/20 (3.2 expiry test is a pre-existing flake unrelated to this PR)./tests/test-readonly-scope-regression.zsh— 5/5Notes for review
**Version:** v7.6.0→ v7.7.0) in docs. Real code changes are 4 commits (doctor-cache, libs, framework, regression test) — see commit history for atomic reads.