Skip to content

feat(cli): record whether each tracked command ran over SSH - #316

Merged
AnnatarHe merged 1 commit into
mainfrom
claude/relaxed-babbage-9t545p
Oct 7, 2026
Merged

AnnatarHe merged 1 commit into
mainfrom
claude/relaxed-babbage-9t545p

Conversation

@AnnatarHe

Copy link
Copy Markdown
Contributor

Summary

Part of the command timeline fix. When you run ssh devbox on your laptop and then work on devbox, the web timeline now nests devbox's commands under that ssh command. Without SSH information, a remote shell on devbox looks just like someone sitting at devbox.

  • New model.IsSSHSession(getenv). It is true when SSH_CONNECTION, SSH_CLIENT or SSH_TTY is set. sshd exports these into the login shell, and shelltime track inherits them.
  • shelltime track records the flag on each pre/post record (model.Command.ViaSSH, *bool). It is computed in the track process, because the daemon's own environment has no SSH variables.
    • The flag survives the daemon socket and the bolt/file stores, which all serialize as JSON.
    • Records buffered before the upgrade stay nil, so the server stores them as unknown.
  • TrackingData.viaSsh is sent per row. It is taken from the post record, falling back to the pre record. terminal and shell in meta are per upload batch, so they can't serve this purpose.
  • shelltime q reuses the same helper. Its SSH check now also counts SSH_CLIENT.

No hook changes are needed.

Merge order

  1. shelltime/server: stores viaSsh (older servers ignore the field)
  2. This PR
  3. shelltime/web: the new timeline UI

Tests

  • TestBuildTrackingDataViaSSH:
    • unknown stays nil;
    • the value is taken from post, falling back to pre;
    • true on either side wins.
  • TestIsSSHSession table test.
  • go vet ./... and go test ./model/... ./commands/... ./daemon/... pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ev8KCoqb66RRr6CBZp23wo


Generated by Claude Code

`shelltime track` now checks SSH_CONNECTION/SSH_CLIENT/SSH_TTY (inherited
from the login shell) and sends a per-command `viaSsh` flag. The web
timeline uses it to nest commands run on a remote host under the
`ssh` command that opened the session. The check is done in the track
process because the daemon's own environment has no SSH variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ev8KCoqb66RRr6CBZp23wo
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@claude

claude Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Flag Coverage Δ
unittests 85.78% <100.00%> (?)

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

Files with missing lines Coverage Δ
commands/query_context.go 85.14% <100.00%> (ø)
commands/track.go 82.41% <100.00%> (+0.39%) ⬆️
model/api.go 100.00% <ø> (ø)
model/command.go 92.62% <ø> (ø)
model/sys.go 48.71% <100.00%> (+1.34%) ⬆️
model/tracking_build.go 92.42% <100.00%> (+0.36%) ⬆️

... and 1 file 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
Copy Markdown
Contributor Author

@claude review


Generated by Claude Code

@claude

claude Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @AnnatarHe's task in 17s —— View job


Code review

I read the full diff and found no blocking issues. The change is small and consistent with the repo's conventions.

What I checked

  • ViaSSH on model.Command is a *bool with omitempty. Nil stays nil, so buffered records from before the upgrade read back as unknown. The bolt store, the file store (ToLine and FromLine serialize the struct as JSON) and the daemon socket all use JSON, so the flag survives each of them.
  • The flag is computed in the shelltime track process, where the SSH variables are inherited from the shell. The daemon's environment is never consulted, which is correct.
  • BuildTrackingData takes the value from the post record and falls back to the pre record. true on either side wins, and a pre false fills in when post is nil. The table test covers all five cases.
  • IsSSHSession is shared with shelltime q, so that command now also counts SSH_CLIENT. The IsSSHSession table test covers each variable.

Minor, non-blocking notes

  1. JSON key mismatch. Command.ViaSSH serializes as "ssh" (model/command.go), while TrackingData.ViaSSH serializes as "viaSsh" (model/api.go). It works, because they are different structs. Using the same key (viaSsh) would make local buffers easier to read and debug. Changing it later would orphan records already written as ssh, so it is cheapest to decide now.
  2. Pointer aliasing. td.ViaSSH = pre copies the pointer from the pre record. Nothing mutates it afterwards, so this is safe today. If you want to stay defensive, copy the value instead.
  3. Env scope. sudo and su - can drop SSH_* variables. Commands run that way may be recorded as non-SSH. The PR description says the flag is best-effort, so this seems acceptable.
  4. Test coverage. There is no test that commandTrack sets ViaSSH. Codecov shows the changed lines as covered, so this is optional.

No changes requested. This looks good to merge once the server side that stores viaSsh has landed, as the merge order in the description says.

I did not run the build or tests. This was a read-only review.

Copy link
Copy Markdown
Contributor Author

Thanks. All four notes stay as they are:

  1. ssh vs viaSsh: model.Command is the local buffer record and uses short keys throughout (sid, cmd, hn, un, t, et), so ssh follows that convention. viaSsh is only the key sent to the server.
  2. Pointer aliasing: pre and post records are read-only once loaded, and nothing writes through the pointer, so sharing it is safe.
  3. sudo / su -: expected, since the flag is best-effort. When it's missing, the web timeline still links the shell if the ssh destination matches the hostname.
  4. commandTrack test: the changed lines are already covered (codecov 100% patch), so I'm not adding one.

Generated by Claude Code

@AnnatarHe
AnnatarHe merged commit a5b2991 into main Oct 7, 2026
4 checks passed
@AnnatarHe
AnnatarHe deleted the claude/relaxed-babbage-9t545p branch October 7, 2026 09:27
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.

2 participants