OSC 3008, formally Hierarchical Context Signalling, is UAPI.15 in the UAPI Group specifications. It defines a set of terminal control sequences that let programs tell a terminal emulator about the current context hierarchy inside a terminal session.
In plain terms, it tries to answer a question that has become common in modern Linux terminals: which layer are you actually in?
For example, you may SSH from your local machine to a server, enter a container, and then use run0 or a similar tool to run a command with elevated privileges. To you, it still looks like one continuous terminal window. To the terminal emulator, without an extra signal, it is hard to know whether the current output comes from the local machine, the remote host, a container, or an elevated command.
OSC 3008 is designed for that kind of situation. The UAPI specification says it allows terminal emulators to track the hierarchical context of the current content on screen. systemd also provides an implementation, usually through a script named 80-systemd-osc-context.sh.
Official references:
- UAPI.15 OSC 3008: Hierarchical Context Signalling: https://uapi-group.org/specifications/specs/osc_context/
- systemd
80-systemd-osc-context.sh: https://github.com/systemd/systemd/blob/main/profile.d/80-systemd-osc-context.sh
Why OSC 3008 Exists
Traditional terminals mainly display program output. At most, they use window titles, colors, and prompts to help distinguish environments. The problem is that modern terminal sessions are often nested:
|
|
If everything depends only on PS1, several problems appear:
- The prompt can only describe the current shell and struggles to express the full nesting hierarchy.
- Different shells, distributions, and user configurations can override one another.
- The terminal emulator cannot reliably know which output belongs to which context.
- Some control sequences become visible garbage in incompatible terminals.
OSC 3008 provides a more structured approach: the program that enters a context sends a start sequence, and the program that leaves the context sends an end sequence. After parsing those sequences, the terminal emulator can know which context node the current output belongs to.
What Information It Sends
OSC 3008 is a set of OSC escape sequences. The specification defines two core commands:
|
|
start means a context starts, is updated, or becomes current again. end means a context ends.
Each context can carry metadata fields. Common fields include:
type=: context type, such asshell,command,remote,container,vm, orelevate.user=: the UNIX user that emits the sequence.hostname=: the hostname.machineid=: the machine ID from/etc/machine-id.bootid=: the boot ID from/proc/sys/kernel/random/boot_id.pid=: the PID of the process that emits the sequence.comm=: the process name.cwd=: the current working directory, mainly forshellorcommand.cmdline=: the command line invoked interactively.container=andvm=: container or virtual machine names.targetuser=andtargethost=: target user or remote target host.
These fields are not meant to be read directly by users. They are meant for terminal emulators to parse. A compatible terminal consumes these control sequences and uses them to improve the interface.
What It Can Improve
If a terminal emulator supports OSC 3008, it can do smarter things.
First, it can mark output from different contexts. For example, container output, elevated-command output, and remote SSH output may use different backgrounds, borders, or hints.
Second, it can display a hierarchy breadcrumb. The terminal may know that the current session is roughly:
|
|
This is more stable than relying only on a window title or PS1.
Third, it can help window titles, tabs, and context menus. The terminal can update tab titles according to the current context, or provide actions such as opening a shell in the same directory for a selected output segment.
Fourth, it can reduce mistakes. If you are inside a production container and running commands as root, the terminal can show a more obvious warning and reduce the chance of running dangerous commands in the wrong environment.
How systemd Hooks Into It
systemd’s implementation mainly lives in 80-systemd-osc-context.sh. This script is loaded through the profile mechanism and sets related functions and prompt hooks in interactive Bash environments.
Common paths may include:
|
|
The exact location depends on the distribution. Comments in the systemd source explain that this file can be activated through systemd-tmpfiles, which links it into /etc/profile.d/.
In Bash, it uses function names such as:
|
|
It also affects PROMPT_COMMAND and PS0. PS0 is expanded after Bash reads a command and before it executes the command. That is why incompatible terminals may show a long 3008;start=... text before each command runs.
Why Some Terminals Show Garbage
Normally, a terminal emulator should parse OSC 3008 as a control sequence and not display it directly.
But if the terminal does not recognize OSC 3008, or an intermediate layer filters, escapes, or breaks the control characters, the raw content may be displayed. Common cases include:
- Older terminal emulators.
- SSH clients that have not adapted to OSC 3008.
- Web bastion hosts or browser terminals, including some Apache Guacamole environments.
- Environments with limited OSC support, such as Emacs
term.el, serial terminals, orminicom. - Multi-hop forwarding where an intermediate layer damages the control sequence.
In that case, you may see something like:
|
|
Or you may see long strings containing machineid=, bootid=, pid=, comm=, and cwd=. These are not normal program output. They are context signals that were supposed to be handled by the terminal.
How to Tell Whether It Is OSC 3008
Check three clues first.
First, does the garbage include 3008;start= or 3008;end=?
Second, does it appear before or after command execution, especially every time you press Enter to run a command?
Third, has the current system loaded systemd’s OSC functions?
|
|
If you can see a function definition, the current shell has loaded the related logic.
You can also check the profile file:
|
|
If this path exists and your terminal does not support OSC 3008, the garbage output likely comes from there.
Temporary Workaround
If only the current session is affected, you can override the related functions and clear PS0:
|
|
If you want this to happen automatically only in SSH sessions, put it in ~/.bashrc:
|
|
This has a small scope. It does not change system-level profile files and is easy to remove later.
System-Level Disable Method
If you are sure this machine’s terminal environment is generally incompatible with OSC 3008, you can consider disabling it system-wide. Be careful: this affects all users and login sessions.
The idea described in the systemd script comments is to remove the /etc/profile.d/80-systemd-osc-context.sh symlink and mask the corresponding tmpfiles fragment so it will not be recreated later.
You can use a command like this:
|
|
This is heavier than changing ~/.bashrc. Try the temporary workaround first to confirm the problem really comes from OSC 3008, then decide whether system-level disablement is justified.
If only your personal SSH client is incompatible, do not change system-level configuration first. Prefer your own ~/.bashrc, or configure a post-login cleanup command in the SSH client.
Upgrade the Terminal or Disable OSC 3008?
If you use a modern terminal that already supports OSC 3008, it is better to keep this capability. It may make remote shells, containers, virtual machines, and elevated commands easier to understand in the future.
If your actual environment is a bastion host, serial console, old terminal, or Web SSH client that cannot be upgraded soon, suppressing it is more practical. For most operations work, clean and readable terminal output is more important than enhanced context display.
Use this order:
- If you can upgrade the terminal emulator, upgrade it first.
- If you cannot upgrade and only your own session is affected, suppress it in
~/.bashrcor the client’s post-login command. - If the whole machine is affected, then consider removing
/etc/profile.d/80-systemd-osc-context.shand masking the tmpfiles fragment.
Practical Fixes for OSC 3008 Escape Sequences over SSH
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:
|
|
After saving, log in again over SSH, or run this in the current shell:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
Command executed after authenticationPost-login commandRemote command after login- Login post-command
Use this one-liner:
|
|
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:
|
|
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
OSC 3008 is not malware and not random program output. It is UAPI.15 terminal context signaling, designed to help terminals understand the hierarchy between shells, SSH sessions, containers, virtual machines, and elevated commands.
The real issue is usually compatibility. A compatible terminal parses it automatically. An incompatible one may show the control sequence as garbage.
If you see content such as 3008;start=, machineid=, bootid=, or cwd=, first check whether systemd’s 80-systemd-osc-context.sh injected it. After confirmation, choose a temporary workaround, a personal .bashrc workaround, or system-level disablement depending on the scope of the impact.