Environment variables can be set at three scopes. A step-level env: block applies only to that step, a job-level env: applies to all steps within the job, and a workflow-level env: placed under on: and above jobs: applies to every step in every job. The default working directory is the repository root, also known as GITHUB_WORKSPACE, which can be overridden with working-directory: at the job or step level.
Steps and jobs exchange data through outputs. A step declares its outputs in its id block, and downstream steps read them with ${{ steps.step_id.outputs.output_name }}. Jobs similarly declare outputs under jobs.
The shell used by run: defaults to bash on Linux runners and can be changed per step with shell: pwsh, shell: sh, shell: python, and so on, or globally for a job with defaults.run.shell. Multiline scripts are written with a pipe block under run: or a heredoc inside the script. Conditional steps can read these context variables directly, such as if: github.event_name == 'pull_request' to run only on pull requests, or if: "! contains(github.event.head_commit.message, '[skip ci]')" to skip workflows based on commit message patterns.