Skip to content

Comments

feat(track): add terminal tracking via PPID resolution#200

Merged
AnnatarHe merged 2 commits intomainfrom
feat/terminal-tracking
Jan 6, 2026
Merged

feat(track): add terminal tracking via PPID resolution#200
AnnatarHe merged 2 commits intomainfrom
feat/terminal-tracking

Conversation

@AnnatarHe
Copy link
Contributor

Summary

  • Add terminal application tracking (iTerm2, Terminal.app, Alacritty, tmux, etc.) by capturing shell's parent process ID (PPID) and resolving it to terminal name in daemon service
  • PPID is captured in shell hooks, passed through CLI track command, and resolved to terminal name in daemon before syncing to server
  • Supports combined format for multiplexers: iTerm2 -> tmux

Changes

  • Add PPID field to Command and TrackingData structs
  • Add Terminal field to TrackingMetaData
  • Add --ppid flag to shelltime track command
  • Create daemon/terminal_resolver.go for process tree walking
  • Integrate terminal resolution in daemon sync handler

Related Changes (other repos)

  • Server: Add terminal field to model and database schema
  • Installation: Update shell hooks to pass --ppid=$PPID

Test plan

  • Manual test with iTerm2, Terminal.app
  • Test with tmux multiplexer (should show iTerm2 -> tmux)
  • Verify empty terminal when daemon not running (direct HTTP mode)

🤖 Generated with Claude Code

Capture parent process ID in shell hooks and resolve to terminal
emulator name (iTerm2, Terminal.app, tmux, etc.) in daemon service.

- Add PPID field to Command and TrackingData structs
- Add Terminal field to TrackingMetaData
- Add --ppid flag to track command
- Create terminal_resolver.go for process tree walking
- Integrate resolver in daemon sync handler

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @AnnatarHe, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the context captured for tracked commands by adding terminal application and multiplexer detection. By leveraging the parent process ID, the system can now identify the environment where commands are run, providing richer insights into user activity. This change involves updates across the CLI, daemon, and data models to seamlessly integrate this new tracking capability.

Highlights

  • Terminal Tracking: Introduced a new feature to automatically detect and track the terminal application (e.g., iTerm2, Alacritty, Terminal.app) and multiplexer (e.g., tmux) from which commands are executed.
  • PPID Resolution: Implemented a mechanism to capture the shell's parent process ID (PPID) and resolve it to the terminal name within the daemon service by walking up the process tree.
  • Multiplexer Support: The system now supports identifying and combining terminal and multiplexer information, presenting it in a clear format like 'iTerm2 -> tmux' for multiplexed sessions.
  • Data Model Extension: Extended the data models (Command, TrackingData, TrackingMetaData) to include the PPID and the resolved Terminal information.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codecov
Copy link

codecov bot commented Jan 5, 2026

Codecov Report

❌ Patch coverage is 4.00000% with 72 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
daemon/terminal_resolver.go 0.00% 67 Missing ⚠️
daemon/handlers.sync.go 0.00% 4 Missing and 1 partial ⚠️
Flag Coverage Δ
unittests 37.01% <4.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
commands/track.go 65.16% <100.00%> (+2.66%) ⬆️
model/api.go 100.00% <ø> (ø)
model/command.go 83.45% <ø> (ø)
daemon/handlers.sync.go 54.05% <0.00%> (-3.92%) ⬇️
daemon/terminal_resolver.go 0.00% <0.00%> (ø)

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new feature to track the terminal application used for shell commands. This is achieved by capturing the shell's parent process ID (PPID) and resolving it to a terminal name in the daemon.

The changes look good overall. The core logic for resolving the terminal by walking the process tree is implemented in the new daemon/terminal_resolver.go file. I've made a few suggestions to improve this implementation:

  • Improve performance and robustness on Linux by reading from the /proc filesystem directly instead of shelling out to external commands like cat.
  • Improve the robustness of terminal resolution in the daemon by searching for the first valid PPID in a batch of commands, rather than only checking the first one.
  • A minor refactoring suggestion to improve code readability.

Once these points are addressed, the PR should be in great shape.

Comment on lines +173 to +189
out, err := exec.Command("cat", "/proc/"+strconv.Itoa(pid)+"/stat").Output()
if err != nil {
return 0
}
// /proc/pid/stat format: pid (comm) state ppid ...
// Find the closing ) and get the 4th field after it
data := string(out)
idx := strings.LastIndex(data, ")")
if idx < 0 {
return 0
}
fields := strings.Fields(data[idx+1:])
if len(fields) < 2 {
return 0
}
ppid, _ := strconv.Atoi(fields[1])
return ppid
Copy link
Contributor

Choose a reason for hiding this comment

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

high

There are two issues here:

  1. For better performance, it's preferable to read /proc/<pid>/stat directly using os.ReadFile instead of shelling out to cat.
  2. The error from strconv.Atoi is ignored. This could lead to incorrect behavior if the parsed value is not a valid integer, although unlikely for /proc/pid/stat. It's best practice to handle this error.
		out, err := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
		if err != nil {
			return 0
		}
		// /proc/pid/stat format: pid (comm) state ppid ...
		// Find the closing ) and get the 4th field after it
		data := string(out)
		idx := strings.LastIndex(data, ")")
		if idx < 0 {
			return 0
		}
		fields := strings.Fields(data[idx+1:])
		if len(fields) < 2 {
			return 0
		}
		ppid, err := strconv.Atoi(fields[1])
		if err != nil {
			return 0
		}
		return ppid

Comment on lines 38 to 42
if len(syncMsg.Data) > 0 && syncMsg.Data[0].PPID > 0 {
terminal := ResolveTerminal(syncMsg.Data[0].PPID)
syncMsg.Meta.Terminal = terminal
slog.Debug("Resolved terminal", slog.String("terminal", terminal), slog.Int("ppid", syncMsg.Data[0].PPID))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation only checks the PPID of the first data item (syncMsg.Data[0]). If that item happens to have a PPID of 0, but subsequent items have a valid PPID, the terminal will not be resolved. It would be more robust to iterate through the data items and use the first valid PPID found.

	var ppid int
	for _, d := range syncMsg.Data {
		if d.PPID > 0 {
			ppid = d.PPID
			break
		}
	}

	if ppid > 0 {
		terminal := ResolveTerminal(ppid)
		syncMsg.Meta.Terminal = terminal
		slog.Debug("Resolved terminal", slog.String("terminal", terminal), slog.Int("ppid", ppid))
	}

Comment on lines 114 to 128
// Build result string
if terminal == "" && multiplexer == "" {
return "unknown"
}

if terminal != "" && multiplexer != "" {
return terminal + " -> " + multiplexer
}

if terminal != "" {
return terminal
}

return multiplexer
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This block of if statements for building the result string can be simplified using a switch statement, which can make the logic clearer and more concise.

	// Build result string
	switch {
	case terminal != "" && multiplexer != "":
		return terminal + " -> " + multiplexer
	case terminal != "":
		return terminal
	case multiplexer != "":
		return multiplexer
	default:
		return "unknown"
	}
}


case "linux":
// Linux: /proc/<pid>/comm
out, err := exec.Command("cat", "/proc/"+strconv.Itoa(pid)+"/comm").Output()
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For better performance, consider reading from the /proc filesystem directly using os.ReadFile instead of shelling out to cat. This avoids the overhead of creating a new process.

		out, err := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/comm")

Separate the combined "terminal -> multiplexer" format into two distinct
fields for cleaner server-side storage and querying.

- Change ResolveTerminal to return (terminal, multiplexer) tuple
- Add Multiplexer field to TrackingMetaData struct
- Update sync handler to populate both fields

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@AnnatarHe AnnatarHe merged commit fa5227a into main Jan 6, 2026
2 of 3 checks passed
@AnnatarHe AnnatarHe deleted the feat/terminal-tracking branch January 6, 2026 14:43
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.

1 participant