fix(state): refuse mkdir in uninitialized projects#77
Merged
Conversation
Closes a cross-IDE leak where opening a non-ctx workspace in Cursor and submitting a single prompt deposited a stub .context/state/ (mode 0750) into the project. Cursor imports Claude Code hooks and sets CLAUDE_PROJECT_DIR to the workspace root for compatibility; the ctx@activememory-ctx plugin's UserPromptSubmit chain then fired in every workspace. The check-reminder hook's "provenance-first" ordering called Preamble -> nudge.Paused -> PauseMarkerPath -> state.Dir before the Initialized() gate, leaving the mkdir as the authoritative source of the leak. Move the gate inside state.Dir() itself: when the project is not initialized, return errCtx.ErrNotInitialized without mkdir. Hook callers' existing dirErr != nil branches absorb the new sentinel silently; interactive callers (ctx add, ctx task complete, ctx prune) surface a path-bearing message via cobra's standard error path. Refactor cooldown.TombstonePath to delegate to state.Dir so the same gate applies to the PreToolUse "ctx agent" hook path. Tests: - internal/cli/system/core/state: full Dir/Initialized matrix including override bypass and partial-init cases. - internal/cli/system/cmd/check_reminder: end-to-end regression that simulates the Cursor flow and asserts no .context/ exists after running the hook in an uninitialized tempdir. - pause/resume tests updated to seed the now-required init files (FilesRequired) so state.Dir keeps succeeding. Spec: specs/state-dir-no-mkdir-when-uninitialized.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
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.
Closes a cross-IDE leak where opening a non-ctx workspace in Cursor and submitting a single prompt deposited a stub .context/state/ (mode 0750) into the project. Cursor imports Claude Code hooks and sets CLAUDE_PROJECT_DIR to the workspace root for compatibility; the ctx@activememory-ctx plugin's UserPromptSubmit chain then fired in every workspace. The check-reminder hook's "provenance-first" ordering called Preamble -> nudge.Paused -> PauseMarkerPath -> state.Dir before the Initialized() gate, leaving the mkdir as the authoritative source of the leak.
Move the gate inside state.Dir() itself: when the project is not initialized, return errCtx.ErrNotInitialized without mkdir. Hook callers' existing dirErr != nil branches absorb the new sentinel silently; interactive callers (ctx add, ctx task complete, ctx prune) surface a path-bearing message via cobra's standard error path. Refactor cooldown.TombstonePath to delegate to state.Dir so the same gate applies to the PreToolUse "ctx agent" hook path.
Tests:
Spec: specs/state-dir-no-mkdir-when-uninitialized.md