Skip to content

.First() and .First.sys() are bypassed during the startup sequence #158

Closed

Description

@bsirak

Session information (:info output)

# Session Information

arf version:    0.3.0
OS:             windows (x86_64)
Config file:    ~\AppData\Roaming\arf\arf.toml
R version:      4.5.3
R_HOME:         ~\.R/versions/4.5.3
R source:       PATH

rig:            not installed
air:            not installed

Current mode:   R

R history:      ~\AppData\Roaming\arf\history\r.db
Shell history:  ~\AppData\Roaming\arf\history\shell.db

## Environment Variables

R_LIBS_USER:     ~\.R/library/4.5
R_LIBS_SITE:     ~\.R/versions/4.5.3/site-library

Description

Description:
I've been testing arf as a modern drop-in replacement for radian and standard R, but I've run into an issue with how the frontend handles the legacy R startup sequence.

Specifically, arf evaluates the user .Rprofile correctly, but it appears to skip executing .First() and .First.sys() at the end of the initialization pipeline.

Impact:
Because these standard hooks never fire, it breaks two major workflows:

  1. VS Code Compatibility (vscode-R): The VS Code session watcher relies on overwriting .First.sys() to delay its attachment until the base packages load. Because arf skips this hook, the IDE's Workspace, Plot viewer, and Data viewer permanently fail to attach to the arf session.
  2. User Profiles: Any user-defined startup logic or message() calls wrapped in .First() inside a .Rprofile are completely swallowed.

Steps to reproduce

  1. Place the following in an ~/.Rprofile:

    .First <- function() {
      message("--> This message should print at startup.")
    }
  2. start arf

Expected behavior

--> This message should print at startup. is displayed before banner or prompt.

Actual behavior

Nothing is displayed. If .First() is run in terminal, message is displayed.

Additional context

No response

Activity

  1. eitsupi commented on Apr 22, 2026

    @eitsupi
    Owner

    Thanks for pointing that out.
    Could you try #159?

  2. bsirak commented on Apr 22, 2026

    @bsirak
    Author

    Thank you for looking into this! I think we're half way there. .First() seems to be evaluated properly now, but .First.sys() is still not being run because R is still not attaching the environment. When .First.sys() is run manually, everything attaches seemingly just fine. Would you like me to raise another issue or reopen this one?

  3. eitsupi commented on Apr 22, 2026

    @eitsupi
    Owner

    @bsirak Thank you. Do you have any examples that can be reproduced?

  4. eitsupi commented on Apr 22, 2026

    @eitsupi
    Owner

    I've investigated, but it doesn't seem like an easy fix.
    I suspect the same problem might occur in Ark as well.

    When does this actually occur?
    If it only happens in vscode-r, it might be faster to rewrite it in vscode-r.
    See REditorSupport/vscode-R#1684

  5. eitsupi commented on Apr 22, 2026

    @eitsupi
    Owner

    To clarify the technical situation:

    What arf does

    arf follows the startup sequence described in ?Startup:

    Next, if a function .First is found on the search path, it is executed as .First(). Finally, function .First.sys() in the base package is run.

    Concretely:

    • .First() is looked up starting from R_GlobalEnv (i.e. the search path)
    • .First.sys() is looked up in R_BaseNamespace and evaluated in R_BaseEnv — i.e. the function "in the base package"

    This mirrors what R's own setup_Rmainloop() does (src/main/main.c, around the .First.sys block): R_findVar(cmd, baseNSenv) followed by eval(lang1(cmd), R_BaseEnv).

    What vscode-R does

    vscode-R's R/session/init.R does:

    assign(".First.sys", init_last, envir = globalenv())

    It places .First.sys in the global environment, not in the base package. ?Startup explicitly describes .First.sys() as "the function in the base package", so a user-level override in globalenv isn't part of the documented contract — it happens to work in plain R because of implementation behavior in setup_Rmainloop() that we haven't fully traced yet (the documented lookup path shouldn't pick up a globalenv binding, yet empirically it does in a stock R session).

    Why arf behaves differently

    arf runs setup_Rmainloop() with load_init_file = FALSE on Windows (needed for globalCallingHandlers() compatibility) and sources .Rprofile manually afterwards. The manual invocation of .First.sys() is then done via the C API with the same documented lookup (R_BaseNamespace / R_BaseEnv), which — as documented — resolves to base::.First.sys. It does not pick up the globalenv binding that vscode-R installs.

    As I noted, Ark is likely to have the same problem for the same reason, since Ark also disables the built-in profile loading and sources .Rprofile manually.

    Documented alternatives for vscode-R

    Any of the following would not depend on implementation details of setup_Rmainloop():

    1. Put the init logic in .First() (the documented user hook) instead of .First.sys().
    2. Use setHook(packageEvent("base", "onLoad"), ...) / setHook("rstudio.sessionInit", ...) style hooks.
    3. Install the wrapper into the base namespace directly:
      old <- base::.First.sys
      ns <- asNamespace("base")
      unlockBinding(".First.sys", ns)
      assign(".First.sys", function() { old(); vsc_attach() }, envir = ns)
      lockBinding(".First.sys", ns)
      (Option 3 is more invasive but works with any embedder that honors ?Startup.)

    Given that arf is spec-conformant here, I think REditorSupport/vscode-R#1684 is the right place to address this.

  6. bsirak commented on Apr 22, 2026

    @bsirak
    Author

    @eitsupi, thank you again for such a quick fix and a through explanation! I agree with your plan and I'll head over to vscode-R to suggest a more elegant solution on their end. For now, I've placed this statement inside my .First() call.

    if (
        Sys.getenv("TERM_PROGRAM") == "vscode" &&
          exists(".First.sys", envir = .GlobalEnv)
      ) {
        .GlobalEnv$.First.sys()
        if (exists(".First.sys", envir = .GlobalEnv, inherits = FALSE)) {
          rm(".First.sys", envir = .GlobalEnv)
        }
      }

    Just so I know I'm understanding correctly, both ark and arf are now handling .First() and .First.sys() the same way, right?

  7. eitsupi commented on Apr 25, 2026

    @eitsupi
    Owner

    @bsirak I don't know because I haven't actually tried it myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions