> ## Documentation Index
> Fetch the complete documentation index at: https://namespace.so/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Job Ordering & Priority

export const KeepTabPosition = () => {
  useEffect(() => {
    const stateKey = "__namespaceKeepTabPosition";
    const releaseState = state => {
      state.instances -= 1;
      if (state.instances > 0) return;
      state.removeListeners();
      if (window[stateKey] === state) delete window[stateKey];
    };
    const existingState = window[stateKey];
    if (existingState) {
      existingState.instances += 1;
      return () => releaseState(existingState);
    }
    const maxSettleTime = 250;
    const requiredStableFrames = 3;
    const positionTolerance = 0.5;
    let animationFrame;
    let observer;
    let settleDeadline;
    let stableFrames = 0;
    let tabListToKeep;
    let tabListTop;
    const findTab = event => {
      if (!(event.target instanceof Element)) return null;
      return event.target.closest(".tab-container [role='tab']");
    };
    const findScrollContainer = element => {
      for (let parent = element.parentElement; parent; parent = parent.parentElement) {
        const {overflowY} = getComputedStyle(parent);
        if ((overflowY === "auto" || overflowY === "scroll") && parent.scrollHeight > parent.clientHeight) {
          return parent;
        }
      }
      return null;
    };
    const stopKeepingPosition = () => {
      if (animationFrame) cancelAnimationFrame(animationFrame);
      animationFrame = undefined;
      observer?.disconnect();
      tabListToKeep = undefined;
    };
    const restoreTabListPosition = () => {
      if (!tabListToKeep?.isConnected) return 0;
      const offset = tabListToKeep.getBoundingClientRect().top - tabListTop;
      if (Math.abs(offset) < positionTolerance) return offset;
      const scrollOptions = {
        top: offset,
        behavior: "instant"
      };
      const scrollContainer = findScrollContainer(tabListToKeep);
      if (scrollContainer) {
        scrollContainer.scrollBy(scrollOptions);
      } else {
        window.scrollBy(scrollOptions);
      }
      return offset;
    };
    const settlePosition = () => {
      const offset = restoreTabListPosition();
      stableFrames = Math.abs(offset) < positionTolerance ? stableFrames + 1 : 0;
      if (stableFrames >= requiredStableFrames || performance.now() >= settleDeadline) {
        stopKeepingPosition();
        return;
      }
      animationFrame = requestAnimationFrame(settlePosition);
    };
    observer = new MutationObserver(() => {
      stableFrames = 0;
      restoreTabListPosition();
    });
    const keepTabListInPlace = tab => {
      stopKeepingPosition();
      tabListToKeep = tab.closest("[role='tablist']");
      if (!tabListToKeep) return;
      tabListTop = tabListToKeep.getBoundingClientRect().top;
      settleDeadline = performance.now() + maxSettleTime;
      stableFrames = 0;
      observer.observe(document.getElementById("content") ?? document.documentElement, {
        attributes: true,
        attributeFilter: ["aria-selected", "class"],
        childList: true,
        subtree: true
      });
      animationFrame = requestAnimationFrame(settlePosition);
    };
    const selectWithoutNavigation = event => {
      if (!event.isTrusted) return;
      const tab = findTab(event);
      if (!tab) return;
      keepTabListInPlace(tab);
      event.preventDefault();
      event.stopPropagation();
      tab.click();
      restoreTabListPosition();
    };
    const selectWithKeyboard = event => {
      if (!event.isTrusted) return;
      const tab = findTab(event);
      const tabList = tab?.closest("[role='tablist']");
      if (!tab || !tabList) return;
      const tabs = Array.from(tabList.querySelectorAll(":scope > [role='tab']"));
      const currentIndex = tabs.indexOf(tab);
      let nextIndex;
      switch (event.key) {
        case "ArrowLeft":
          nextIndex = (currentIndex - 1 + tabs.length) % tabs.length;
          break;
        case "ArrowRight":
          nextIndex = (currentIndex + 1) % tabs.length;
          break;
        case "Home":
          nextIndex = 0;
          break;
        case "End":
          nextIndex = tabs.length - 1;
          break;
        case "Enter":
        case " ":
          nextIndex = currentIndex;
          break;
        default:
          return;
      }
      keepTabListInPlace(tab);
      event.preventDefault();
      event.stopPropagation();
      tabs[nextIndex]?.click();
      tabs[nextIndex]?.focus({
        preventScroll: true
      });
      restoreTabListPosition();
    };
    document.addEventListener("click", selectWithoutNavigation, true);
    document.addEventListener("keydown", selectWithKeyboard, true);
    const state = {
      instances: 1,
      removeListeners: () => {
        stopKeepingPosition();
        document.removeEventListener("click", selectWithoutNavigation, true);
        document.removeEventListener("keydown", selectWithKeyboard, true);
      }
    };
    window[stateKey] = state;
    return () => releaseState(state);
  }, []);
  return null;
};

<KeepTabPosition />

By default, there is no queuing or ordering for jobs: as soon as a job is scheduled a runner is started for it.
This changes when your workspace is hitting [concurrency limits](/docs/architecture/compute/resource-limits)
and jobs have to wait for resources to become available.

Namespace offers controls to influence the order of jobs to be picked up.

See the [Runner Controls overview](/docs/solutions/github-actions/runner-controls) for how to pass these settings via a profile or labels.

## Deterministic assignments

By default, a job can be assigned to any runner that matches that job's labels.
A runner will pick up the job that it's assigned by GitHub, which does not happen in order.
This means that while hitting concurrency limits, jobs with the same labels might not be executed in the expected order.

As a consequence, a runner that was started for one job may never pick up a job at all, because another matching
runner got there first. Such instances are not billed, see
[Billing and Limits](/docs/workspaces/billing-and-limits#am-i-billed-for-runner-instances-that-do-not-pick-up-a-job).

To control this behaviour, you can add the run id to the labels.
Then only that specific job can be picked up by the runner started for it, and jobs are executed in the order in which they are scheduled:

<Tabs>
  <Tab title="With runner profiles">
    ```yaml {2} theme={null}
    runs-on:
      - namespace-profile-my-profile;github.run-id=${{github.run_id}}
    ```
  </Tab>

  <Tab title="With runner labels">
    ```yaml {3} theme={null}
    runs-on:
      - nscloud-ubuntu-24.04-amd64-2x4-with-features
      - namespace-features:github.run-id=${{github.run_id}}
    ```
  </Tab>
</Tabs>

## Job priority

When some workflows should not be blocked by other less, time-sensitive ones, you can assign them a priority.
Priority only affects scheduling while running into concurrency limits.

Pending jobs with the lowest numbers will be started first. Jobs with any priority specified take precedence over those without.

To specify a priority, append `job.priority=<number>` to the runs-on label:

<Tabs>
  <Tab title="With runner profiles">
    ```yaml theme={null}
    runs-on: namespace-profile-my-profile;job.priority=1
    ```
  </Tab>

  <Tab title="With runner labels">
    ```yaml theme={null}
    runs-on: nscloud-ubuntu-24.04-amd64-4x8;job.priority=1
    ```

    Unlike other label settings, `job.priority` must be appended inline directly to your machine label.
  </Tab>
</Tabs>

## Next steps

* [Runner Controls overview](/docs/solutions/github-actions/runner-controls)
* [Privileged workflows](/docs/solutions/github-actions/runner-controls/privileged-workflows)
