{"course":{"slug":"building-apps-with-ai","title":"Building Apps with AI","url":"https://addaly.com/learn/building-apps-with-ai","level":"beginner","track":"build"},"lesson":{"slug":"github-and-the-remote","title":"GitHub, and what a remote is for","url":"https://addaly.com/learn/building-apps-with-ai/github-and-the-remote","position":41,"of":87,"minutes":9,"answer":"A remote is another copy of the history that only accepts commits building on what it already has, which is what a rejected push is telling you.","keyIdea":"A remote is another copy of the history that only accepts commits building on what it already has, which is what a rejected push is telling you."},"body":{"format":"markdown","text":"## A remote is just another copy\n\nGit on your laptop is complete. A remote is a second copy of the same history somewhere else, and `origin` is the conventional name for the main one. Nothing about it is special; you could have a remote on a USB stick.\n\nGitHub is the most used host, and GitLab, Codeberg and Bitbucket do the same job. All of them offer free private repositories, which is the default you want until you decide otherwise.\n\n```bash\ngit remote -v                                     # what remotes exist\ngit remote add origin git@github.com:you/app.git  # attach one\ngit push -u origin main                           # send main, remember the pairing\n```\n\nThe `-u` sets up tracking so that later `git push` and `git pull` know where to go without being told.\n\n## Getting in\n\nGitHub stopped accepting passwords for git operations in 2021. A push that asks for a password and rejects the correct one is this: it wants a personal access token, or an SSH key. The simplest route is the GitHub command-line tool, `gh auth login`, which walks through either. Do it once per machine.\n\n## Push, fetch, pull\n\n`git push` sends commits the remote does not have. It only succeeds if your branch contains everything the remote's branch contains: your history must be a continuation of theirs. When it is not, you see `rejected: non-fast-forward`. Something has been added on the remote that you do not have: you pushed from another machine, you edited a file in the web interface, or an automated tool committed. The fix is to bring those commits down first.\n\n`git fetch` downloads the remote's commits without touching your files. It is always safe, and it lets you look before merging: `git log main..origin/main` shows what is there that you lack.\n\n`git pull` is fetch followed by merge. It is what most people run, and the merge can conflict like any other.\n\nAfter a fetch, `git status` reports things like \"ahead by 2, behind by 1\". Ahead: commits you have not pushed. Behind: commits you have not pulled. `origin/main` in that output is your last known view of the remote's branch, updated only when you fetch.\n\n## A new machine\n\n```bash\ngit clone git@github.com:you/app.git\ncd app\nnpm install\ncp .env.example .env    # then fill it in by hand\n```\n\nThe last line is the one that gets forgotten, because `.env` is ignored by design and so it is the one thing the clone does not carry. An app that fails on a fresh clone with a configuration error is almost always this.\n\n## Pull requests when you are alone\n\nA pull request is a proposed merge shown as a diff, in a browser, with a comment box. It exists for teams, and it is worth using by yourself, for one reason: it puts the whole of a change on a different screen from the one you built it on, all at once, file after file, with a preview deployment attached.\n\nReading an agent's day of work that way catches things the editor did not: the file you never opened, the dependency added in `package.json`, the migration nobody mentioned. Push the branch, open the PR, read it, merge with the button, and `git pull` on your machine to catch up.\n\nGitHub will also show an automated review from a model on the PR if you turn it on. Treat that as a second reader who has not run the app, which is what it is. It finds real things and it misses the things this course teaches you to test for.\n\n## Public or private\n\nA public repository is readable by anyone and indexed within minutes. Bots scanning for keys find a committed secret faster than you can rotate it. Keep repositories private by default and make one public as a deliberate act, after checking the whole history rather than the current files:\n\n```bash\ngit log -p | grep -iE \"sk-|api[_-]?key|password\" | head\n```\n\nIf that finds anything, the key has to be revoked at the provider, because rewriting history to remove it is both hard and pointless once it has been pushed.\n\n## What a remote is not\n\nIt is not a backup of uncommitted work; only commits travel. It is not the deployment, though the host watches it and deploys on push, which is the next lesson but one. And it is not a substitute for your local copy: if GitHub is unavailable for an hour, you keep working, because git on your machine never needed it."},"check":{"question":"git push fails with 'rejected: non-fast-forward'. What has happened?","options":[{"text":"Your access token has expired and needs replacing.","correct":false,"tempting":"Authentication failures say so in the message; non-fast-forward is about history, not identity."},{"text":"Your commit is too large for the remote to accept.","correct":false,"tempting":"Size limits produce a different, explicit error naming the file."},{"text":"You need to add --force so the push goes through, as most search results for the message suggest.","correct":false,"tempting":"That is the fix search results offer, and it overwrites the remote's commits, the very thing the rejection exists to prevent."},{"text":"The remote has commits your branch does not, so git will not overwrite them.","correct":true}]},"prev":{"slug":"branches-for-the-risky-change","title":"Branches for the risky change","url":"https://addaly.com/learn/building-apps-with-ai/branches-for-the-risky-change"},"next":{"slug":"merge-conflicts-without-panic","title":"Merge conflicts without panic","url":"https://addaly.com/learn/building-apps-with-ai/merge-conflicts-without-panic"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'GitHub, and what a remote is for', in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/github-and-the-remote (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"GitHub, and what a remote is for","courseTitle":"Building Apps with AI","module":{"index":5,"slug":"save-points-and-shipping","title":"Save points, and shipping to a stranger"},"answer":null,"keyIdea":"A remote is another copy of the history that only accepts commits building on what it already has, which is what a rejected push is telling you.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"a-remote-is-just-another-copy","runs":[{"text":"A remote is just another copy"}]},{"t":"p","runs":[{"text":"Git on your laptop is complete. A remote is a second copy of the same history somewhere else, and "},{"text":"origin","code":true},{"text":" is the conventional name for the main one. Nothing about it is special; you could have a remote on a USB stick."}]},{"t":"p","runs":[{"text":"GitHub is the most used host, and GitLab, Codeberg and Bitbucket do the same job. All of them offer free private repositories, which is the default you want until you decide otherwise."}]},{"t":"code","lang":"bash","label":"Bash","text":"git remote -v                                     # what remotes exist\ngit remote add origin git@github.com:you/app.git  # attach one\ngit push -u origin main                           # send main, remember the pairing"},{"t":"p","runs":[{"text":"The "},{"text":"-u","code":true},{"text":" sets up tracking so that later "},{"text":"git push","code":true},{"text":" and "},{"text":"git pull","code":true},{"text":" know where to go without being told."}]},{"t":"h2","id":"getting-in","runs":[{"text":"Getting in"}]},{"t":"p","runs":[{"text":"GitHub stopped accepting passwords for git operations in 2021. A push that asks for a password and rejects the correct one is this: it wants a personal access token, or an SSH key. The simplest route is the GitHub command-line tool, "},{"text":"gh auth login","code":true},{"text":", which walks through either. Do it once per machine."}]},{"t":"h2","id":"push-fetch-pull","runs":[{"text":"Push, fetch, pull"}]},{"t":"p","runs":[{"text":"git push","code":true},{"text":" sends commits the remote does not have. It only succeeds if your branch contains everything the remote’s branch contains: your history must be a continuation of theirs. When it is not, you see "},{"text":"rejected: non-fast-forward","code":true},{"text":". Something has been added on the remote that you do not have: you pushed from another machine, you edited a file in the web interface, or an automated tool committed. The fix is to bring those commits down first."}]},{"t":"p","runs":[{"text":"git fetch","code":true},{"text":" downloads the remote’s commits without touching your files. It is always safe, and it lets you look before merging: "},{"text":"git log main..origin/main","code":true},{"text":" shows what is there that you lack."}]},{"t":"p","runs":[{"text":"git pull","code":true},{"text":" is fetch followed by merge. It is what most people run, and the merge can conflict like any other."}]},{"t":"p","runs":[{"text":"After a fetch, "},{"text":"git status","code":true},{"text":" reports things like “ahead by 2, behind by 1”. Ahead: commits you have not pushed. Behind: commits you have not pulled. "},{"text":"origin/main","code":true},{"text":" in that output is your last known view of the remote’s branch, updated only when you fetch."}]},{"t":"h2","id":"a-new-machine","runs":[{"text":"A new machine"}]},{"t":"code","lang":"bash","label":"Bash","text":"git clone git@github.com:you/app.git\ncd app\nnpm install\ncp .env.example .env    # then fill it in by hand"},{"t":"p","runs":[{"text":"The last line is the one that gets forgotten, because "},{"text":".env","code":true},{"text":" is ignored by design and so it is the one thing the clone does not carry. An app that fails on a fresh clone with a configuration error is almost always this."}]},{"t":"h2","id":"pull-requests-when-you-are-alone","runs":[{"text":"Pull requests when you are alone"}]},{"t":"p","runs":[{"text":"A pull request is a proposed merge shown as a diff, in a browser, with a comment box. It exists for teams, and it is worth using by yourself, for one reason: it puts the whole of a change on a different screen from the one you built it on, all at once, file after file, with a preview deployment attached."}]},{"t":"p","runs":[{"text":"Reading an agent’s day of work that way catches things the editor did not: the file you never opened, the dependency added in "},{"text":"package.json","code":true},{"text":", the migration nobody mentioned. Push the branch, open the PR, read it, merge with the button, and "},{"text":"git pull","code":true},{"text":" on your machine to catch up."}]},{"t":"p","runs":[{"text":"GitHub will also show an automated review from a model on the PR if you turn it on. Treat that as a second reader who has not run the app, which is what it is. It finds real things and it misses the things this course teaches you to test for."}]},{"t":"h2","id":"public-or-private","runs":[{"text":"Public or private"}]},{"t":"p","runs":[{"text":"A public repository is readable by anyone and indexed within minutes. Bots scanning for keys find a committed secret faster than you can rotate it. Keep repositories private by default and make one public as a deliberate act, after checking the whole history rather than the current files:"}]},{"t":"code","lang":"bash","label":"Bash","text":"git log -p | grep -iE \"sk-|api[_-]?key|password\" | head"},{"t":"p","runs":[{"text":"If that finds anything, the key has to be revoked at the provider, because rewriting history to remove it is both hard and pointless once it has been pushed."}]},{"t":"h2","id":"what-a-remote-is-not","runs":[{"text":"What a remote is not"}]},{"t":"p","runs":[{"text":"It is not a backup of uncommitted work; only commits travel. It is not the deployment, though the host watches it and deploys on push, which is the next lesson but one. And it is not a substitute for your local copy: if GitHub is unavailable for an hour, you keep working, because git on your machine never needed it."}]}],"check":{"question":[{"text":"git push fails with ‘rejected: non-fast-forward’. What has happened?"}],"options":[{"text":[{"text":"Your access token has expired and needs replacing."}],"correct":false,"tempting":[{"text":"Authentication failures say so in the message; non-fast-forward is about history, not identity."}]},{"text":[{"text":"Your commit is too large for the remote to accept."}],"correct":false,"tempting":[{"text":"Size limits produce a different, explicit error naming the file."}]},{"text":[{"text":"You need to add --force so the push goes through, as most search results for the message suggest."}],"correct":false,"tempting":[{"text":"That is the fix search results offer, and it overwrites the remote’s commits, the very thing the rejection exists to prevent."}]},{"text":[{"text":"The remote has commits your branch does not, so git will not overwrite them."}],"correct":true,"tempting":null}],"order":[0,1,3,2],"why":null},"prev":{"slug":"branches-for-the-risky-change","title":"Branches for the risky change","minutes":8},"next":{"slug":"merge-conflicts-without-panic","title":"Merge conflicts without panic","minutes":7}}}