ss -tlnp and lsof -i :PORT tell you the PID and command name, but for Node.js or Python processes, "node" or "python" alone doesn't tell you which project is running. The working directory is what you actually need — and it's sitting right there in /proc/$pid/cwd.
Put this in ~/.bashrc:
whoseport() {
if [ -z "$1" ]; then
sudo ss -tlnp | tail -n +2 | while read -r line; do
port=$(echo "$line" | grep -oP ':\K[0-9]+(?=\s)')
pid=$(echo "$line" | grep -oP 'pid=\K[0-9]+' | head -1 | tr -dc '0-9')
[ -n "$pid" ] && echo "PORT: $port | PID: $pid | CMD: $(ps -p $pid -o comm=) | CWD: $(readlink /proc/$pid/cwd)"
done
else
whoseport | grep 'PORT: '$1
fi
}
Usage:
$ whoseport 6173
PORT: 6173 | PID: 1326886 | CMD: node | CWD: /home/davidw/Projects/ccode_viewer/server
$ whoseport # list all listening ports
PORT: 61217 | PID: 1356 | CMD: tailscaled | CWD: /
PORT: 22 | PID: 1385 | CMD: sshd | CWD: /
A few things that went wrong before arriving at this version:
Don't use an alias for this — $1 in a single-quoted alias gets swallowed by inner sh -c calls, and local variables don't survive xargs boundaries. A function avoids both problems. The tr -dc '0-9' on the PID is not cosmetic — ss output can carry trailing whitespace or newlines that break ps -p with a "process ID list syntax error".
Need to work on two branches simultaneously without stashing or cloning? git worktree creates a separate working directory that shares the same .git — no duplicate objects, no wasted space.
git worktree add <path> <branch>
For example, to checkout feature/login into a sibling directory:
git worktree add ../my-feature feature/login
This creates ../my-feature with the branch checked out and ready to work. Commits, branches, and remotes are shared — it's one repository, multiple workspaces.
Day-to-day operations:
git worktree list — see all linked worktrees
git worktree remove <path> — clean up when done
One restriction: the same branch cannot be checked out in two worktrees at once. Git enforces this to prevent conflicting writes to the ref.
Most EC2 Windows Server instances don't expose nested virtualization to the guest OS. WSL2 requires Hyper-V extensions, so it simply won't start. WSL1 works fine — it's a translation layer, not a VM.
Enable the WSL feature and reboot in an Admin powershell window:
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
Restart-Computer
After reboot, pin the default version to 1:
wsl --set-default-version 1
On Windows Server, Add-AppxPackage is often broken or unavailable, so the standard wsl --install route fails. Use wsl --import with a rootfs tarball instead:
Now use a non-admin powershell:
mkdir $HOME\WSL
mkdir $HOME\WSL\Ubuntu
Then:
wsl --import Ubuntu $HOME\WSL\Ubuntu .\ubuntu.tar.gz --version 1
What you lose with WSL1: no Docker, no real Linux kernel, no systemd, weaker filesystem compatibility. Fine for CLI tooling, scripting, and build environments. If you need a real kernel, spin up a native Linux EC2 instance instead.
WezTerm binds Alt+Enter to toggle fullscreen by default. That hijacks Alt+Enter newline in Claude Code, GitHub Copilot, and other terminal-based tools.
Drop this in ~/.wezterm.lua to disable the default and remap fullscreen to Ctrl+Shift+F11:
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
config.keys = {
{
key = 'Enter',
mods = 'ALT',
action = wezterm.action.DisableDefaultAssignment,
},
{
key = 'F11',
mods = 'CTRL|SHIFT',
action = wezterm.action.ToggleFullScreen,
},
}
return config
Config takes effect immediately on save — no restart needed.
If you've been alt-tabbing to an admin cmd window to run mklink every time you wanted a symlink on Windows, there's a much cleaner way nobody seems to mention. Two settings, set them once, forget about it.
The reason ln -s in git-bash silently turns into cp by default is two unrelated barriers stacked together:
- Windows non-admins can't create symlinks unless Developer Mode is on.
- MSYS (git-bash's runtime) doesn't even try to create native symlinks without the
MSYS env var set.
Fix the first once with admin, fix the second in your shell profile.
# Run once in admin PowerShell, or use Settings → Update & Security → For developers → Developer Mode
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" `
-Name "AllowDevelopmentWithoutDevLicense" -Value 1 -Type DWord
# In ~/.bashrc
export MSYS=winsymlinks:nativestrict
nativestrict makes failures loud — ln -s errors out instead of silently copying when symlinks aren't supported. Avoid winsymlinks:native (the lenient sibling) — its silent fallback is exactly the trap you're trying to escape. Avoid winsymlinks:lnk entirely; .lnk shortcuts aren't symlinks anything else recognizes.
Open a new git-bash window and verify:
ln -s ~/.bashrc /tmp/test-link
ls -la /tmp/test-link
If the line starts with l, it's a real symlink that git, Linux tools, GNU stow, and your dotfiles install.sh all treat consistently across platforms. If it starts with -, either the env var didn't reach the new shell or Developer Mode didn't actually toggle.
Bonus: Developer Mode also unlocks Windows 11's native sudo command, so you can stop alt-tabbing to admin terminals for quick one-offs entirely.