systemd OSC 3008 Garbage in SSH Terminals: Two Simple Fixes

A practical guide to fixing systemd OSC 3008 garbage output when connecting to Ubuntu and other Linux systems over SSH: intercept it in .bashrc or clear it through a post-login command in your SSH client.

When connecting to Ubuntu, Kubuntu, or other newer systemd-based Linux systems over SSH, you may sometimes see strange control characters near the command prompt. Common clues include OSC 3008, systemd context, and __systemd_osc_context_*. This usually does not break command execution, but it pollutes terminal output and can get copied into logs.

This issue is not limited to one SSH client. WindTerm, embedded terminals, older terminal emulators, or clients with incomplete OSC support may all display control sequences that were supposed to be interpreted by the terminal.

The usual cause is that the remote system loads systemd’s OSC context hooks in the Bash environment, while the current SSH client does not handle those sequences correctly. The fix is straightforward: once these hook functions are confirmed to exist, override them with empty functions and clear PS0.

Below are two approaches. The first goes into the remote ~/.bashrc and is suitable when you want all SSH sessions to be handled consistently. The second goes into the SSH client’s post-login command and is better when you only want it to affect one client or one session.

Option 1: Intercept SSH Sessions in .bashrc

The logic is simple: when the current session is an SSH session and the systemd OSC hook functions really exist, override the related functions with no-op functions and clear PS0.

Add this block to the end of the remote server’s ~/.bashrc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Detect SSH sessions and block systemd OSC 3008 terminal control sequence garbage
if [[ -n "$SSH_CLIENT" || -n "$SSH_TTY" || -n "$SSH_CONNECTION" ]]; then
    # Only rewrite when the system really loaded systemd's OSC functions
    if declare -f __systemd_osc_context_precmdline >/dev/null; then
        __systemd_osc_context_precmdline() { :; }
        __systemd_osc_context_common() { :; }
        __systemd_osc_context_escape() { :; }
        PS0=""
    fi
fi

After saving, log in again over SSH, or run this in the current shell:

1
source ~/.bashrc

If the garbage output came from systemd’s OSC hooks, it usually disappears after entering a new shell.

If You Also Want to Match a Specific Client

Some clients set their own environment variables. For example, WindTerm may set TERM_PROGRAM=WindTerm. If you want to keep that check, use a slightly broader version:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Detect WindTerm or SSH sessions and block systemd OSC 3008 terminal control sequence garbage
if [[ "$TERM_PROGRAM" == "WindTerm" || -n "$SSH_CLIENT" || -n "$SSH_TTY" || -n "$SSH_CONNECTION" ]]; then
    # Only rewrite when the system really loaded systemd's OSC functions
    if declare -f __systemd_osc_context_precmdline >/dev/null; then
        __systemd_osc_context_precmdline() { :; }
        __systemd_osc_context_common() { :; }
        __systemd_osc_context_escape() { :; }
        PS0=""
    fi
fi

For normal SSH usage, checking SSH_CLIENT, SSH_TTY, and SSH_CONNECTION is enough. Adding TERM_PROGRAM only covers clients that expose their own identifier.

Why This Is Relatively Safe

This configuration has two guardrails.

The first is the session check:

1
[[ -n "$SSH_CLIENT" || -n "$SSH_TTY" || -n "$SSH_CONNECTION" ]]

These variables normally appear in SSH login sessions, so the change mainly affects remote logins and does not casually alter your local desktop terminal.

The second is the function existence check:

1
declare -f __systemd_osc_context_precmdline >/dev/null

This is the safety catch. The override only happens when the current shell has already loaded __systemd_osc_context_precmdline. If the system does not have this systemd OSC logic, the block does nothing extra.

The actual overrides are:

1
2
3
4
__systemd_osc_context_precmdline() { :; }
__systemd_osc_context_common() { :; }
__systemd_osc_context_escape() { :; }
PS0=""

These prevent the related hooks from outputting content. PS0 is a Bash prompt variable expanded after a command is read and before it is executed. Some OSC sequences are inserted through PS0 or similar mechanisms, so clearing it is part of the cleanup.

Option 2: Use a Post-Login Command in the SSH Client

If you do not want to edit the remote server’s ~/.bashrc, put the fix in your SSH client’s post-login command. Many terminals or SSH clients have a similar feature, often named:

  1. Command executed after authentication
  2. Post-login command
  3. Remote command after login
  4. Login post-command

Use this one-liner:

1
__systemd_osc_context_precmdline() { :; }; __systemd_osc_context_common() { :; }; __systemd_osc_context_escape() { :; }; PS0=""

If the client needs an explicit newline after auto-running the command, append it using that client’s syntax. For example, in WindTerm’s Command executed after authentication, you can use:

1
__systemd_osc_context_precmdline() { :; }; __systemd_osc_context_common() { :; }; __systemd_osc_context_escape() { :; }; PS0="" \n \n

The two \n markers make the client run the cleanup command and then move to a fresh prompt line. This works for clients like WindTerm that support automatic commands after authentication, and it has been tested in a Kubuntu 24.04 environment.

This approach has a smaller blast radius: the cleanup only runs when you connect with that client and that session. The server’s shell configuration does not need to change.

Which Option Should You Choose

If the server is mainly yours, or you want all SSH clients to avoid this garbage output, use option 1. Once it is in ~/.bashrc, WindTerm, Windows Terminal, Tabby, Xshell, and other SSH clients can all benefit from the same handling.

If the server is shared, or you only want one client to avoid the problem, use option 2. It does not modify remote configuration and does not affect other users’ shell environments.

You can also test with option 2 first. If it confirms the issue, then decide whether option 1 belongs in the server’s ~/.bashrc.

Notes

Both options only suppress systemd’s OSC output hooks. They do not modify systemd services and do not affect SSH login itself.

However, if you are using a modern terminal that supports OSC 3008 and depends on it to display command context, working directory, or system state, these enhanced hints may disappear after suppression. For ordinary SSH operations, development-machine logins, and server administration, this is usually fine.

Also, do not start by writing these overrides into the global /etc/bash.bashrc. Unless you are sure every user on the machine needs this behavior, prefer ~/.bashrc for personal environments and a client-side post-login command for targeted fixes.

Short Conclusion

If systemd OSC 3008 garbage appears after connecting to Linux over SSH, handle it in this order:

  1. If you do not want to change the server, configure a post-login cleanup command in the SSH client.
  2. If it is your own server, put the SSH-session interception block in ~/.bashrc.
  3. If it is a shared server, verify with the client-side fix first, then decide whether to roll it into shell configuration.

The core idea is simple: only in SSH sessions, check whether systemd’s OSC hook functions exist; if they do, override them with empty functions and clear PS0. This suppresses the garbage while keeping the normal terminal environment mostly untouched.

记录并分享
Built with Hugo
Theme Stack designed by Jimmy