Skip to content

CM-62381-add-session-start-hook#434

Open
RoniCycode wants to merge 6 commits intomainfrom
CM-62381-refactor-ensure-auth-command
Open

CM-62381-add-session-start-hook#434
RoniCycode wants to merge 6 commits intomainfrom
CM-62381-refactor-ensure-auth-command

Conversation

@RoniCycode
Copy link
Copy Markdown
Collaborator

  • Change "ensure-auth" to "session-start" which includes conversation creation and MCP usage checks (once per session)

Comment thread cycode/cli/apps/ai_guardrails/session_start_command.py Outdated
Comment thread cycode/cli/apps/ai_guardrails/session_start_command.py Outdated
Comment thread cycode/cli/apps/ai_guardrails/session_start_command.py Outdated
Comment thread cycode/cyclient/ai_security_manager_client.py Outdated
Comment on lines +71 to +75
# Step 2: Read stdin payload (backward compat: old hooks pipe no stdin)
if sys.stdin.isatty():
logger.debug('No stdin payload (TTY), skipping session initialization')
return

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain

Copy link
Copy Markdown
Collaborator Author

@RoniCycode RoniCycode Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What it does: sys.stdin.isatty() returns True when stdin is attached to a terminal — i.e. nothing is piped in. If we didn't have this guard, sys.stdin.read() on the next line would block forever waiting
for input that never arrives.

Why it's needed:

  1. Manual/interactive invocation. If you run cycode ai-guardrails session-start in a shell to debug it, stdin is the terminal. Without this check, the command would hang.
  2. Backward compat with the old ensure-auth hook. In init.py we kept ensure-auth as a deprecated alias:
    app.command(hidden=True, name='ensure-auth', ...)(session_start_command)
  3. Users who installed hooks before this branch have cycode ai-guardrails ensure-auth wired into their IDE config. The old command only ran auth and didn't read stdin. After they upgrade the CLI, that same
    hook now invokes session_start_command. The guard ensures that even if the invocation context somehow doesn't provide a JSON payload on stdin, we still do the auth step (which is what the old command did)
    and then gracefully skip the conversation/MCP reporting steps instead of hanging.
    Flow:
  • Hook invoked by IDE → stdin is a pipe → isatty() is False → read JSON, create conversation, report MCP
  • Hook invoked with no stdin (manual / edge case) → isatty() is True → auth happens above, then we return early

If you want to make the intent clearer, the comment could be:

  • No piped input: would block forever on read(). Happens when run manually,
  • or with the legacy ensure-auth hook which didn't supply stdin.

Comment thread cycode/cli/apps/ai_guardrails/session_start_command.py Outdated
Comment thread cycode/cli/apps/ai_guardrails/session_start_command.py Outdated
Comment thread cycode/cli/apps/ai_guardrails/session_start_command.py Outdated
Comment thread cycode/cli/apps/ai_guardrails/session_start_command.py Outdated
if ide == AIIDEType.CLAUDE_CODE:
claude_config = load_claude_config()
ide_user_email = get_user_email(claude_config) if claude_config else None
ide_version, _, _ = _extract_from_claude_transcript(payload.get('transcript_path'))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a better way to get claude version? i want to get rid of this function and also stop creating conversations for each hook

Comment thread tests/cli/commands/ai_guardrails/test_session_start_command.py Outdated
Copy link
Copy Markdown
Collaborator

@elsapet elsapet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments for your consideration but no major blockers IMO 🙏

def _get_cursor_session_context() -> tuple[dict, dict]:
"""Return (mcp_servers, enabled_plugins) for Cursor. Cursor has no plugin system."""
config = load_cursor_config()
mcp_servers = get_mcp_servers(config) or {} if config else {}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion to use parens here to make it clearer and less ambiguous. Also, maybe we want to ensure a dict as we do on line 59?

Suggested change
mcp_servers = get_mcp_servers(config) or {} if config else {}
mcp_servers = dict(get_mcp_servers(config) or {}) if config else {}



def _report_session_context(ai_client, ide: str) -> None:
"""Report IDE session context to the AI security manager. Never raises."""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never raises.

This comment confuses me because there's nothing in the _report_session_context method itself that guarantees this.

If we want to ensure this behaviour here, I would wrap this method logic itself in a try / except block. What do you think?

return None


def get_enabled_plugins(settings: dict) -> Optional[dict]:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this method used?

resolve_plugins,
)
from cycode.cli.apps.ai_guardrails.scan.cursor_config import load_cursor_config
from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload, _extract_from_claude_transcript
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're importing _extract_from_claude_transcript here, should we make it a public function?

Comment on lines +21 to +25
app.command(hidden=True, name='session-start', short_help='Handle session start: auth, conversation, session context.')(
session_start_command
)
app.command(hidden=True, name='ensure-auth', short_help='[Deprecated] Alias for session-start.')(
session_start_command
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume because these are hidden commands, there are no related documentation updates required 👍

Also, for interest, is it planned to clean up / remove the deprecated command in a later PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants