Posts in category “Tips”

Tired of Jira's slow UI? Drive it from your terminal with `jr`

If your day looks like open Jira → wait for the SPA to boot → hunt for the ticket → click into the status dropdown → wait again, you already know the tax. Most of what we actually do to a ticket — move it, comment on it, assign it — is two seconds of intent buried under ten seconds of web app. jira-sh is a tiny bash CLI that skips all of it. One command, jr, talking straight to the Jira Cloud REST API.

jr move PROJ-123 "In Review"
jr comment PROJ-123 "Deployed to staging"
jr view PROJ-123

No Electron, no browser, no waiting. It's a single bash script plus curl and python3 (standard library only for the core commands).

Setup in four lines

git clone https://github.com/shukebeta/jira-sh ~/Projects/jira-sh
bash ~/Projects/jira-sh/install.sh          # adds a source line to ~/.bashrc

# put these in ~/.bashrc
export JIRA_BASE=https://yourcompany.atlassian.net
export [email protected]
export JIRA_TOKEN=your-api-token

source ~/.bashrc
…more

IT blocked SSH to GitHub? One config line and you're done

If a network policy change suddenly breaks git push/pull to [email protected]:..., the panic response is for r in */; do git -C "$r" remote set-url origin ...; done across N repos. Don't. Add this to ~/.gitconfig and never think about it again:

[url "https://github.com/"]
    insteadOf = [email protected]:

What it does: any URL starting with [email protected]: is transparently rewritten to https://github.com/... at fetch/push time. git remote -v still shows the SSH form (good for humans), but the actual network request goes over HTTPS (good for the firewall).

Verify it works without pushing anything:

GIT_TRACE=1 git ls-remote [email protected]:YOUR_USER/YOUR_REPO.git

Look for run_command: ... remote-https ... in the output. If you see remote-ssh, the rule isn't matching (typo, wrong section, or a system-level gitconfig overriding it).

The rule also covers submodule URLs and any URL that happens to embed [email protected]: — they're all rewritten the same way.

If you have other GitHub Enterprise hosts ([email protected]:), add a matching pair for each. The pattern is always: target HTTPS host on the left, the SSH prefix you want rewritten on the right.

The one thing this doesn't do: change remote.origin.url in your .git/config. Existing repos still display the SSH form. If you want to be tidy, run a one-shot cleanup, but it's purely cosmetic — the wire traffic is already HTTPS.

tmux Alt+z for pane zoom works better with Chinese IME

tmux's built-in pane zoom (Prefix+z) can be unreliable for users with Chinese input methods. The standalone z keystroke may be intercepted by the IME before tmux sees it, forcing you to switch to English mode first.

Bind Alt+z as a prefix-free shortcut instead:

bind -n M-z resize-pane -Z

M-z (Alt+z) is sent as a Meta key sequence directly to the terminal, which most input methods don't intercept. The same logic applies to navigation—add hjkl pane switching without the prefix:

bind -n M-h select-pane -L
bind -n M-j select-pane -D
bind -n M-k select-pane -U
bind -n M-l select-pane -R

This keeps tmus responsive regardless of your current input state.

Show each tmux pane's working directory on its border (with ~ for $HOME)

When you split a window into several panes — say a dev/planner/reviewer layout — it's easy to lose track of which directory each one is actually in. tmux exposes pane_current_path, so you can paint the cwd right onto the pane's top border:

set -g pane-border-status top
set -g pane-border-format " #{pane_index}: #{pane_title} [#{pane_current_path}] "

Full paths get long and crowd out the title. Two ways to trim. #{b:...} gives just the basename (last component):

set -g pane-border-format " #{pane_index}: #{pane_title} [#{b:pane_current_path}] "

Or abbreviate $HOME to ~ with tmux's s/search/replace/ modifier. The neat trick is that the replacement is itself a format, so you can nest #{HOME} inside it — no hardcoded username, so the same dotfile works on every machine:

set -g pane-border-format " #{pane_index}: #{pane_title} [#{s|^#{HOME}|~|:pane_current_path}] "

s takes any delimiter after it; using | keeps the slashes in the path readable, and ^ anchors the match to the start so a directory that merely contains your home path elsewhere isn't touched. A pane in /home/you/Projects/foo now shows [~/Projects/foo].

tmux doesn't watch the config file — after editing, reload the running server with tmux source-file ~/.tmux.conf (or prefix then :source-file ~/.tmux.conf). Without that, only a fresh server start picks up the change.

One caveat worth knowing: pane_current_path is the cwd of the pane's foreground process, and tmux only updates it on chdir(2). A shell reflects it faithfully. But a long-running process that was started in one directory and then operates on files by absolute path — without ever cd-ing — won't move the border, because its cwd never changed. So the border shows the shell's real cwd, which isn't always where a process is "logically" working.

tmux `run-shell` eats your format strings — use `detach-on-destroy off` instead

A common pattern for "hop to another session before killing this one" looks like this:

bind Q run-shell 'next=$(tmux list-sessions -F "#{session_name}" 2>/dev/null \
  | grep -v "^#{session_name}$" | head -1); \
  if [ -n "$next" ]; then tmux switch-client -t "$next"; \
    tmux kill-session -t "#{session_name}"; \
  else tmux kill-session; fi'

It doesn't work. run-shell expands all #{...} format strings before handing the command to the shell. So list-sessions -F "#{session_name}" becomes list-sessions -F "mysession" — a literal string, not a format specifier. Every session prints "mysession", grep -v strips them all, $next is always empty, and you drop straight to bash.

The fix is one line:

set -g detach-on-destroy off
bind Q kill-session

detach-on-destroy off is a native tmux option: when a session is destroyed, the client automatically switches to another surviving session. It only falls back to exit when there's nothing left. No shell escaping, no format-string footguns, and it covers every exit path — not just prefix+Q.