Invoking a snap from within a VS Code extension overrides the snap environment variables

Hi folks,

I wrote a snap for this project of mine: GitHub: Palang compiler. I am invoking this snap from a VS Code extension and it doesn’t have access to its snap user and common data directories because my VS Code process is also snapped.

For context, this project is a compiler for the Palang programming language. Palang is an experimental programming language where LLM prompts are one of the first-class primitives of the language. As such, the prompts need to target a specific LLM provider (whether it be in a private cloud we built or on a public cloud). These configurations are stored under ~/.palang/profiles in the non-snapped version. For the snapped version, I am storing the profiles under ~/snap/palang/[...] using the SNAP_USER_COMMON, SNAP_USER_DATA, etc. environment variables.

I wrote a VS Code extension that watches your files and when you save a file, it re-compiles it and provides you with a list of prompts and functions you can run, in a side panel. To compile files, I invoke the palang snap:

import { execSync } from "child_process";
...

// When I need to access profiles
JSON.parse(execSync('palang profiles --json').toString().trim());

The palang profiles --json command then uses the SNAP_USER_DATA environment variable to read the profiles from ~/snap/palang/..., i.e. to read one profile:

pub fn load<T>(name: &String, collection: &str) -> Result<T, String>
    where T: DeserializeOwned
{
    let file_name_with_extension: String = format!("{}.yaml", name);
    let directory: PathBuf = (
        if let Ok(snap_user_data) = env::var("SNAP_USER_DATA") {
            PathBuf::from(snap_user_data)
        } else {
            dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")).join(".palang")
        }
    ).join(collection);
 
    let file_path: PathBuf = directory.join(file_name_with_extension);
    load_file(&file_path)
}

Using the non-snapped version of the compiler, the extension works ; nothing prevents it from accessing ~/.palang/profiles.

Using the snapped version, the extension breaks because the VS Code installation I am using is also snapped. Thus, when my VS Code extension calls palang profiles --json, it does so with SNAP_USER_DATA = ~/snap/code/[...] rather than SNAP_USER_DATA = ~/snap/palang/[...]. The compiler then sees no profile because it is not looking in the right directory.

Is this a known problematic? I am sure it must have happened to other projects in the past. I am looking for elegant workarounds. One way I could go around this is by looking for the ~/snap/palang directory rather than looking for the environment variables, but I feel like what I am seeing should not be the expected behavior. I would expect SNAP_USER_DATA to be overridden by snapd when the palang profiles --json starts and to see SNAP_USER_DATA = ~/snap/palang/[...]. Perhaps I should file this under snapd and ask for a change?

Any opinions, suggestions and insights about this and greatly appreciated!

Your plugin will run inside the environment of the snapped VScode so it will inherit the $SNAP_* variables from it, I think the most elegant solution is to look for the dir as you already stated …

Thank you for the quick response!

I think looking for the directory is reasonably elegant, I might go for that.

However there is one thing that is bothering me with this approach. It is that if my plugin is running inside the environment of the snapped VS Code, then not only do I inherit the environment variables, but I also inherit the sandbox of that parent snap (VS Code).

It is fine in my specific case because the VS Code snap has classic confinement so it has access to all files in the host system.

Normally though, with strict confinement, I would not be able to access the ~/snap/palang directory from the parent application’s sandbox as that would be a violation of the sandbox.

I guess as an alternate solution, I could write a daemon for my application that runs in its own snap sandbox on the host system and then I can connect to that daemon either through a socket, through TCP or through something else that makes sense. That way I just need to make sure the VS Code snap’s sandbox can reach my daemon and my daemon would be able to find the profiles because it is in its own sandbox.

But then again there are issues: say I use a socket linked to a file, then I need VS Code to be allowed access to that file. Again it will be fine for VS Code specifically because it has classic confinement, but for strict confinement I would get the same issue as before.

So in the end, I think creating a daemon that uses a TCP connection to connect with clients would be a reasonably clean solution.

But taking a step back, I feel like I’m introducing a lot of complexity and not adding much value to the project so I will probably just stick with looking for the ~/snap/palang directory ; it will work for VS Code.

Thank you for the feedback though!

Well, this won’t ever change given the nature of VScode being an IDE, so i don’t think you need to worry about this, VScode will likely (have to) stay classic forever…

That’s fair enough !